タグ別アーカイブ: PHP

PHPテンプレートエンジンを使おう 素のPHP編

このブログの作成当初、「作成するサイトにはテンプレートエンジンとしてSmartyを採用します!」とか言ってしまっているのだけれど、現在のところ結局Twigに落ち着いてしまっている。いや、色々と理由があったのだけれども、それらをすっ飛ばして、当初の宣言を無かった事にしてTwigの紹介を始めても、というところがあるので、本当にさらっとテンプレートエンジンとは何かと、諸事情で通過してしまったSmartyについて書こうと思う。

テンプレートの考え方

というわけで、まずテンプレートとは何か。テンプレートというのは、Dreamweaverを始めとして数々のPCアプリケーションではそのままの単語が使用されている、作成する書類の「ひな形」の事だ。つまりもう文書構造まではでき上がっているファイルに、タイトルだの本文だのオリジナルな情報を加えて保存すると、立派な当たり障りの無いファイルが出来る。また、オリジナル情報の部分を書き換える事で、似たような文書を量産する事が出来る、そういった仕組みだ。

WEBサイトを構成するhtmlファイルについても、サイトの全ページを集めて見比べてみると、共通部分が多い事に気付くだろう。たとえばヘッダ部分とかフッタ部分とか。ブログなどでは記事横のサイドバーなども共通するし、広告を表示する設定にしているならば、その部分も共通している。ちょっと例を挙げてみよう。

 
<!-- トップページ -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>サンプル株式会社 トップページ</title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>
 
<!-- 各商品紹介ページ -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>サンプル株式会社 商品A</title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<article>
<h1>商品Aの概要</h1>
<p>説明説明説明</p>
.
.
</article>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>

この2つのページは、ほぼ共通部分によって構成されている。違いと言えば、ページタイトルとid=”main”の中のarticleだけだ。
テンプレートを使わない方法では、たとえばテキストファイルでページタイトルとarticle以外の部分を空白にした文書を作っておき、新しくページを加える際にはそのファイルを元にして新ページのファイルを書くといった手順になるだろう。
けれども、この方法だと全ページのサイドバーに項目を増やしたいなどとなったとき、テキストファイルを開いてコピー、開いてコピーと忙しい。非現実的である。

Dreamweaverにおけるテンプレート

そこで、Dreamweaverなんかではテンプレートファイル.dwtを作成して、テンプレート全体に渡る変更はこの.dwtファイルを弄れば良いというような仕組みでやっている。

 
<!-- テンプレートファイル.dwt -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<!-- TemplateBeginEditable name="doctitle" -->
<!-- TemplateEndEditable -->
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<!-- TemplateBeginEditable name="mainarea" -->
<!-- TemplateEndEditable -->
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>

このような.dwtファイルを作成しておき、新しくページを作成する際はこのテンプレートを元にTemplateBeginEditable…というhtmlコメントアウトとTemplateEndEditable…に挟まれた部分にページオリジナルのコンテンツを入れる。もしテンプレート自体に変更が起きた場合は、そのテンプレートを元に作成された全てのページへの同様の変更が有効になるといった具合だ。

PHPでテンプレート方式を利用する

でも、Dreamweaverはお高い。現在ではCreative Cloudなんかで継続的にコストがかかるし、契約を止めてしまえば作成した.dwtファイルなんて無用の長物になってしまう。そこで、テンプレート機能のためだけにDreamweaverを導入するくらいならば、プログラムで何とかしてしてしまおうという考え方もあるだろう。
幸いPHPにはテンプレート方式を利用するのに具合の良い仕組みがある。一番大きいのは、html文書の途中にインラインで出力命令を埋め込めること。そこで、実際ページ毎に出力が異なる部分にあらかじめecho命令を書いておいたものをテンプレートファイルとして保存し、各ページのアドレスに対応するファイルから変数に出力する値を設定して呼び出せば良いのだ。

 
//テンプレートファイル template.php
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title><?php echo $title; ?></title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<?php echo $honbun; ?>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>
 
//トップページ index.php
<?php
$title = "サンプル株式会社 トップページ";
$honbun = "<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>";
include("template.php");
?>
 
//各商品紹介ページ itemA.php
<?php
$title = "サンプル株式会社 商品A";
$honbun = "<article>
<h1>商品Aの概要</h1>
<p>説明説明説明</p>
.
.
</article>";
include("template.php");
?>

ショートタグを使えばプレースホルダに近く

