‘php’ カテゴリーのアーカイブ

正規表現メモ

2008年10月30日

パターン中のメタ文字を纏めてエスケープする

PHP4.3.3以降では、\Q~\Eで囲むとメタ文字を纏めてエスケープできます。

PHP

  1. preg_match('/^\Q(+*%&)\E$/''(+*%&)');

部分的にオプションを変更する

(?オプション文字)を使うとオプションの設定と解除が可能です。

PHP

  1. // 設定
  2. preg_match('/abc((?i)def)ghi/''abcDEFghi', $matches);
  3. print_r($matches);
  4. // Array ( [0] => abcDEFghi [1] => DEF )
  5. // 解除
  6. preg_match('/abc((?-i)def)ghi/i''ABCdefGHI', $matches);
  7. print_r($matches);
  8. // Array ( [0] => ABCdefGHI [1] => def )

部分的にキャプチャを避ける

(?:)を使うとサブパターンがキャプチャされなくなります。

PHP

  1. preg_match('/(?:say) (hello)/''say hello', $matches);
  2. print_r($matches);
  3. // Array ( [0] => say hello [1] => hello )

キャプチャにキーを指定する

(?P<キー名>パターン)でキーを指定できます。

PHP

  1. preg_match('/say (?P<key>hello)/''say hello', $matches);
  2. print_r($matches);
  3. // Array ( [0] => say hello [key] => hello [1] => hello )

先読み言明

ある文字の直後を限定します。
言明パターンはキャプチャされません。

PHP

  1. // 肯定: 直後がbarのfoo
  2. preg_match('/foo(?=bar)/''foobar', $matches);
  3. print_r($matches);
  4. // Array ( [0] => foo )
  5. // 否定: 直後がvaa以外のfoo
  6. preg_match('/foo(?!vaa)/''foobar', $matches);
  7. print_r($matches);
  8. // Array ( [0] => foo )

戻り読み言明

ある文字の直前を限定します。
言明パターンはキャプチャされません。

PHP

  1. // 肯定: 直前がfooのbar
  2. preg_match('/(?<=foo)bar/''foobar', $matches);
  3. print_r($matches);
  4. // Array ( [0] => bar )
  5. // 否定: 直前がhoo以外のbar
  6. preg_match('/(?<!hoo)bar/''foobar', $matches);
  7. print_r($matches);
  8. // Array ( [0] => bar )

繰り返し指定の再試行をキャンセル

サブパターン中に*、+、{}によって繰り返しが指定されている場合、その続きのマッチングに失敗すると、繰り返し部分の桁が調整され、再度マッチングが行われます。この繰り返しが無駄だと分かっている場合には、再試行無しのサブパターン(?>)を使ってキャンセルできます。
このパターンもキャプチャされません。

PHP

  1. // 再試行あり
  2. preg_match('/(\d+)bar/''0123456789foo');
  3. // 再試行なし
  4. preg_match('/(?>\d+)bar/''0123456789foo');

パターン中の後方参照

パターン内部で前の部分でマッチした文字を参照したい場合は、\1の形で参照できます。$1は使えません。

mオプション

複数行を単一行とみなせるmオプションは行を改行コードLFで判断しているので、Windowsでは末尾を表す$マークの扱いに注意する。最後の文字は改行手前の文字ではなくCRとなる。

参考文献

PHPパターン構文


携帯端末番号(個体識別番号)判別

2008年10月30日

携帯の端末番号の取得方法を紹介します。
こういった携帯にまつわる問題は各社仕様がバラバラなので簡単に済ませる方法です。

方法

DoCoMoとSoftBankはユーザーエージェントに番号が含まれますので、これを解析すればすぐにわかります。AUはユーザーエージェントには含まれず$_SERVER['HTTP_X_UP_SUBNO'];に格納されています。この特殊な変数があれば、ほぼ端末番号であると分かる訳ですが、ログとして保存したい場合には個別に保存項目が必要になりやっかいです。そこで前の番号部分を抜いてユーザーエージェントに連結させます。これで大手三社の番号をユーザーエージェントだけで確認できます。

