Home > アーカイブ > 2007-06

2007-06

CakePHP テーブルのカラムサイズでvalidate

この記事の所要時間: 241

モデルで文字列の長さをvalidateする際にテーブルのカラムサイズを使う方法です。

CakePHPではモデルがテーブルのカラム情報を保持しているのですが、この情報にはカラム名やデータ型の他にカラムサイズ(varchar(N)等)も含まれています。

そこでモデルのvalidateでこの値を使えば最低限テーブルのカラムに収まるかどうかはチェックできます。

ここでは以下のようなテーブルでnameとemailの文字列長をチェックします。(DBはPostgreSQL)

SQL:
  1. CREATE TABLE users (
  2. id        serial            PRIMARY KEY
  3. ,name   varchar(50)   NOT NULL
  4. ,email   varchar(100)   NOT NULL
  5. );

1. AppModelにカラムサイズ取得、文字列長チェックメソッドを追加する

AppModelにカラムサイズ取得用メソッド(getColumnLength)と文字列長チェックメソッド(validatesLength)を記述します。

PHP:
  1. <?php
  2. class AppModel extends Model {
  3.   /**
  4.    * Returns the column type of a column length in the model
  5.    *
  6.    * @param string $column The name of the model column
  7.    * @return integer
  8.    * @access public
  9.    */
  10.   function getColumnLength($column) {
  11.     $columns = $this->loadInfo();
  12.     $columns = $columns->value;
  13.     $cols = array();
  14.  
  15.     foreach($columns as $col) {
  16.       if ($col['name'] == $column) {
  17.         return $col['length'];
  18.       }
  19.     }
  20.     return null;
  21.   }
  22.  
  23.   /**
  24.    * valid value length
  25.    *
  26.    * @param array $data
  27.    * @param string $fields
  28.    * @access public
  29.    */
  30.   function validatesLength($data, $fields) {
  31.     foreach ($fields as $v) {
  32.       $len = $this->getColumnLength($v);
  33.       if (!empty($data[$v]) && mb_strlen($data[$v])> $len) {
  34.         $this->invalidate($v . '_len_' . $len);
  35.       }
  36.     }
  37.   }
  38. }
  39. ?>

2. モデルで文字列長をチェックする

モデルで文字列長チェックを呼びます。単に$dataとvalidatesLengthメソッドにチェックするカラムを配列で渡すだけでokです。

validatesLength内部ではテーブルのカラムサイズで文字列長チェックを行い、エラーならinvalidateメソッドを呼びます。このinvalidateメソッドに与える値[カラム名 + '_len_' + カラムサイズ]がポイントです。これをモデルの$validationErrorsに持たせておくことにより、ビューで正規の文字列長を含んだエラーメッセージを動的に出力することができます。

PHP:
  1. <?php
  2.   function validates($data = array()) {
  3.     if (!parent::validates($data)) {
  4.       return false;
  5.     }
  6.     if (empty($data)) {
  7.       $data = $this->data;
  8.     }
  9.     $data = $data['User'];
  10.  
  11.     // 文字列長チェック
  12.     $list = array('name', 'email');
  13.     $this->validatesLength($data, $list);
  14.   }
  15. ?>

3. HtmlExtヘルパでエラーメッセージを出力

文字列長エラーメッセージを動的に出力するメソッド(tagLengthErrorMsg)をHtmlヘルパーを継承したHtmlExtに定義しておきます。

PHP:
  1. <?php
  2. class HtmlExtHelper extends HtmlHelper {
  3.   /**                       
  4.    * lengthエラーメッセージ表示
  5.    *                   
  6.    * @param string $field
  7.    * @param string $name
  8.    * @return string
  9.    */                         
  10.   function tagLengthErrorMsg($field, $name) {
  11.     if (empty($this->validationErrors)) {
  12.       return null;
  13.     }
  14.                              
  15.     $this->setFormTag($field);
  16.     foreach ($this->validationErrors[$this->model] as $k => $v) {
  17.       if (preg_match("/^" . $this->field . "_len_([0-9]+)$/", $k, $m)) {
  18.         $message = sprintf("%sは%d文字以内で入力してください。", $name, $m[1]);
  19.         return $this->tagErrorMsg($field . "_len_" . $m[1], $message);
  20.       }
  21.     }
  22.     return null;
  23.   }
  24. }
  25. ?>

4. ビューテンプレートでヘルパ呼び出し

最後にビューテンプレートで先程のtagLengthErrorMsgメソッドを呼び出します。これは引数としてカラム名とエラーメッセージ用項目名を与えます。