呼び出し側のファイルでは、変数を参照する箇所で毎度毎度インラインのPHPを呼び出し、echo命令を打っている。このままでも別に構わないのだろうが、よりテンプレートっぽくするために、プレースホルダに近い省略表現にしてみよう。
プレースホルダというのは、テンプレートエンジンで使われる用語の一つで、後から何か別のものを置く場所に仮置きしておくものである。Dreamweaverではイメージプレースホルダーという名前の、仮置き用のイメージをおける機能があるが、これはとりあえず関係ない。先の例でechoを使っていた部分を、そこに吐き出す変数名とプレースホルダであることを示すちょっとした目印表現の組み合わせ程度に簡略化すれば、仮置きらしくなる。
PHPは本当にテンプレートに向いている言語というべきか、echo命令の同義語として以下のような変換が出来る。

//これが
<?php echo $title; ?>
 
//これと同義語に
<?= $title ?>

幾分短くなった。この書き換えは、PHP5.4以降のヴァージョンなら標準で、5.3以前のヴァージョンなら、php.iniを書き換えてshort_open_tagをOnにすればできる(残念ながらini_setでは不可能)。
テンプレート使用のメリットの一つとして、デザイナーとプログラマーの作業の分担というものがある。PHPは全く分からないというデザイナー・htmlコーダにテンプレートファイルを渡して、”<?=”と”?>”に囲まれている部分には絶対に手を付けるなと言っておけば、デザイン作業が進みつつ、プログラマも並行してプログラムを書くことが出来る。ということで、テンプレート導入は、WEB制作の大きなパラダイムとなったわけだ。


標準出力のバッファリングを有効化するPHP関数ob_start

PHPは、WEBブラウザという手近な実行環境を持つプログラミング言語です。そして、WEBブラウザ以外にも実行環境を持つプログラミング言語です。WEB制作の目的でPHPを習得しており、かつPHPが初めてのプログラミング言語である場合などには気付かないかもしれませんが、コマンドライン上でPHPを動かすことも可能なわけです。
したがって、echo命令等が出力する先は、常にWEBブラウザのウィンドウというわけではありません(そもそもWEBブラウザで動いていないPHPがどこのウィンドウに出力するというのでしょう)。echo命令のマニュアルに書いてあるように、出力先はあくまで”標準出力”となっています。

標準出力をバッファリングする

さて、WEBブラウザ上でPHPを実行する場合、この”標準出力”として、WEBブラウザ上のウィンドウが指定してあります。そのおかげあってecho命令はいつもブラウザ上に文字列を吐き出すわけですが、echo命令が吐き出す内容を一時的に変数に入れたい場合や、ファイルに保存したい場合などは、プログラム中でタイミングを指定して、標準出力のバッファリングを行うことが可能です。
標準出力バッファリングのための関数には、output bufferingの略、ob_という接頭辞がついています。プログラム中のバッファリングを開始したいポイントでob_start関数をコールし、バッファリングを停止したいポイントでob_end_clean関数ないしob_end_flush関数を呼びます。

<?php
echo "a";
ob_start();
echo "b";
echo "c";
ob_end_clean();
echo "d";
?>

このプログラムをWEBブラウザで走らせると、ブラウザのウィンドウには”ad”と出力されます。ob_startとob_end_clean関数に挟まれている部分で出力されるべき”bc”は、その間標準出力がジャックされているので表示されません。
一方、終了時の関数をob_end_cleanではなくob_end_flushにした場合、バッファリングの終了時に貯めていた出力内容を標準出力に開放します。そこで、ブラウザの出力は”abcd”となります。

バッファリングした内容を変数に格納する

バッファリングした内容を、そのまま破棄あるいは標準出力に出力してしまうのも面白くありません。ob_get_contentsという関数を使い、変数に格納して取り出してみましょう。

<?php
echo "a";
ob_start();
echo "b";
$buffer = ob_get_contents();
echo "c";
ob_end_clean();
echo "d";
echo $buffer;
?>

このプログラムの出力結果は”adb”となります。ob_get_contents関数が呼ばれた時点でのバッファを変数に格納し、echo “d”;の後に吐き出した結果ですね。

応用:他ファイルでのPHPスクリプト実行結果を読み込む

このようなバッファリングが役に立つケースとして、他ファイルでのPHPスクリプトの、”実行結果”を読み込み、変数に格納しておきたい場合が挙げられます。
たとえばWEBページのコンテンツ部分のHTMLを、PHPスクリプトを使って動的に生成する場合について考えます。仮に、contents.phpというファイルを作成していた場合、呼び出し側の記述は以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
.
.
</head>
<body>
.
.
<div id="contents">
<?php include("contents.php"); ?>
</div>
.
.
</body>
</html>

この例の場合、includeは1回で済んでいますが、contents.phpの生成内容を同じページ内で何度も繰り返す必要がある場合、都度includeするのではなく、変数に格納しておいた方がよいということになります。