DoCoMo

// MOVA
DoCoMo/1.0/X503i/c10/ser12345678901
serの後に11桁英数字

// FOMA
DoCoMo/2.0 N2001(c10;ser12345678901;
icc12345678901234567890)
serの後に15桁英数字
iccの後に20桁英数字
改行が含まれます。

PHP

  1. // ser以降で判別
  2. $agent = $_SERVER['HTTP_USER_AGENT'];
  3. $has_subno = preg_match('/ser[0-9A-Za-z]{11,15}/', $agent);

SoftBank

// Vodafone
Vodafone/1.0/V802SH/SHJ002/SN123456789012345 Browser/UP.Browser/7.0.2.1 Profile/
MIDP-2.0 Configuration/CLDC-1.1 Ext-J-Pr

// SoftBank
SoftBank/1.0/705P/PJP10/SN123456789012345 Browser/Teleca-Browser/3.1 Profile/
MIDP-2.0 Configuration/CLDC-1.1

いずれも/SNの後に15桁英数字

PHP

  1. // /SN以降で判別
  2. $agent = $_SERVER['HTTP_USER_AGENT'];
  3. $has_subno = preg_match('|/SN[0-9A-Za-z]{15}|', $agent);

AU

AUの場合だけ先にユーザーエージェントに番号を付けます。

PHP

  1. // 05051234567890_ae.ezweb.ne.jp
  2. // 4桁地域コードと10桁端末コード
  3. $subno = $_SERVER['HTTP_X_UP_SUBNO'];
  4. // KDDI-CA23 UP.Browser/5.1 (GUI) MMP/2.0/SUB05051234567890_ae
  5. $_SERVER['HTTP_USER_AGENT'] .= '/SUB' . substr($subno, 0, -12);

PHP

  1. // 連結した末尾で判別
  2. $agent = $_SERVER['HTTP_USER_AGENT'];
  3. $has_subno = preg_match('|/SUB0\d0\d\d{10}_[a-z\d]{2}$|', $agent);

まとめ

これで端末番号を送信しているかどうかユーザーエージェントだけで判断できるし、特定の端末番号を拒否する事もできます。今回は省略しましたが、以前の携帯判別のコードと合わせれば便利に書けるかもしれません。
ユーザーエージェント携帯判別

注意

これらの環境変数($_SERVER)は信用してはいけません。保存したり、ページへ出力させる場合にはどんな文字が入っていても問題ない様にしましょう。


DoCoMo絵文字データ保存方法

2008年10月30日

携帯から入力された絵文字の判定を簡単に行う方法があるようです。mb_substitute_character();を使って存在しない文字コードを炙り出します。

携帯端末からの投稿を保存

PHP

  1. // 出力文字エンコーディングに文字コードが存在しない場合の代替文字を指定
  2. mb_substitute_character('long');
  3. // 太陽絵文字入り文字列
  4. $str = '今日も暑いですね・';
  5. // 強制的に絵文字部分がBAD+16進コードに変換される
  6. $str = mb_convert_encoding($str, mb_internal_encoding());
  7. // 絵文字を[d:絵文字コード]に変換して保存
  8. $str = preg_replace('/BAD\+(F[89][0-F]{2})/''[d:$1]', $str);

こうしておけば[d:~]を探して変換すれば、PCには絵文字画像、携帯には直接絵文字を表示させる事ができる。記事の修正が必要になっても絵文字を意識しなくて済みます。

保存ログを携帯端末へ出力

PHP

  1. $str = '今日も暑いですね[d:F8A0]';
  2. $str = preg_replace('/\[d:(F[89][0-F]{2})\]/e'"pack('H*', $1)", $str);
  3. print $str;

まとめ

BAD+からF3、F4、F6、F7で始まる文字列を調査すればAU絵文字も判別可能らしい。めちゃくちゃ楽だな、これ。