PHP:
  1. <!-- エラー時は「氏名は50文字以内で入力して下さい。」を表示 -->
  2. <?php echo $htmlExt->tagLengthErrorMsg('User/name', '氏名'); ?>
  3. <!-- エラー時は「メールアドレスは100文字以内で入力して下さい。」を表示 -->
  4. <?php echo $htmlExt->tagLengthErrorMsg('User/email', 'メールアドレス'); ?>

ソース内に文字列長を書かなくて良い

この方法では文字列長をテーブル定義から取得するので、ソースに文字列長を書く必要がありません。もしテーブルのカラムサイズが変わってもソースはそのままで新しいカラムサイズに対応することができます。

(ちなみにtext型などカラムサイズが無いものについてはテーブル定義情報(モデルの$_tableInfo)に直接文字列長を入れるという方法もあります。)

ここでは1.1系で記述していますが、1.2系ではvalidateが柔軟な形になっているので2.のモデルでのチェックはもうちょっとスマートに書けそうです。

PHPによる攻撃コードが出現–GIFファイルに隠される?

この記事の所要時間: 219

 セキュリティ研究者らは米国時間6月19日、PHPによって記述され、GIFファイルに埋め込まれた攻撃コードを大手の画像ホスティングサイトで発見した。SANS Internet Storm Centerへの投稿によると、この攻撃コードはファイルの先頭に正規の画像を配置することで監視の目をすり抜けたという。

PHPによる攻撃コードが出現--GIFファイルに隠される:ニュース - CNET Japan

GIFファイルにPHPコードが隠されてたとしてもPHPとして実行されなきゃ問題無いような気が。

なんなんだろーと思って twitter でつぶやいてみると @koyhoge さんと@takesako さんからヒントを頂きました。

自分なりに理解すると、ようはGIFファイルにPHPコードが入ってるんじゃなくて、PHPファイルの先頭にGIFヘッダ+バイナリが入っていると考えれば良いんじゃかいかと。なのでアップロードするファイルは「hoge.gif」ではなく「hoge.gif.php」になるという事でしょう。

これならアップロードした画像(PHPファイル)が<img src="hoge.gif.php" />で読み込まれた際にPHPコードが実行されるというストーリーが理解できます。

ただこれが成功するにはアップロードしたファイルの拡張子が「.php」になっている必要があるので、アップロード時にサーバ側で拡張子をチェックしておけば簡単に防げるように思います。実際に被害が出ているらしいのですが、被害があったサーバでは拡張子のチェックすらしていなかったということでしょうか。

うーん、何か思い違いをしているような気もする・・・。

[追記]

via: ITmedia エンタープライズ:攻撃コード隠した画像ファイル、大手ホスティングサイトで発見

PHPコード入りGIFファイルが実際に見つかっているようです。通常ならこれをアップロードしたところでどうという事はありませんが、もし万が一このファイルをinclude文やらrequire文等で読み込んでしまう or 拡張子を変える等で埋め込まれているPHPコードが実行されてしまいます。

このファイルは一見するとただの画像ファイルなのでライセンスフリーの画像として配布すれば、インターネット上に広がっていく可能性があります。いつ爆発するか分からない時限爆弾がばらまかれるようなものです。

今のところ爆発する危険性は低いとは思いますが、別のセキュリティホールと結びつくことによりPHPコードが実行される可能性も考えられます。

今後は画像ファイルにPHPコードが含まれているか否かを確かめる必要があるかもしれません。

うーん、どうなんだろ。。

IPアドレスなしでSSLサーバ証明書?

この記事の所要時間: 145

確かにグローバルIPが1つしか割り当てられていないサーバに複数サイトを詰め込むというのは良くある話なんでこれは中々興味深いです。

従来、独自ドメインでSSLサーバ証明書を利用するには、ウェブサイトごとにIPアドレスが必要だったが、同サービスにより、IPアドレスなしでも独自ドメインSSLが利用可能になる。

グローバルサイン、IPアドレスなしでも独自ドメインSSLが利用可能なサービス - CNET Japan

確かにグローバルIPが1つしか割り当てられていないサーバに複数サイトを詰め込むというのは良くある話しなんでこれは中々興味深いです。

でもこれどうやってるんだろ。

SSL 電子証明書 NonIP SSL サービス グローバルサイン株式会社の構成図を見てなんとなく想像してみた。