<?php
ob_start();
include("contents.php");
$contents = ob_get_contents();
ob_end_clean();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
</head>
<body>
.
.
<div id="contents1">
<?php echo $contents; ?>
</div>
<div id="contents2">
<?php echo $contents; ?>
</div>
.
.
</body>
</html>

また、ページによってどのファイルを呼び出すか、分岐の可能性がある場合などには、この書き方ではincludeの周辺が分岐ロジックの記述などで膨れ上がってしまい、呼び出し側のHTMLの視認性を損ないます。それを避けるため、コンテンツを呼び出す部分では変数の出力にとどめ、変数に入れる値をPHPのロジック部分にてあらかじめ求めておくといった形にする方がベターです。その場合にも、出力バッファ関数が役に立ちます。

<?php
ob_start();
if("条件式"){
include("contents1.php");
} else {
include("contents2.php");
}
$contents = ob_get_contents();
ob_end_clean();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
</head>
<body>
.
.
<div id="contents">
<?php echo $contents; ?>
</div>
.
.
</body>
</html>

その他、色々とアクロバティックなコードを書くときに使えたりします。もちろん推奨はしませんが(笑)。


PHP、JavaScriptにおける三項演算子(条件演算子)

ライブラリなどの出来合いコードを読解するようになると、割と目にすることも多い三項演算子(条件演算子とも)。条件分岐のifクローズをただの式に変えてしまうということで、その使用には抵抗がある向きも多いと思う(実際私もあまり好きではない)。ただ、三項演算子イコール単純なifクローズの省略記法というわけではなく、少々のメリットも存在する。その辺りを含めて、PHPとJavaScriptにおける三項演算子の使い方をまとめてみることにした。

三項演算子の基本的使用法

三項演算子については、PHP、JavaScriptともにC言語と同じ”?”や”:”といった演算子を使う。とりあえずPHPの場合で説明すると、

//ifクローズを使った場合
if($age >= 20){
$alcohol = "OK";
} else {
$alcohol = "NG";
}
 
//三項演算子を使った場合
//(式1) ? (式2) : (式3);
//(式1)が真であるとき(式2)、偽であるなら(式3)
$alcohol = $age >= 20 ? "OK" : "NG";

という具合になる。JavaScriptの場合は変数に$がつかない程度の違い。

三項演算子に慣れて親しんでいないと、直感的に理解できず少し混乱してしまう部分もあるかもしれない。上の例で見ると、変数$bへの代入で使われる”=”と、(式1)であるところの”$a <= 100"のような条件式で使われる"<="等の記号が字面上ややこしく見える。 三項演算子は算術演算子のように式を評価した結果を返すと考えなければならない。つまり、

$b = $a * 100;

こういった代入式と同じで、右辺を評価したが代入されているのだ。三項演算子は代入演算子より優先度が高い。

三項演算子に値ではなく式を入れる

ところで、三項演算子を用いた例として、以下のようなものもあり得る。PHPでの例。

//例1
$age >= 20 ? $alcohol = "OK" : $alcohol = "NG";
 
//例2
$age >= 20 ? print "OK" : print "NG";

これは、前回PHPの言語構造を説明した際に出てきた式とは何かというところで、式とは値をもつものという定義により、文法的にパスすると書いた例だ。もちろんJavaScriptの式の定義も同じであり、たとえば

//例1
age >= 20 ? alcohol = "OK" : alcohol = "NG";
 
//例2
age >= 20 ? alert("OK") : alert("NG");

このような式の代入が可能だ。

エルビス演算子(PHP5.3以降のみ)

PHPの場合、三項の真ん中の(式2)を省略して、(式1)がTRUEである場合値も(式1)になる書き方が出来る。この場合、”?:”という演算子を使う。時計回り90度回転したときに誰かさんの顔に見えるので、エルビス演算子と呼ばれる。

//(式1) ?: (式3);
//(式1)が真であるとき(式1)、偽であるなら(式3)
$tobaccoOkFlag = $alcohol == "OK" ?: 0;
 
//つまり、こう書いているのと同じ
$tobaccoOkFlag = $alcohol == "OK" ? 1 : 0;

ただし、PHP5.3以降でないと使えない。

三項演算子の右結合と左結合

三項演算子を多重に用いる場合、評価する順序が問題となってくる。標準的な順序は右結合と呼ばれる、右から評価していく方法。JavaScriptは右結合なので、JavaScriptの例を書く。

//右結合の場合
tobaccoOkFlag = pregnantFlag ? 0 : alcoholOkFlag ? 1 : 0;
 
//評価順序を分かりやすく書くと
tobaccoOkFlag = pregnantFlag ? 0 : ( alcoholOkFlag ? 1 : 0 );