参考文献

絵文字の抽出をPHPで最も簡単にする方法 ぎじゅっやさん


携帯特有の注意点

2008年10月30日

ホスト名

携帯はホスト名が頻繁に変わってしまいます。
またホスト名の後半でキャリア判別すると失敗する場合があり、信用できません。正確に携帯判別するには各社サイトに掲載されているIPリストと照合する必要があります。

Content-Lengthを付ける

DoCoMoはContent-Lengthがないと表示できない場合があります。
「接続先のサーバーは現在ご利用できません」

リダイレクトは避ける

正しく処理されなかったり、エラーになる機種があります。
「無効なデータを受信しました」

POST後の二重投稿防止ボタン

HTML

  1. <form action="./index.php" method="post">
  2.   <p>投稿完了しました</p>
  3.   <p><input type="submit" value="戻る" /></p>
  4. </form>

POST後にこれを踏むと二重投稿を防ぐ事ができます。
GETでは駄目。

<input>フィールド内で改行を入力

PCブラウザでは改行できなくても携帯ではできてしまいます。

<textarea>フィールド内の改行コード

<textarea>フィールドに文章を出力する場合は改行コードをCRLFにする。
LFでは改行されません。

istyle属性

istyle=”1″ 全角かな
istyle=”2″ 半角カナ
istyle=”3″ 半角英字
istyle=”4″ 半角数字

パスワード入力欄<input type="password">はDoCoMoはistyleが無視されて半角数字モードになりますが、AUでは付けていないと全角モードになるのでここにもistyleを付けておく。最近のSoftBank携帯はistyleに対応しているようです。

まとめ

携帯の機能はよりPCに近づいています。
この記事も、すぐに当てにならなくなるかもしれませんので、参考程度に。


処理速度最適化法

2008年10月30日

いろいろ纏めようと思っていたんですが、すでに分かりやすいサイトが沢山あります。
PHP最適化tipsまとめサイト
基礎構文処理速度のあれこれ

require VS require_once

実際に計測してみた所、試行回数が少なかったせいか、あまり差は見られませんでした。
(未読ファイルの読み込みに関して)
一度しか読まないのが分かっていればrequire_onceを使うメリットはないと思います。
速度は気にせず以下で使い分ければOK。

複数回読まれる可能性があるが、一度しか読む必要がない。require_once
それ以外。require

== VS ===

型を含めて比較する後者の方が若干速い。
パスワードなど外部データと比較する場合にはセキュリティーホールとなる可能性があるので===を常用した方が良い。

is_type() VS ===

PHP

  1. if (is_null($var)) {}
  2. if ($var === null) {}

後者が速い。

count($array) > 0 VS (bool)$array

配列に要素があるかどうかの判別。
後者が速い。

array_merge() VS +演算子

array_mergeは連結後に数字キーを振り直すので、連想配列の場合は+演算子を使った方が速い。

PHP

  1. $a = array('a' => 1, 'b' => 2);
  2. $b = array('a' => 3, 'c' => 4);
  3. // array_merge($a, $b) == $b + $a;

バージョン分岐

PHPのメジャーバージョンで分岐したい場合はversion_compare()を使うより各バージョン特有の定数を調べるのが良い。

PHP

  1. // PHP_VERSIONの一文字目を調べるより速い
  2. $isPHP5 = defined('E_STRICT');

is_file() VS file_exists()

file_exists()はディレクトリも含まれるので、ファイルの有無を調べたいだけならis_file()の方が高速。

文字列連結速度

PHP

  1. $str = $str1.$str2;
  2. $str = "$str1$str2";
  3. $str = "{$str1}{$str2}";

上から順に高速。

splitは使うな

文字列を分割して配列を作成する場合、splitは遅いので避ける。
正規表現分割は、 preg_split (PHP 3.0.9, 4, 5)
固定文字分割は、 explode (PHP 3, 4, 5)
分割して一部分だけ取得したい場合は、分割数を制限する3番目のlimit引数を付けておきます。 特にループ内で繰り返し使う場合、全分割するより時間を短縮できます。