まず[ssl.example.com]をNonIP SSLから提供されるIPアドレスに設定する。で、証明書もNonIP SSLサーバにインストールする。あとは[ssl.example.com]から来たリクエストをSSLからのアクセスとして扱えば良いという感じなのかな。

ようはSSLのリバースプロキシですな。

もしこの通りなら、[ssl.example.com]<->[www.example.com]をどうやって繋ぐかが問題になりそう。

本来のSSLの機能に近づけるのなら、SSLサーバはホスティング事業者の
管理下におくべきなんじゃないかな?

Tadahira's blog - セキュアか?・・・グローバルサインの「NonIP SSL」

ですね。ローカルで繋げられればOKです。

でもサービスの内容から想像すると別のホスティング業者でも利用できるのがウリっぽいので、複合化してインターネット経由になるんでしょうね。このサービス利用者がVPN引くとも思えないですし:-p

うーん、微妙かな。。。でも実際問題とりあえずユーザからHTTPSに見えていればそれで良いという人もいたりするんで、ニーズはあるかも。(打診されそうな気もするぞ・・・)

CakePHP ライフサイクルチャート

この記事の所要時間: 025

via: cakebaker » What happens if you do a request?

CakePHPのライフサイクルがチャートになっています。

URLへのアクセスから出力まで(Controller#afterFilter()まで)書かれています。こうして図になっていると理解しやすくて良いですね。ソースを読む助けにもなります。

# ちょうどこのあたりを文章化していたので実はものすごく有り難かったり;-)

PHP4なスクリプトをPHP5へ移行する際に発生する7つの問題

この記事の所要時間: 119

2007/11/13: 3.と4.が同じ内容となっていましたので、4.の内容を変更しました。

ここ最近PHP4.1.2で稼働していたシステムをPHP5.1.6へ移行しています。

移行の際に発生した問題とその対応をメモしておきます。

ちなみに全ての内容がPHP4->PHP5で必要なものではないです。PHP4も4.1/4.2/4.3/4.4の間でちょこちょこ変わっているので、一部の内容はPHP4間の移行でも修正が必要になります。またPHP4.4に適応しているスクリプトをPHP5へ移行するなら修正内容は少なくなります。

1.Notice: Only variable references should be returned by reference

「function &method()」と定義しているメソッドでスカラー値を返すと発生します。

PHP:
  1. <?php
  2. class Hoge {
  3.   function &method() {
  4.     return false;
  5.   }
  6. }
  7. Hoge::method();

「function method()」にするか、本当に参照を返す必要がある場合ならスカラー値を一旦変数に代入してからreturnします。

PHP:
  1. <?php
  2. class Hoge {
  3.   // 参照を返さなくて良いなら
  4.   function method() {
  5.     return false;
  6.   }
  7.  
  8.   // 参照を返す必要があるなら
  9.   function &method() {
  10.     $ret = false;
  11.     return $ret;
  12.   }
  13. }

2.get_class()がクラス名を小文字で返さない

get_class()でクラス名を判定している時にはまります。PHP4ではクラス名が小文字(hoge)で返されるのに対してPHP5ではクラス名(Hoge)がそのまま返ります。

PHP:
  1. <?php
  2. class Hoge {}
  3.  
  4. $obj = new Hoge();
  5. if (get_class($obj) == 'hoge') {
  6.   // PHP4ならこちらを通る
  7. } else {
  8.   // PHP5ならこちらを通る
  9. }

PHP5専用で良いなら比較するクラス名を実際のクラス名(Hoge)に変更します。PHP4との互換性が必要ならget_class()の返り値をstrtolower()で小文字にします。

PHP:
  1. <?php
  2. class Hoge {}
  3.  
  4. $obj = new Hoge();
  5. // クラス名を変える
  6. if (get_class($obj) == 'Hoge') {
  7. }
  8.  
  9. // 小文字にする
  10. if (strtolower(get_class($obj)) == 'hoge') {
  11. }

3.Notice: Trying to get property of non-object

PHP4では値がnullな変数のインスタンス変数もnullでした。($obj = nullなら$obj->idもnull)この仕様の是非はともかく、まあこういう仕様でした。PHP5では改善?され、nullな変数のインスタンス変数を参照しようとするとNoticeが発生するようになっています。

PHP:
  1. <?php
  2. $obj = null;
  3. if (is_null($obj->id)) {
  4.   echo "foo\n";
  5. }
  6.  
  7. // PHP4.1.2
  8. // foo
  9.  
  10. // PHP5.1.6
  11. // PHP Notice:  Trying to get property of non-object in /home/shin/tmp/getter.php on line 3
  12. // foo

$obj->idをempty()で判定するか、$objがnullか否かを判定します。

PHP:
  1. <?php
  2. $obj = null;
  3. if (empty($obj->id)) {
  4.   echo "foo\n";
  5. }
  6.  
  7. if (!is_null($obj) && is_null($obj->id)) {
  8.   echo "foo\n";
  9. }

4.PHP Fatal error: Cannot redeclare

PHP4.1.2では同じ名前のメソッドが複数存在してもエラーにはなりませんでした。(単に後で定義したメソッドが呼ばれる)PHP5ではFatalエラーが発生します。

PHP:
  1. <?php
  2. class Hoge {
  3.   function a() {
  4.     echo "foo\n";
  5.   }
  6.  
  7.   function a() {
  8.     echo "bar\n";
  9.   }
  10. }
  11.  
  12. $obj = new Hoge();
  13. $obj->a();
  14.  
  15. // PHP4.1.2
  16. // bar
  17.  
  18. // PHP5.1.6
  19. // PHP Fatal error:  Cannot redeclare Hoge::a() in /home/shin/tmp/same_method.php on line 7
  20. ?>

5.short_open_tagがoff

ソースに含まれるphp.ini-recommendでは[short_open_tag=off]になっています。もし必要な場合はOnにしておきます。

6.Fatal error: Cannot redeclare method()

PHP4.1.2ではクラス内に同名のメソッドがあっても動作していました(後で定義した方が有効)。PHP5.1.6ではFatal errorが発生するようになっています。(確かPHP4.3.0あたりからこうなってたと思います。)

PHP:
  1. <?php
  2. class Hoge {
  3.   function method() {
  4.     echo "method1\n";
  5.   }
  6.   function method() {
  7.     echo "method2\n";
  8.   }
  9. }
  10.  
  11. Hoge::method();

先に定義されているメソッドはどこからも呼ばれていないはずなので削除するか、もし必要ならば別のメソッド名にします。

7.array_push()では参照を格納する

PHP5のオブジェクトは参照渡しが基本となっているので、array_push()でもオブジェクトの参照が連想配列に格納されます。ですのでループ内等で連続でarray_push()する際は注意する必要があります。

下の例ではPHP4.1.2ではidが異なるオブジェクトが格納されていますが、PHP5.1.6ではどちらも同じidになっているのが分かります。

PHP:
  1. <?php
  2. $array = array();
  3. $a = new stdClass;
  4. for ($i = 1; $i <= 2; $i++) {
  5.   $a->id = $i;
  6.   array_push($array, $a);
  7. }
  8.  
  9. var_dump($array);
  10.  
  11. // PHP4.1.2
  12. // array(2) {
  13. //   [0]=>
  14. //   object(stdClass)(1) {
  15. //     ["id"]=>
  16. //     int(1)
  17. //   }
  18. //   [1]=>
  19. //   object(stdClass)(1) {
  20. //     ["id"]=>
  21. //     int(2)
  22. //   }
  23. // }
  24.  
  25. // PHP5.1.6
  26. // array(2) {
  27. //   [0]=>
  28. //   object(stdClass)#1 (1) {
  29. //     ["id"]=>
  30. //     int(2)
  31. //   }
  32. //   [1]=>
  33. //   object(stdClass)#1 (1) {
  34. //     ["id"]=>
  35. //     int(2)
  36. //   }
  37. // }

PHP5ではオブジェクトを毎回newするか__cloneする必要があります。

PHP:
  1. <?php
  2. // new
  3. $array = array();
  4. for ($i = 1; $i <= 2; $i++) {
  5.   $a = new stdClass;
  6.   $a->id = $i;
  7.   array_push($array, $a);
  8. }
  9. var_dump($array);
  10.  
  11. // clone
  12. $array = array();
  13. $a = new stdClass;
  14. for ($i = 1; $i <= 2; $i++) {
  15.   $b = clone $a;
  16.   $b->id = $i;
  17.   array_push($array, $b);
  18. }
  19. var_dump($array);

意外と動きます

バージョン間の互換性が悪いと言われるPHPですが、PHP4.1.2からPHP5.1.6というロングジャンプ;-)でも、意外と動作しました。