この場合、多重に用いられた三項演算子のうち、右側のものが優先して評価される。alcoholOkFlagが0の場合は、pregnantFlag、つまり妊婦かどうかに関係なくtobaccoOkFlagは0になる。
一方、PHPはこれとは逆の左結合になるのだが、PHPの場合で解釈してみよう。

//左結合の場合
$tobaccoOkFlag = $pregnantFlag ? 0 : $alcoholOkFlag ? 1 : 0;
 
//評価順序を分かりやすく書くと
$tobaccoOkFlag = ( $pregnantFlag ? 0 : $alcoholOkFlag ) ? 1 : 0;

$pregnantFlagつまり妊婦であるかどうかを評価して、妊婦なら0、そうでないなら$alcoholOkFlagの値によって$tobaccoOkFlagが決まる。例があまり直感的なものに出来なかったので、申し訳ない気持ちでいっぱい。

左結合になる言語はほぼPHPのみ。というのも、PHPが左結合になってしまっているのは言語仕様を決めた際の間違いをひきずっているらしい。今更変えられないので、今後もPHPの短所として残り続けるだろう。

右結合の場合のif-elseの書き方

三項演算子が右結合だとif-elseがスッキリ書けるというメリットがある。右結合のJavaScriptの例。

//if-elseを使う場合
if(age >= 65){
message = "高齢者料金になります。";
} else if(age >= 20){
message = "大人料金になります。";
} else if(age >= 6){
message = "小人料金になります。";
} else {
message = "幼児料金になります。";
}
alert(message);
 
//三項演算子を使う場合
message = age >= 65 ? "高齢者料金になります。"
: age >= 20 ? "大人料金になります。"
: age >= 6 ? "小人料金になります。"
: "幼児料金になります。";
alert(message);

さらに、ifクローズが制御構造であるのに対して、三項演算子は式であるため、関数の直接の引数にすることも出来る。

//三項演算子を引数に
alert(age >= 65 ? "高齢者料金になります。"
: age >= 20 ? "大人料金になります。"
: age >= 6 ? "小人料金になります。"
: "幼児料金になります。");

ここまでスッキリするのなら、三項演算子を導入することも検討してよいのではないだろうか。
逆にPHPでは、三項演算子を多重に使用する事は避けた方が良いのかもしれない。


PHPのechoとprintは言語構造だというけど、言語構造とは何か

細かいことが気になり出すと、調べ尽くして解答を出すまで次の事に移れないタイプで。
PHPを覚えたての頃、標準出力にテキストや変数などを書き出す際に使う命令、echoとprintがあるということを学んだ。そして、解説書に書いてある通り、これらの命令はそれほど違わないので好きな方を使えば良いと理解した。

たまたまPHPにおける三項演算子を調べていたとき、echoとprintで挙動が異なるという例に当たった。三項演算子については、まとめ直した投稿をそのうち上げる予定だけれど、”?”や”:”といった演算子で挟まれる部分は、式でなければならないらしい。そして、echoなどは式という条件を満たさない言語構造であるから、入れるとエラーが出ますよ。ちなみにprintも言語構造ですよ。でも、関数のように振る舞うからエラーが出ませんよ。といったよく分からない説明があった。

//エラーにならない(理由:式だから)
$alcohol = $age >= 20 ? "OK" : "NG";
echo $alcohol;
 
//エラーにならない(理由:式だから)
$age >= 20 ? $alcohol = "OK" : $alcohol = "NG";
echo $alcohol;
 
//エラーにならない(理由:式だから)
$alcohol = $age >= 20 ? htmlentities("OK") : htmlentities("NG");
echo $alcohol;
 
//エラーになる(理由:言語構造だから)
$age >= 20 ? echo "OK" : echo "NG";
 
//エラーにならない(理由:言語構造だけど関数のように振る舞うから)
$age >= 20 ? print "OK" : print "NG";

PHPの式とはなんだろう?

上の3つの例でエラーにならないのは、三項演算子の書式(式1)?(式2):(式3);これに即しているため。ではそもそもPHPにおける式の定義とは何だろうか。マニュアルを見るとちゃんと一項目があって式とは値があるものだそうです。
一番上の例、”OK”や”NG”というのは、値である。これはとても直感的にわかる。
上から2番目は代入式だ。ここで結構ショッキングな事実になるけれども、実は代入式では、左辺の$alcoholに右辺の値”OK”が代入されるのみならず、式自体も右辺と同じ値を持つということらしい。
したがって、代入式は値を持っているのでエラーにならない。
上から3番目。関数を呼んで実行するのも式だ。htmlentities関数はstring型を返り値として返す。また、特に値を返す設定をしていない関数(たとえば、ユーザ定義関数でreturnを行わないタイプ)でも、自動的にNULLを返す設定となっている。つまり値を返すから式だ。