PHP

  1. $str = 'id,date,name,msg,pwd';
  2. // limit引数:必要なデータ数 + 1
  3. $arr = explode(',', $str, 3);
  4. print_r($arr);
  5. // 出力結果 Array ([0] => 'id' [1] => 'date' [2] => 'name,msg,pwd')

time()を何度も呼ばない

何度も使う場合は定数にするなりして使い回す。(2回程度なら定数化しない方が速いかもしれないが。)PHP5では$_SERVER['REQUEST_TIME'];でも可能。

PHP

  1. define('REQUEST_TIME', time());

in_array()よりisset()

調べたい要素が複数ある、又は複数回調べたい、それが空文字以外の文字や整数(つまりキーになりうる)で重複していない(整数1と文字’1′は重複)事が確かならarray_flip()で反転させてキーで調べると速い。

PHP

  1. // 0と5はある?
  2. $list = array(5, 4, 3, 1, 2, 0);
  3. $list = array_filp($list);
  4. if (isset($list[0], $list[5])) {
  5.   print 'あった!';
  6. }

array_key_exists()よりisset()

配列の要素がnullの場合両者の挙動が異なります。
それでも影響がない場面ではisset()を使った方が速い。

大文字小文字変換

大文字変換 strtoupper();
小文字変換 strtolower();
速度は変換数に比例して遅くなる。

PHP

  1. $s = 'aaaaaAaaaaAaaaaaa';
  2. // 大文字が少ない分strtolower($s);よりstrtoupper($s);の方が時間がかかる。

まとめ

関数を使わずに済むならその方が高速。
正規表現を使わずに済むならその方が高速。
その他、同様の役割でも何かしらの判断基準が設けられている場合は、そちらの方が遅くなる傾向にあります。
また、計算式では誤差が生じたり可読性が落ちるのでキャストは使わずにintvalなどの変換関数を用いる。状況に応じて速度と手間と可読性のどれを重視するか判断しましょう。


コメントスパム対策法

2008年10月30日

近年スパム被害が増えています。
誰でも投稿できるブログや掲示板も狙われています。
ここでは手間のかからない、簡単な対処法をいくつか紹介します。

  • 主な対処法
    • 禁止ワードを設定
    • 日本語が使えない環境からの投稿を拒否
    • 半角英数字のみの投稿を拒否
    • 投稿できるURLの数を制限
    • プロキシサーバ経由の投稿を拒否
    • リファラーを参照してフォームを経由したか判断
    • フォームを表示してから投稿するまでの許容時間を設定する
    • ダミー項目を作り入力があった場合に拒否
    • ワンタイムトークンを埋め込む
    • フォームを解析されない様に、ロボットを排除
    • データ送信先を頻繁に変更
    • Captcha画像認証

検索ロボットを排除

HTML

  1. <meta name="robots" content="none,noindex,nofollow,noarchive" />
  2. <meta name="googlebot" content="noindex,nofollow,noarchive,nosnippet" />
  3. <meta name="libwww-perl" content="none,noindex,nofollow" />

ロボットを完全に排除するのは難しい様ですが、少しは効果があると思います。googlebotのsnippetは検索結果に表示される該当ページからの抜粋です。これがある方がクリック率が高まるそうです。これを削除(nosnippet)するとキャッシュも削除されます。

Googleページ削除

リファラーからフォームを経由したか調べる

PHP

  1. $from = $_SERVER['HTTP_REFERER'];
  2. $host = $_SERVER['HTTP_HOST'];
  3. if (!stristr($from, $host)) {
  4.   print '不正なリファラーです。';
  5. }

リファラーを持たない環境は結構あります。ノートン等のセキュリティソフトによって隠蔽されている可能性もあります。ドコモなど一部携帯でアウト。

逆引き不可能なIPアドレスを拒否