専用サーバなどでよく採用されているCentOS(RHEL互換)の最新版(CentOS5)ではPHP5.1.6が採用されました。これにより今後はPHP5への移行が進むのではと期待しています。PHP4なシステムをメンテナンスされている方、一度PHP5環境を試してみてはいかがでしょうか。

ref: PHP: 下位互換性のない変更点 - Manual

CakePHP 任意のSQLを書く

この記事の所要時間: 125

CakePHPには強力なO/Rマッパーがあるので、普段はSQLを書かなくても良いのですが、DBの関数を呼ぶ時などやはりSQL文を直接書きたい場面が出てきます。

そこでSQL文を直接発行する方法です。

SQL文を直接発行するにはモデルのqueryメソッドを呼ぶ方法とdboのqueryメソッドを呼ぶ方法があります。モデルのqueryメソッドは結局内部でdboのqueryメソッドを呼んでいますので、どちらも取れる値は同じです。

PHP:
  1. <?php
  2. class HogeControlle extends AppController {
  3.   $name = 'Hoge';
  4.  
  5.   function index() {
  6.     // PostgreSQL
  7.     // モデル
  8.     var_dump($this->Hoge->query('select version();'));
  9.  
  10.     // dbo
  11.     $db =& ConnectionManager::getDataSource($this->Hoge->useDbConfig);
  12.     var_dump($db->query('select version();'));
  13.   }
  14. }
  15. ?>