言語構造とは

では、関数ではない言語構造と呼ばれるものは一体なんなのだろう。言語構造(Language Constructs)とは、英語で見れば何のこともない。言語を構成する要素である。ifとかwhileとかといったものと同じ。制御構造を作るためのifやwhileとともに、言語に最初から存在するものとしてハードコードされているため、関数のように決まった形でないと呼び出せないというような制約はない。echoはecho “文字列”;のようにもecho(“文字列”);と関数のようにも呼び出せるが、これは便宜のためたまたまどちらの書き方でも呼び出せるようにハードコードされているからで、echoの属する言語構造というカテゴリがこうした文法的制約を持っているわけではない。
関数ではないということで、当然のことながらcall_user_func(“echo”,”文字列”);こういった呼び出し方も出来ない。

echoとprintの違いとは

では、echoとprintが両方とも言語構造でありながら、異なるというところはどこだろうか。実は先程echoが便宜のため関数のように括弧を付けても呼び出せると書いたが、printはさらに返り値として常に1を返してくる。つまり、printの方が関数への擬態がうまい。そしてその分処理が重い。
そして、何らかの値を返すということは、printは式になるのである。そのため、三項演算子の中の式としても使えていたのである。

なるほどよく分かった。ちなみに違いとしてもう一つ、echoはecho”値1″,”値2″;のように使う事も出来るというものがあり、大抵の解説書にはechoとprintの違いとしてこちらが説明されているのではないかと思う。そっちの方が重要かもしれないね。

関数っぽいけど言語構造のもの一覧

最後に、一見関数のように見えて実は言語構造という例をいくつか挙げておこう。

返り値の型 名前 機能
なし echo 文字列を出力
int print 文字列を出力
array array 配列を生成
array list 配列の形式で、変数に値を代入
mixed eval 文字列をPHPコードとして評価
なし unset 変数を破棄する
bool isset 変数がセットされているか調べる
bool empty 変数が空かどうか調べる
なし exit スクリプトの実行を終了
なし die exitと同等
mixed include ファイルを読み込む
mixed require ファイルを読み込む(読み込めないと停止)

同列に扱っているけど、既に説明したとおり共通した呼び出し方の制約はない。フリーダムなやつらなので注意が必要。


Conceptual:EXIFviewerに撮影地の地図表示機能を追加

予告通り、ローカル領域にあるjpegファイルのEXIF情報を表示するWEBサービス、EXIFviewerの機能を追加してみた。今回追加した機能は、jpegファイルのEXIFの中に撮影地情報が格納されていた場合、Google Mapsで表示するというもの。EXIFファイルの中のGPS情報をテーブルに表示するまでは前のヴァージョンでも実現していたため、今回の説明はGoogle Maps APIと連携する際の注意点が中心となる。

連携の前段階、既存プログラムの手直し

前のヴァージョンのEXIFviewerの作成手順では、表の成形部分で特にid指定などもしないセルを吐き出していた。今回、緯度経度情報をGoogle Maps APIに渡すには、JavaScript(jQuery)を使ったクライアントサイドでの値取得が必要となる。そのため、値の入ったセルにidで一意の名前をつけるようにする。また、前回手を付けられなかった値のサニタイジングもついでに行う。

if(!empty($_POST["path"])){
if($exif = exif_read_data($_POST["path"],NULL,true)){
echo "<h2>EXIF情報</h2>";
echo "<table class='exiftable table table-striped'><thead><tr><th>キー名</th><th>値</th></tr></thead><tbody>";
foreach ($exif as $key => $section) {
    foreach ($section as $name => $val) {
	if(is_array($val)){
	foreach ($val as $valname => $vval) {
	echo "<tr><td>" . htmlentities($key,ENT_QUOTES,"UTF-8") . ' ' . htmlentities($name,ENT_QUOTES,"UTF-8") . ' ' .  htmlentities($valname,ENT_QUOTES,"UTF-8") . "</td><td id=" . '"' . htmlentities($name . $valname,ENT_QUOTES,"UTF-8") . '">' . "$vval</td></tr>";
	}
	} else {
	echo "<tr><td>" . htmlentities($key,ENT_QUOTES,"UTF-8") . ' ' . htmlentities($name,ENT_QUOTES,"UTF-8") . "</td><td id=" . '"' . htmlentities($name,ENT_QUOTES,"UTF-8") . '">' . htmlentities($val,ENT_QUOTES,"UTF-8") . "</td></tr>";
	}
    }
}
echo "</tbody></table>";
} else {
echo "画像のEXIFデータが見つかりませんでした。";
}
} else {
echo "画像をセットして下さい。";
}