PHP

  1. $ip = $_SERVER['REMOTE_ADDR'];
  2. $host = getHostByAddr($ip);
  3. if ($ip == $host) {
  4.   print '逆引き不可能なIPアドレスです。';
  5. }
  6. // これでも可能
  7. if (!preg_match('/[a-z]/', $host)) {
  8.   print '逆引き不可能なIPアドレスです。';
  9. }

プロキシ経由を拒否

プロキシ特有の環境変数とホスト名を調べる方法です。
環境変数を吐かない匿名プロキシもあって、すべてを検出できる訳ではありません。
他にはプロキシリストを作成したり、ポートスキャンする方法もあります。

PHP

  1. $env = $_SERVER + $_ENV;
  2. $ip = $systems['REMOTE_ADDR'];
  3. $host = getHostByAddr($ip);
  4. // ドコモの携帯はproxyが存在するので注意
  5. // anonym=anonymous,anonymizer
  6. // prox=proxy,proxify
  7. $proxhost = preg_match(
  8.   '/anonym|cache|delegate|firewall|gateway|httpd|keeper|prox|squid|via|www/', $host);
  9. $bool_arr = array(
  10.   isset($env['HTTP_CLIENT_IP']),
  11.   isset($env['HTTP_FORWARDED']),
  12.   isset($env['HTTP_MAX_FORWARS']),
  13.   isset($env['HTTP_PROXY_CONNECTION']),
  14.   isset($env['HTTP_SP_HOST']),
  15.   isset($env['HTTP_TE']),
  16.   isset($env['HTTP_VIA']),
  17.   isset($env['HTTP_X_FORWARDED_FOR']),
  18.   isset($env['HTTP_X_LOCKING']),
  19.   isset($env['HTTP_XONNECTION']),
  20.   isset($env['HTTP_XROXY_CONNECTION'])
  21. );
  22. $proxvar = in_array(TRUE, $bool_arr);
  23. if($proxhost || $proxvar) {
  24.   print 'プロキシ経由です。';
  25. }

ネットワーク総合辞書 - プロクシとは
サイバーシンドローム

その他の変数

PHP

  1. // 使用言語(ja以外のen-US等は不可)
  2. $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  3. if (!stristr($lang, 'ja')) {
  4.   print '海外からのアクセスです。';
  5. }

PHP

  1. // 接続状況(none,close等は不可)
  2. $conn = $_SERVER['HTTP_CONNECTION'];
  3. if (!stristr($conn, 'Keep-Alive')) {
  4.   print '接続状況が不明です。';
  5. }

これらの方法は携帯では使えない場合が多い。

URL数を制限

PHP

  1. // URLを3つまでに制限する
  2. if (substr_count($message, 'http://') > 3) {
  3.   print 'URLは3つ以下に制限されています。';
  4. }

URLを載せる目的のスパムが多いので効果的。

ひらがなカタカナを含まない投稿を拒否

PHP

  1. if (!mb_ereg('[ぁ-んァ-ン]', $message)) {
  2.   print 'カナが含まれていません。';
  3. }

効果的ですが、鎖国的で残念な方法。

禁止ワードの入力を拒否

PHP

  1. // 投稿されたくないキーワードをカンマ区切りで設定
  2. $spamword = 'アホ,バカ';
  3. // 正規表現に変換
  4. $spamword = preg_quote($spamword);
  5. $spamword = str_replace(',''|', $spamword);
  6. if (mb_eregi($spamword, $message, $matches)) {
  7.   print $matches[0].'が含まれています。';
  8. }

(preg_quoteはShift_JISでは無効)

宣伝書き込み調査中

日本語のスパムが目立ち始めたので、NGワード集を作成してみました。

まとめ

スパムは、フォーム情報をスキャンして一方的に大量に送りつけられます。以前は英語のスパムが殆どで対処も楽でしたが、最近は日本語のスパムも目立つ様になってきました。フォームを探すだけでなく、手動で登録したり、弾かれても内容を変えてリトライさせるスパム生成アプリまで販売されています。