出力値です。(同じ出力なので一つだけ)

CODE:
  1. array(1) {
  2.   [0]=>
  3.   array(1) {
  4.     [0]=>
  5.     array(1) {
  6.       ["version"]=>
  7.       string(88) "PostgreSQL 8.2.3 on i686-pc-mingw32, compiled by GCC gcc.exe (GCC) 3.4.4 (mingw special)"
  8.     }
  9.   }
  10. }

これが良いなあというのはSQLを発行した結果の値がメソッドの戻り値で取得できることです。

発行するだけなら何使っても簡単なんですが、結果を取得するのにfetchしたりするのがちょっと面倒だなと感じていました。(いやコード書いてもしれてるんですが、何となく;-))

[2007/06/07追記]

というかモデルに無いメソッドは__callでもれなくSQL文として発行されるみたいです。なので↓でいけます。(これはちょっとやり過ぎな感も)

PHP:
  1. <?php
  2. class HogeControlle extends AppController {
  3.   $name = 'Hoge';
  4.  
  5.   function index() {
  6.     $sql = "select version()";
  7.     var_dump($this->Hoge->$sql());
  8.   }
  9. }
  10. ?>

Ruby on Rails vs PHP?

この記事の所要時間: 152

Hi, I’m Mac もとい I’m Ruby on Rails で始まるコマーシャル。

よくできたパロディです。

Mac vs Windows のパロディ Ruby on Rails vs PHP のコマーシャル : 僕は発展途上技術者

MacのCMっぽく作られてて中々面白いです(ほんと良くできてます)。;-)

ネタなのであまり真面目に取るのはあれですが、Ruby on Rails(フレークワーク)とPHP(言語)を比べるのはどうかと。Ruby vs PHP or Rails vs CakePHP(SymfonyでもEthnaでもZFでも可)なら分かりますが。

これを見てると何年か前にあったPerl vs PHPを思い出しました。

「PHPはPerlより速い」

当時そんな話しをあちらこちらで見聞きしていたのですが、実際に比較されている内容を見るとCGIのPerlとApacheモジュールのPHP(mod_php)を比べて「PHPが速い」とされていました。知っている人から見れば「おいおい」な話なんですが、(私の知る限りでは)わりとそんな話がまかり通っていました。*1

# あと「PHPはDBに強い」なんてのもありましたね。Perlでも似たような事できるでしょうに。:-p

私自身はその頃からPHPで開発をしていたので「Perl よりPHP」「これからはPHP」みたいな当時の風潮は有り難いものでした。それまでPHPを使おうとすると相手を説得する必要があったのが、逆に「PHPで」と指名されるようになったり。

開発に関わる人(クライアントも含む)には非技術者の人もたくさんいます。(というかそちらの方が多い)ですのでイメージは大事です。(「雑誌見ると最近○○がきてるらしいんだけど、次は○○で行こうか。」とか言われたりしません?)

もしその人が持っているイメージと現実が異なる時は「あいつは分かってない」で済ませるのではなく、分かっている人がちゃんと説明するということですね。

# あ、RoRがどうこうという話ではないのであしからず。

*1 Perl vs PHPをキチンと比較したベンチマーク PHPの方が軽くて速いは本当?

Home > アーカイブ > 2007-06

検索
フィード
メタ情報

Return to page top