これがPHP部分。画像のEXIFにGPSの情報が不足なく入っていた場合に初めて処理を行うようにするので、このPHP部分で情報の不足をチェックし、地図表示の実行フラグとして非表示のセルにでも入れれば、JavaScript側の記述がスマートに済む筈。今回それをしなかったのは、不特定多数が利用する可能性があるため、出来るだけサーバ側の負担を軽く、クライアントサイドで処理を担保してもらうように設計したため。

Google Maps APIの使用方法

Google Maps APIを使いたい場合、htmlのヘッダ部で読み込む事になる。

<head>
.
.
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
</head>

こういった具合。クエリストリングのsensor=falseという部分は、利用者の位置情報を求めるかどうかということで、falseに設定しておけば鬱陶しい確認ダイアログが出ないだろう。
今回は、id=”googlemapdiv”というdiv要素の中にマップを表示する場合で説明する。jQueryのreadyの中に、以下のように記述した。

$(function(){
.
.
var option = {
zoom: 17,//地図の縮尺初期設定(0〜19)
center: new google.maps.LatLng(imagelat,imagelong),
//地図の中心の座標 LatLng(緯度,経度)
//とりあえず今回はimagelat,imagelngというユーザ定義変数を使った
//もちろんこれより前に宣言されてないといけない
mapTypeId: google.maps.MapTypeId.ROADMAP,//地図の種別初期設定
zoomControl: true,//ズームGUIの表示非表示
streetViewControl: true,//ストリートビュー表示可能にするか
scaleControl: true,//ズームスライダーGUIの表示非表示
mapTypeControl: true//地図の種別切り替えGUIの表示非表示
};
var map = new google.maps.Map($("#googlemapdiv")[0],option);
var marker = new google.maps.Marker({
				position : option.center,
				map : map
			});
});

コントロールの指定については必須ではない。google.maps.Mapメソッドに引数として地図の設置場所(DOMオブジェクトで指定)と、各種オプションを代入する。

Google Maps API用に緯度経度を10進法に変換する

さて、お膳立ても出来たので、単純にPHP側で成形した表のGPS情報の部分を取得し、Google Maps APIに渡せば良い、、とはいかない。
iPhone等で記録されるEXIFのGPSデータは、60進法で記録されているが、Google Maps APIが受け付けるのは10進法の数字なのだ。そこで、画像から取得した緯度経度を60→10進法に変換する処理を用意しないといけない。
60→10進法変換の方法だが、たとえば60進法で12.3という数字があった場合、10の位の1は10進法の60*1を表し、1の位の2は1*2、小数点第一位の3は60分の1*3を表している…といった数学の知識を、思い出してほしい。iPhoneなどが採用している60進法の表示方式では、”北緯36度46.5分0秒,東経137度54.52分0秒”といったように書くが、これは成形された表ではこのように表示されている。

EXIFでの緯度経度

EXIFでの緯度経度

GPSLatitudeRefやGPSLongitudeRefが東西南北(EWSN)を表している事は分かり易い。GPSLatitude 0が表すのは、緯度の”度”の部分。”36/1″という数字は、分母が1で分子が36ということ、つまり分数だ。同様に、GPSLatitude 1は100分の4650、つまり46.5分を表すといったように、面倒臭い書き方をしている。しかもこの値が60進法なのだ。
変換手順は、分数の文字列をなんとか少数に直して、それから10進法への変換を行うという形になる。
そして最後に10進法に直した後の値に、西経、南緯の場合マイナスをつけるという手順も必要だ。

 
//緯度を求める
var imagelat = parseFloat($("#GPSLatitude0").text().split("/")[0])/parseFloat($("#GPSLatitude0").text().split("/")[1]) + parseFloat($("#GPSLatitude1").text().split("/")[0])/(parseFloat($("#GPSLatitude1").text().split("/")[1])*60) + parseFloat($("#GPSLatitude2").text().split("/")[0])/(parseFloat($("#GPSLatitude2").text().split("/")[1])*3600);
if($("#GPSLatitudeRef").text() == "S"){
imagelat = "-" + imagelat;
}
 
//経度を求める
var imagelong = parseFloat($("#GPSLongitude0").text().split("/")[0])/parseFloat($("#GPSLongitude0").text().split("/")[1]) + parseFloat($("#GPSLongitude1").text().split("/")[0])/(parseFloat($("#GPSLongitude1").text().split("/")[1])*60) + parseFloat($("#GPSLongitude2").text().split("/")[0])/(parseFloat($("#GPSLongitude2").text().split("/")[1])*3600);
if($("#GPSLongitudeRef").text() == "W"){
imagelong = "-" + imagelong;
}