有名なスクリプトや一般的な対処法はすぐに狙われてしまいます。効果的で簡単な対処方法はなかなかありませんが、ちょっとした「ひねり」を加える事で被害を抑える事ができます。例えばフォームを表示してから投稿するまでの時間を計測して短すぎたら拒否するのも賢い手法といえます。また、セッションを活用するのも効果的です。今後良い方法を入手できたら紹介したいと思います。


Amazon Web Service

2008年10月30日

Amazon Web Service(AWS)を使うと、Amazonで検索した商品データを自分のサイトに載せる事ができます。商品データはXML形式で返されますので、これをPHPで解析してHTMLに成型します。

商品検索方法

http://xml-jp.amznxslt.com/onca/xml3?local=jp
このアドレスに検索用のパラメータを連結させて問い合わせします。

オプションパラメータ
f= XSLTを適用する場合はそのURL
f=xmlとするとそのままXMLを取得できます。
locale= 国コード 日本はjp
type= lite 簡易版
heavy 完全版
t= アソシエイトID
dev-t= Subscription Id (デベロッパトークン)
page= 結果が複数ページに渡る場合のページ数
mode= 検索する商品のジャンル
和書 books-jp
洋書 books-us
音楽 music-jp
音楽クラシック classical-jp
DVD dvd-jp
ビデオ vhs-jp
エレクトロニクス electronics-jp
ソフトウェア software-jp
ゲーム videogames-jp
ASINSearch= 商品固有のASIN(Amazon Standard Item Number)コード、もしくは書籍に付いているISBNコードを指定。 カンマ(,)で区切って複数指定可能
KeywordSearch= 検索単語

これ以外にも詳細な検索が行えるパラメータがいくつかあります。

XMLを解析する

アマゾンから返ってきたXMLは、PHP標準のDOM関数を使って自力で解析しても良いのですが、構造が複雑になると少々大変です。そこで、PEARのXML_Serializerパッケージに含まれているXML_Unserializerクラスを使います。これを使えば、XMLテキストを瞬時に連想配列にしてくれますので、後で好きな様にHTMLに加工できます。

(注) PEARがない場合、まずPEARを導入して下さい。
http://pear.php.net/package/XML_Serializer

PHP

  1. // XML_Unserializerクラスの読み込み
  2. require 'XML/Unserializer.php';
  3. class AWS_QueryString {
  4.   var $_params;
  5.   function AWS_QueryString($token, $assoc, $type = 'heavy', $format = 'xml',
  6. $locale = 'jp') {
  7.     $this->_params = array();
  8.     // 基本データだけ入れておく
  9.     $this->add('dev-t', $token);
  10.     $this->add('t', $assoc);
  11.     $this->add('locale', $locale);
  12.     $this->add('f', $format);
  13.     $this->add('type', $type);
  14.   }
  15.   function add($name, $value) {
  16.     $this->_params[$name] = $value;
  17.   }
  18.   function get($key) {
  19.     if(array_key_exists($key, $this->_params)) {
  20.       return $this->_params[$key];
  21.     }
  22.     return NULL;
  23.   }
  24.   function toString() {
  25.     if(count($this->_params) > 0) {
  26.       $tmp = array();
  27.       $str = '';
  28.       foreach($this->_params as $key => $value) {
  29.         if($value) $tmp[] = "$key=$value";
  30.       }
  31.     }
  32.     $str = implode('&', $tmp);
  33.     $str = "http://xml-jp.amznxslt.com/onca/xml3?$str";
  34.     return $str;
  35.   }
  36. }
  37. class AWS_ProductData {
  38.   var $_products;
  39.   function AWS_ProductData($xmlsrc, $encoding = 'SJIS') {
  40.     $lines = file($xmlsrc);
  41.     $line = implode('', $lines);
  42.     $line = mb_convert_encoding($line, $encoding, 'UTF-8');
  43.     
  44.     $unserializer =& new XML_Unserializer();
  45.     // 解析に属性も含める
  46.     $unserializer->setOption('parseAttributes', TRUE);
  47.     $success = $unserializer->unserialize($line);
  48.     if(is_object($success)) die( $success->getMessage() );
  49.     $this->_products = $unserializer->getUnserializedData();
  50.   }
  51.   function getProducts() {
  52.     return $this->_products;
  53.   }
  54. }
  55. // アマゾン・リクエストID(デベロッパトークン)
  56. define('AWS_TOKEN''xxxxxxxx');
  57. // アマゾン・アソシエイトID
  58. define('AWS_ASSOC''xxxxx-22');
  59. $qs =& new AWS_QueryString(AWS_TOKEN, AWS_ASSOC);
  60. $qs->add('mode''music-jp');
  61. $qs->add('KeywordSearch''宇多田ヒカル');
  62. $url = $qs->toString();
  63. $obj =& new AWS_ProductData($url);
  64. $products = $obj->getProducts();
  65. print '<pre>';
  66. print_r($products);
  67. print '</pre>';