このようにして出てきた結果を、google.maps.LatLngメソッドの引数として与えてやれば良い。

ということで、EXIFViewerでの地図表示が可能になった。労力のわりに、いまや出来て当たり前の機能のように見えるからどうにも報われないのです。


Conceptual:シンプルなPHP製EXIF表示サービスEXIFviewerを公開

わけあって写真の位置情報なんかを弄るプログラムが必要になって、EXIFについて調べ直している。

調べ直しているというのは、以前に一度PHPにおけるEXIFの扱いなどについて調べていて、DamniPhoneEXIFという誰も得しないWEBサービスに応用したことがあるからだ。DamniPhoneEXIFはPHP標準のEXIFクラスを使わずに、PELというライブラリを使っている。PELを使わないとEXIFデータの書き込みが出来ないためであるが、初期のiPhone3Gで起こる位置情報の経度の東西が逆転するバグを解決するためだけのサービスとして作ったので、そこから色々な機能を建て増していくには不適当だった。それなら単純に標準のクラスでEXIF表示するサービスから作り直そう、ということでEXIFviewerを作った。

キモは、ユーザのローカルファイルを一旦HTML5のDataURLで受けているため、サーバに画像のコピーを作らずに画面リロード後の確認用画像の表示が出来ているというところ。これはOnlyCSViewでもやっていたことなので、詳しいやり方についてはOnlyCSViewの作成エントリを参照してほしい。

ところでEXIF形式って何?

EXIF形式というのは、JPEGやTIFFなどの画像ファイルに画像についての情報を付与してまとめたデータ形式だ。デジカメメーカーが主体となって、写真の撮影状況(撮影日、しぼり、感度、位置情報etc)のデータを画像に埋め込めるようにしたもの。正確に言うと、埋め込むわけではなく新たなファイル形式の大括りを作って、そこに画像の情報と当の画像ファイルをパッケージ化する。そのようにして出来たファイルの拡張子は.jpgや.tiffそのままなので、一見するとEXIFの情報が入ったファイルとの区別がつかない。かくして、デジカメユーザは知らず知らずに、EXIF形式のお世話になっている場合が多い。
ではどのように情報を格納するかというと、TIFF形式以来のファイル先頭へのタグ付けにより、情報を格納している。まずTIFFという名前が、(Tagged Image File Format)の略であり、この形式が登場したことにより、画像ファイルに画像の情報を入れられるようになった。JPEGファイルは同様のタグ付け形式のJFIFというフォーマットをほぼ標準としており、このJFIFをもっと大げさにしたものがEXIFというわけだ。

EXIF形式の構造 ざっくばらんに

つまり、EXIFはTIFFの落とし子だ。ファイルのバイナリデータの一番最初には、TIFFヘッダと呼ばれる8バイトのデータがある。この8バイトのデータのうち4バイトは、次のまとまりへの参照(ファイルの頭からの距離)を格納している。このように、ひとまとまりの情報の中に、他のまとまりの場所情報をセットで含んでいるのが、TIFF形式の特徴だ。
EXIFにどんなまとまりがあるかというと、IFD(Image File Directory)という名前のまとまりがあり、そのまとまりの中にさらに複数の情報がぎっしり詰まっている。
IFDの中にはEXIF IFDというものや、GPS IFDというものがあり、EXIF情報をいただきたい場合この場所にアクセスして情報を見る必要があるわけです。ややこしいね。

PHPのEXIFクラスでのアクセス

先程紹介したPelでは、イメージファイルをコンストラクタに与えて、ゲッターメソッドで一つ一つのまとまりの鍵を開けてアクセスしなければならない。DamniPhoneEXIFでGPSの東西を変えるだけでも、こんなに入れ子のプログラムができてしまった。

<?php
require_once('PelJpeg.php');
if(!empty($_POST["path"])){
$pj = new PelJpeg($_POST["path"]);
if($pjexif = $pj->getExif()){
if($pjtiff = $pjexif->getTiff()){
if($pjifd = $pjtiff->getIfd()){
if($pjgps = $pjifd->getSubIfd(PelIfd::GPS)){
if($gpsarray = $pjgps->getEntries()){
if(isset($gpsarray[3]) && isset($gpsarray[4])){
if(($gpsarray[4]->getText()) == "W"){
$gpsarray[3]->setText("E");
echo "converted west to east.<br>";
}
else if(($gpsarray[3]->getText()) == "E"){
$gpsarray[3]->setValue("W");
echo "converted east to west.<br>";
}
}
}
}
}
}
}
}
?>

PHPのexif_read_data関数では、引数にまず欲しいデータより上位のまとまりを指定して、ごっそりまとまりを取得した後には、foreachや添字でアクセスできる。