実行結果

商品リンク

http://www.amazon.co.jp/exec/obidos/ASIN/xxxxxxxxxx/xxxxx-22/
http://www.amazon.co.jp/exec/obidos/ASIN/xxxxxxxxxx/xxxxx-22/ref=nosim/

通常、商品が売れた場合の紹介料は3%ですが、下のようにref=nosimを付けたアドレスにリンクしておくと、そのページで購入された場合に限り5%になりますので、一応付けておきましょう。

参考資料


テンプレートエンジンについて

2008年10月30日

PHPにテンプレートエンジンは必要か?

この話題はPHP users-mlでも取り上げられました。

PHPはSSIの様にHTML内に埋め込んで使う事ができます。では、テンプレートエンジンが使われているのは何故でしょうか。いずれにせよ、他人のスクリプトを使う場合、デザイン部分だけ独立している方が使いやすいですね。

Smarty
HTML_Template_IT

テンプレートエンジンの魅力

それは、ロジックとデザインの完全な分離です。PHPを知らなくても容易にデザインを変更できるようになります。高機能なテンプレートエンジンでは、表組みや日付のフォーマット変更、文章の要約処理、文字コードの変更まで行う事ができます。

  • 以下に該当する場合、テンプレートエンジンは特に威力を発揮します。
    • デザイン担当者がスクリプトを全く知らない場合。
    • ロジックよりデザインを先行して作成する場合。
    • 複雑なデザインの場合。

PHPタグを上手に使おう

テンプレートエンジンは必要ないと判断した場合でも、作り方次第で上手くデザインを分離させられます。まず、PHPを含むデザインファイルは.phpとせず.htmとしておき、PHPでincludeして使うようにします。これでブラウザでデザインを確認しやすくなります。そして、どこでもPHPが書けてしまう事に甘えずに、スクリプトのみのファイルで極力データを加工しておきます。

※.htmとして良いのは画面への出力部分を極力抽出した場合に限ります。と言うのは、直接アクセスされた場合に中身が丸見えになってしまうからです。関数定義を含めるのも避けましょう。

結果データは <h1><?echo $arr["title"] ?></h1> の様にHTMLに埋め込みます。ところが直接ブラウザで見るとデータが消えてしまっているのが分かります。これはテンプレートエンジンを使わない場合は仕方がありません。PHPタグ<??>はブラウザには表示されません。これが気に入らない場合はテンプレートエンジンを導入しましょう。

デザインを崩してしまう書き方

属性内のクォーテーション
<a href="<?echo $arr["url"] ?>">url</a> → ">url
これは「'」を使えば回避できます。
<a href="<?echo $arr['url'] ?>">url</a> → url
比較やオブジェクトで使う ">"
<? if($n>=10){ ?> → =10){ ?>
<?echo $obj->value ?> → value ?>

これらは最近のWEB作成ソフトでは上手く解釈してくれますが、ブラウザでも確認できた方がより便利です。そこで、属性内のクォーテーションと">"は使わないようにします。テンプレート内で使う変数は予め連想配列にしておき、ループが必要な場合はそれを配列に溜めます。ループにはforeachを活用します。連想配列はクォーテーションを使うので、extract($hash);でキー文字列の変数に変換します。

PHP

  1. <?php foreach ($arr_list as $arr) {extract($arr); ?>
  2. <h2><?php echo $title; ?></h2>
  3. <p>written by <?php echo $name; ?> on <?php echo date('m/d H:i', $timestamp); ?></p>
  4. <p><?php echo $message; ?></p>
  5. <p><a href="./bbs.php?reply=<?php echo $id; ?>">返信する</a></p>
  6. <hr />
  7. <?php } ?>

こうして少し気を使うだけで、きれいなデザインを保持できます。
自分だけで使う場合は、これで十分かもしれません。

追記

とは言ってもクラスを多用する場合には必然的にオブジェクトの->を使わざるを得ません。連想配列しか使えないのは不便です。そんな場合はテンプレート内専用の変換関数をクラスに属させずに集めておくと便利になり、見通しも効きます。例えばオブジェクトの変数を取り出すだけの関数や表組み生成などの1行では書けない様な処理も該当します。ここで使っているWordPressもテンプレート用の関数を充実させて使いやすい様に設計されています。


クラスとメソッドの動的使用

2008年10月30日

PHPでは大胆な事ができます。

インスタンスメソッドを呼ぶ

PHP

  1. class ClassA {
  2.   function method() {
  3.     print 'method';
  4.   }
  5. }
  6. // 大文字小文字は区別しない
  7. $className = 'classa';
  8. $methodName = 'method';
  9. $obj = new $className();// new ClassA();
  10. $obj->$methodName();// $obj->method();

staticメソッドを呼ぶ

インスタンスメソッドと若干異なります。

PHP

  1. class ClassA {
  2.   function method() {
  3.     print 'method';
  4.   }
  5. }
  6. // 大文字小文字は区別しない
  7. $className = 'classa';
  8. $methodName = 'method';
  9. $className::$methodName();// parse error
  10. eval("$className::$methodName();");// evalを使えばOK
  11. call_user_func(array($className, $methodName));// こちらがベスト

まとめ

いずれも非常に便利です。
クラス名や関数名を文字列で登録しておくだけで実行できます。
GDのバージョンによって、画像処理関数を切り替えたい時にも使えます。
又、登録モード、削除モード、検索モードなど、モード毎に1つの関数を呼ぶ便利な使い方もできます。わざわざ、if (登録モードなら) なんて分岐せずに済みますね。


メソッドのオーバーロード

2008年10月30日

PHP4ではメソッドのオーバーライドは出来るが、オーバーロードは出来ない。

PHP

  1. class Parent {
  2.   function ride() {
  3.     print 'p_ride';
  4.   }
  5.   function load() {
  6.     print 'p_load';
  7.   }
  8. }
  9. class Child extends Parent {
  10.   function ride() {
  11.     print 'c_ride';
  12.   }
  13.   function load($i) {
  14.     print 'c_load';
  15.   }
  16. }
  17. $c = new Child();
  18. $c->ride(); // c_ride
  19. $c->load(1);// c_load
  20. $c->load(); // 引数ないぞエラー

PHPでは同名メソッドの複数定義ができません。その代わり、引数のデフォルト値を設定できるので、一回の定義でオーバーロードのように振舞う事ができます。
function load($i = 1) {}

複数の引数にデフォルト値が設定されている場合は、最後から順に省略できます。

PHP

  1. function example($arg1, $arg2 = 1, $arg3 = 2) {
  2. }
  3. example(2, 4, 6);
  4. example(2, 4);// $arg3を省略
  5. example(2);// $arg2と$arg3を省略