exif_read_data(ファイル名,
セクション名(省略化、複数の場合カンマ区切り),
配列で取得するかフラグ(省略可、デフォルトfalse,
サムネイル取るかフラグ(省略化、デフォルトfalse)

とりあえず今回のヴァージョンでは全部表に吐き出してしまっている。

<?php
if(!empty($_POST["path"])){
if($exif = exif_read_data($_POST["path"],NULL,true)){
echo "<table class='exiftable table table-striped'><thead><tr><th>キー名</th><th>値</th></tr></thead><tbody>";
foreach ($exif as $key => $section) {
    foreach ($section as $name => $val) {
	if(is_array($val)){
	foreach ($val as $valname => $vval) {
	echo "<tr><td>$key $name $valname</td><td>$vval</td></tr>";
	}
	} else {
	echo "<tr><td>$key $name</td><td>$val</td></tr>";
	}
    }
}
echo "</tbody></table>";
} else {
echo "画像のEXIFデータが見つかりませんでした。";
}
} else {
echo "画像をセットして下さい。";
}
?>

php.netの説明によると、exif_read_data関数の第一引数にはURL形式は与えられないとのこと。けれども試してみたら、普通に外部サイトの画像URLを与えても表示できた。そこで、$_POST[“path”]にはDataURLを与えることにする。画像のセットで起動して、jQueryでフィールドにエンコードしたURLを入れておくだけ。

外部サイトの画像URLが参照できるというのは面白いので、サービスとして機能を増やせる余地がありそうだ。浮気のアリバイ調査に使えますね。


var_dumpよりログ作成に便利なvar_export

PHPで変数をダンプしたいとき

PHPでプログラムを書いていて、処理の一時点での変数の値を参照したいとき。通常であれば、var_dumpやprint_rなどの関数を使います。

$ar = array(
array(1,"愛媛県",array("県庁所在地" => "松山","名物" => "みかん")),
array(2,"香川県",array("県庁所在地" => "高松","名物" => "うどん"))
);
 
//var_dumpの場合
var_dump($ar);
//標準出力への出力結果
array(2) { [0]=> array(3) { [0]=> int(1) [1]=> string(9) "愛媛県" [2]=> array(2) { ["県庁所在地"]=> string(6) "松山" ["名物"]=> string(9) "みかん" } } [1]=> array(3) { [0]=> int(2) [1]=> string(9) "香川県" [2]=> array(2) { ["県庁所在地"]=> string(6) "高松" ["名物"]=> string(9) "うどん" } } }
 
//print_rの場合
print_r($ar);
//標準出力への出力結果
Array ( [0] => Array ( [0] => 1 [1] => 愛媛県 [2] => Array ( [県庁所在地] => 松山 [名物] => みかん ) ) [1] => Array ( [0] => 2 [1] => 香川県 [2] => Array ( [県庁所在地] => 高松 [名物] => うどん ) ) )

var_dumpとprint_rの違いについては、print_rの方が変数を見やすく出力してくれるという説明がされています。変数型のような余計なものがついていませんが、値の確認程度ならprint_rでも間に合いますね。

変数のダンプ結果を文字列として扱いたいとき

通常のダンプだけでなく、たとえばある時点での変数の値をデータベースに格納したい場合、特定のメールアドレスに送信してログを残したい場合、などは、一度標準出力をob_startとob_get_contentsなどを使ってバッファリングした上でvar_dumpなど行う必要があるように思えます。
それらを一緒くたに、変数を展開して即文字列型にしてしまうのがvar_export関数です。

var_export関数は引数を二つとり、第一引数が文字列に展開する変数、第二引数がブーリアン型で、falseならばそのまま標準出力に出力、trueならば出力はせず、返り値として文字列を返します(デフォルトはfalse)。

$ar = array(
array(1,"愛媛県",array("県庁所在地" => "松山","名物" => "みかん")),
array(2,"香川県",array("県庁所在地" => "高松","名物" => "うどん"))
);
 
//var_export
var_export($ar);
//標準出力への出力結果
array ( 0 => array ( 0 => 1, 1 => '愛媛県', 2 => array ( '県庁所在地' => '松山', '名物' => 'みかん', ), ), 1 => array ( 0 => 2, 1 => '香川県', 2 => array ( '県庁所在地' => '高松', '名物' => 'うどん', ), ), )
 
//var_exportの第二引数にtrueを指定して変数に格納
$return = var_export($ar,true);

var_exportの出力は、print_rと同じように変数型については省略のようですね。また、配列の最後には、次に続く要素が無くてもカンマをつける癖があるみたいです。細部に注意は必要ですが、これでログの作成が容易になります。