Home > PHP > CakePHP

CakePHP Archive

第3回CakePHP勉強会&第34回PHP勉強会に参加します

この記事の所要時間: 014

今週末の第3回CakePHP勉強会第34回PHP勉強会に参加してきます。

CakePHP勉強会では運営(手伝い)、PHP勉強会ではLTで話す予定です。

外の勉強会は久しぶりなので楽しみです。

当日参加される方々よろしくお願いします!

フレームワークで使われているPHP関数を数えてみた

この記事の所要時間: 418

CakePHPを使ってからempty()を使うようになった、なんて話が以前社内でもあったので、各フレームワークで使われているPHP関数を調べてみました*1

調べたのはCakePHP/symfony/ZendFrameworkで、それぞれ最新版を使用しています。

あと関数全てを載せると長いので上位20件のみ記載しています。

CakePHP-1.2RC1

array()が圧倒的ですね。2位以下もin_array()から5位のis_array()まではarrayに関係する関数となってます。

さらにarray()自体の数がスゴイです。ソースコードは3つの中で一番少ないのですが(symfonyの約2/3、ZFの約1/3)ですが、array()はsymfonyの約5倍、ZFの約2倍となっています。

いかにCakeがarrayを多用しているかが、良く分かります。

ソース行数:126,469行

16645 array
1519 in_array
1151 isset
889 empty
486 is_array
454 str_replace
378 unset
314 strpos
302 array_merge
270 count
241 sprintf
235 preg_quote
169 define
165 date
148 file_exists
148 preg_match
146 strtolower
144 explode
134 extract
134 substr

symfony-1.1.0RC2

やはりarray()が一位ですね。Cakeと違うのところでは、sprintf()が3位に来ています。さらに4位にsubstr()、5位にstrlen()と文字列関数が上位に来ています。

ソース行数:179,517行

3534 array
1083 isset
729 sprintf
515 count
372 substr
256 strlen
230 is_null
229 is_array
218 dirname
213 empty
204 strpos
201 unset
198 array_merge
164 in_array
156 str_replace
155 strtolower
150 preg_match
134 date
133 implode
124 preg_replace

ZendFramework-1.5.1

ソースコード行数が最も長いZFです。これもやはりarray()が1位となっています。4位にdirname()が入っています。Cakeでは50位(53件)、symfonyで218件(9位)なので、多いように感じます。あとord()が8位に、definedが12位などが上2つとは違う点でしょう。

ord()でソースをgrepしてみるとPDFやLucene、NTP関連のソースが引っかかりました。このへんはライブラリ的要素が強いZFの特徴ですね。

ソース行数:372,103行

8686 array
1528 isset
1407 count
999 dirname
789 is_array
650 empty
542 unset
508 ord
334 strlen
326 substr
295 array_key_exists
286 defined
243 explode
233 is_string
215 strpos
205 preg_match
202 date
201 strtolower
189 define
180 list

DB関数に着目

DB関連の処理は3フレームワーク共にフレームワーク自体に機能があるので、通常はPHPのDB関数を直接実行する必要はありません。これはフレームワーク内の処理も同様で、SQLを直接発行する箇所は1つにまとめておいて、各箇所からはそこを呼び出すイメージです。

よってpg_queryやmysql_queryは1つしか登場しないと思っていました。

Cakeは想像どおりそれぞれ1つしかありませんでした。ZFもPDOを使用しているのでこれらの関数自体が無いのですが、Oracleのoci_executeは2つ(1つはテスト)のみでした。

さて残るsymfonyですが、なぜかmysql_queryが27、pg_queyが20となっていました。あちこちに書いているのには、何か理由があるのでしょうか(歴史的な理由?)。

最後に関数の種類

最後に関数の種類ですが、3フレームワーク共に400前後でした。

もう少しじっくり見ると面白いところがあるかもしれませんが、今回はこのあたりで。

*1 array()やempty()など厳密には関数でないもののありますが、ここでは本質では無いのでスルーしてください;-)

CakePHP 1.2RC1からは比較演算子をキーに書く

この記事の所要時間: 155

via: “1.2RC1でのSQLインジェクション対策” フォーラム – CakePHP Users in Japan

リンク先にあるとおり、1.2RC1ではfind()の条件を指定する際、比較演算子を配列のキー側に書くように変更されています。

CakePHPでは値の先頭にある比較演算子をSQLとして実行してしまう特性があったので、1.2betaまでは以下のようなコードを記述していました。

    // $id = 1が外部から来るとする
    $user = $this->User->find(array('id' => '= ' . $id)); 

しかし1.2RC1でこのコードを実行すると[‘= ‘ . $id]が値として認識されてしまいます。

SELECT * FROM users WHERE id = '''= '' 1';

1.2RC1では条件配列のキーの部分に比較演算子を書くようになっています。

     // 比較演算子の前に半角スペース[0x20]を入れる
     $user = $this->User->find(array('id =' => $id)); 

キーに演算子が無ければ[=]とみなされるので、等号で比較するなら[‘ =’]は省略できます。

     $user = $this->User->find(array('id' => $id)); 

LIKE句なども同様に配列キーに記述します。

     $list = $this->User->find('all', array('conditions' => array('name LIKE' => '%foo%')));

通常、条件配列のキーはコードに直接記述するため、外部からは変更できません。セキュリティを高める意味でこれは良い変更だと思います。

# これでいちいち[‘= ‘ . $value]と書くのから解放されます。:-D

ただ1.2betaまでに合わせて書いたシステムでは、1.2RC1へ移行する際は修正が必要になります。

CakePHP PostgreSQLではgetInsertID()に注意

この記事の所要時間: 325

@deprecated

この情報はCakePHP1.2RC1までのものです。2008/06/26現在のリポジトリではチケットが反映され修正が完了しています。

CakePHP+PostgreSQLでModel#getInsertID()を使う場合、別セッションのシーケンス値が取得される問題があります。

問題点

ユーザ情報とその付加情報や、注文情報と明細情報などINSERT後にそのレコードに関連する情報としてid値を活用する場合、本来とは異なるレコードに結びつく可能性があります。

ex) 別の注文情報に明細が登録される等

ただ、これはほぼ同時にINSERT文が発行された際に起こる現象ですので、それほど登録処理が行われないサイトではあまり遭遇するものではありません。(ですので、これまであまり表面化しなかったかと。)

CakePHP1.2RC1/1.2-beta/1.1.19でこの問題があります。

解決策

フレームワークを修正して貰えるようにチケットは投げています。

https://trac.cakephp.org/ticket/4832

現状のソースのままで対応するなら、INSERT前にLOCK TABLEで該当テーブルをロックしておくことで対応できます。(nextval()で直にシーケンス値を変えられるとNGですが。)

<?php
  // Order は Model
  $this->Order->begin();
  $this->Order->query('LOCK TABLE orders IN EXCLUSIVE MODE');
  if (!$this->Order->save($data)) {
    $this->Order->rollback();
  } else {
    $this->Order->commit();
  }
?>

概要

PostgreSQLでは、自分が発行したINSERT文に対するシーケンス値を取得する際はcurrval()というPostgreSQLの関数を使用します。この関数は自セッションで発行された最新のシーケンス値が返ってくるので、別セッションでINSERT文が発行されても値は変わりません。これにより自分がINSERT文で追加したレコードを特定できるわけです。

SELECT currval('hoge_id_seq');

以前はCakeでもこのcurrval()を使って実装されていたわけですが、いつの頃か実装が変わって、現在のlast_valueを取得する形に変わっています。

SELECT last_value FROM hoge_id_seq;

last_valueはシーケン自体の最新の値になるので、上記のように別セッションにてINSERT文が実行されると、そちらで割り当てられたシーケン値が返ってきます。

流れとしては下のようになります。(初期シーケンス値が0、A/Bが同時に登録したと仮定)

[SQL]
CREATE TABLE hoge(
id serial primary key
,name text
);
[/SQL]
[SQL]
A: INSERT INTO hoge(name) value(‘foo’); // id=1
B: INSERT INTO hoge(name) value(‘bar’); // id=2
A: SELECT last_value FROM hoge_id_seq; // 2!
B: SELECT last_value FROM hoge_id_seq; // 2
[/SQL]

この場合、どちらも2がModel#getInsertID()の値として返ってきます。

当然この値をhoge_idとして別のテーブルに登録すれば、本来hoge_id=1に登録されるべきであったレコードがhoge_id=2として登録されてしまうわけです。

頻度は少ないが、発生すると問題

それほど発生することは無いにせよ、発生した際の影響が大きいです。(注文情報とカード番号が別テーブルになっていて、別の注文情報にカード番号が登録されたら。。。)

フレームワークが修正されるのも大事ですが、LOCK TABLEを活用してトランザクション内で別のINSERTが発生しない(シーケンスが進まない)方がより安全に登録処理を行うことができます。状況が許すなら活用してみて下さい。

[告知] 第3回CakePHP勉強会が開催されます。

この記事の所要時間: 045

第3回CakePHP勉強会を開催します。

CakePHPの活用事例やさまざまなネタで交流し、さらに広がるCakePHP界を盛り上げましょう。

第3回CakePHP勉強会 – events.php.gr.jp

第3回CakePHP勉強会が6/27(金)で開催されます。

場所は前回と同じくトライコーンさん(いつもありがとうございます!)です。

今回もyandoさんの尽力のおかげで強力な事例発表があり、かなり楽しみな内容となっています。Cakeな方もそれでない方も是非ご参加下さい;-)

申込は6/3 13:00から↓のリンク先からお願いします。ここ最近PHP関連の勉強会は申込開始から数十分(!)で定員が埋まってしまうこともあるので、お忘れなく。

概要・申込は、第3回CakePHP勉強会 – events.php.gr.jpからどうぞ。

CakePHP 1.2beta対応-CakeInfo-0.1.2リリース

この記事の所要時間: 116

CakePHPアプリケーションの内容をphpinfo()風に見せるCakeInfoをCakePHP1.2-betaに対応しました。

OpenFLPを表示するとこんな感じです。

主な変更点は以下です。

  • CakePHP1.2-beta対応
  • Controllerにaction_methodを追加

今回追加したControllerのaction_methodでは外部からアクションメソッドとして呼べるメソッドを表示しています。これを見ると公開するつもりの無いメソッドが公開されていないかを確認できるので、自作のCakeアプリにて試してみてください。

設置

設置方法は簡単です。
まず[http://www.1×1.jp/blog/download/cakeinfo-0.1.2.zip]からzipファイルをダウンロードします。
あとはダウンロードしたzipファイルを展開し、[cakeinfo.php][logo-mini.gif]を[app/webroot/]に設置するだけです。

設定

現在のところ設定項目は[DATABASE_CONFIG_FLAG]一つだけです。

  • DATABASE_CONFIG_FLAG
    この定数が設定されていればDatabase項目が表示されます。(デフォルトはコメントアウト)

自作のアプリ以外にも、オープンソースで公開されているシステムの解析(の取っかかり)にも使えますね。お試し下さいませ:-D

CakePHP パフォーマンスが出ない時は、例えばフレームワークを避ける

この記事の所要時間: 331

フレームワークを使ってパフォーマンスに問題があったため、Plain PHPで書き直したという話です。

先日受信したメールの内容を.forward経由でDBを保存するという処理を実装しました。まあ良くある処理なのですが、このシステムではWeb側をCakePHPで実装していたので、メール処理もCakePHPのCLI機能(cakeコマンド)を利用しました。

実際に動作してみて数件程度では問題無かったのですが、負荷試験として短時間に数百件、数千件のメールを受信させると、LAが80程度まで跳ね上がりました。処理自体を見直したり、不要なSQLをカットしたりしたのですが、それほど大きな効果はありませんでした。

これはフレームワークの起動に時間がかかっていると判断し、CakePHPを使わずベタなPHPスクリプトだけで実装してテストすると、同じ負荷をかけてもLAが3-4程度まで落ちました。

timeコマンドで実行時間を計測しても、3-4倍の差がありました。(もちろんPHPスクリプトだけの方が早い)

$ time hoge.php

実はこういった場面はこれまでも経験していて、Web側では負荷が高い箇所やパフォーマンスが出ない箇所ではフレームワークを使わずに実装することがありました。CakePHPにCLI対応が含まれているとはいえ、CLI環境においてもパフォーマンスが出ないシーンでは、あえてフレームワークを使わないという選択肢は覚えておく必要があります。

ただこういったケースがあるからといって、どんなケースでもフレームワークを全く使わないというのは賢明ではありません。

フレームワークで構築することには多くの利点があり、ある程度習得しているフレームワークであれば、PHPスクリプトだけで開発するよりも生産性や安定性等々で有利です。フレームワーク上で十分に性能が出ている場合はそのままで何ら問題ないです。

このあたりのバランスを保つのが大事なのですが、まずはフレームワークで構築しておいて、問題がある箇所を崩していくというアプローチが単純で良いかと。

今回の場合、フレームワーク上で実装していたものをPHPスクリプトで再実装したので、同じ機能をアプローチを実装しただけなので、短時間で再実装することができました。プロトタイプ的にフレームワークで組むというのも良いかもしれません。

CakePHPのDB設定を参照する

これに関連してミニTipsを。

フレームワーク無しで実装する際でもせめてDBの接続情報くらいは共有しておきたいものです。そこで今回はPHPスクリプトから[app/config/database.php]を参照するようにしました。

app/config/database.phpの情報からDBに接続する。

require_once('/path/to/cake/app/config/database.php');

   public function connect() {
     $db = new DATABASE_CONFIG();
     $dsn = sprintf("dbname=%s user=%s password=%s"
                 , $db->default['database'], $db->default['login'], $db->default['password']);
     if ($db->default['host']) {
       $dsn .= sprintf("host=%s port=%d", $db->default['host'], $db->default['port']);
 
     }
     $this->conn = @pg_connect($dsn);
     if (!$this->conn) {
       $this->log(__FILE__ . ':' . __LINE__ . ' ' . $dsn);
       exit;
     }
   }

CakePHP Modelに関する6つの誤解

この記事の所要時間: 327

CakePHPのModelはActiveRecordライクなDBアクセス方法を提供しており、さらにアソシエーションを設定することにより複数テーブルの値を同時に操作できるなど、DB操作に対するインターフェイスが数多くあります。

ただ「手軽にDB操作ができる」という印象が先行しているゆえ誤解を招くことがあるようです。

1. クラス名に対応したテーブルしか操作できない

Modelのクラス名とテーブルを自動でマッピングするのはフレームワークのいわば便利機能です。デフォルトでそのような動作をするだけで、容易に変更することができます。

Model#$useTableにテーブル名を指定すれば任意のテーブルを操作できます。

<?php
class Foo extends AppModel {
  public $useTable = 't_user'; // t_userテーブル
}
?>

2. DBを使わないといけない(DB操作が無いと使えない)

DB操作が強調されるので陥りがちですが、DBを使わないModelも作成できます。例えばバリデーションだけを行うModelやDB操作を行わないビジネスロジックを実装したModelなどももちろんokです(というかビジネスロジックを実装するもの)。

DB操作を行わないModelでは、Model#$useTableにfalseを入れておけば良いです。(nullではなく、falseです)

<?php
class Foo extends AppModel {
  public $useTable = false; // テーブルに対応させない
}
?>

3. ステートレスである

find()/findAll()等ではarrayを返すので、何となくステートレスな感じがしたりしますが、実はModelではしっかり情報を保持しています。

メソッド終了時にリセットされる値も一部ありますが、Model#set()などで設定した値はそのまま保持しています。ループの中で複数レコードを登録する際は注意する必要があります。

保持している値についてはModel#create()を呼ぶことにより初期化(クリア)できます。

<?php
    foreach ($list as $data) {
      $this->Foo->create();  // クリアしておく
      $this->Foo->save($data);
    }
?>

4. bakeで生成したModelは変更しない(できない)

bakeでバリデーションやアソシエーションを設定しておくと、生成されるModelではインスタンス変数に様々な値が記述されます。

単純なDB操作のみならばこのままModelのソースを触ることなく開発できますが、複雑なDB操作やビジネスロジックなどを実装する場合は、臆することなくModelにガンガン追加していきましょう。

5. DBのリレーションを全てアソシエーションでも設定する

アソシエーションはとても便利なものですが、上手く利用しないと思わぬSQLが発行され、パフォーマンスに影響が出ます。テーブル同士に外部整合性制約を付けてリレーションしているからと言って、必ずアソシエーションを設定する必要はありません。

はじめからシステムで不要と思われるアソシエーションについては設定しないというのも手です。

いっそ最低限のアソシエーションのみを設定しておき、後は用途に応じてModel#bind()/bindModel()する方が良いかもしれません。

特にModel#$hasManyを設定していると芋づる式に数多くのSQLが発行される場合があるので、注意しましょう。

6. DAOである

DAOのように振る舞うメソッドが多数ありますが、実際にDBへアクセスするのはDataSourceを継承したdboクラスであり、Modelではありません。

ModelをDAOとしか認識していないと、ビジネスロジックはControllerに実装することになり、Controllerが肥大していくことになります。

もちろんModelをDAOのように使うこともできますが、あくまでModelですので、ビジネスロジックはControllerでなくModelで実装して、Controllerへはそのインターフェイスを提供するようにしましょう。

CakeのModelはMVCのModel

しつこく書いてますが、CakeのModelもいわゆるMVCのModelと同じです。DB機能ばかりが強調されていますが、それに捕らわれることなく本来のModelとして活用しましょう。

CakePHP 1.2で携帯用ビューを表示する

この記事の所要時間: 332

CakePHP1.2ではCakePHP 携帯用ビューを表示するで利用していたwebservicesの機能が無くなります。

1.2-betaでRouting.webservicesをonにすると以下のようなメッセージが表示されます。

Deprecated: webservices routes are deprecated and will not be supported in future versions.  Use Router::parseExtensions() instead.

The prefix automagic in CakePHP routingで紹介されているように、1.2からはwebservicesに替わりprefixをURLルーティングで使用するようです。

そこで実際にどのように使用するかを試してみました。

1. URLルーティングでprefixを設定する

Router::connect()の第2引数ではパラメータを連想配列で設定できるのですが、そこに’prefix’というキーで値を設定しておくと、対象URLにアクセスがあればこの機能が有効となります。

下記の例では[/m/foo/bar]へアクセスがあると、prefix=mobileが有効となります。

[app/config/routes.php]

Router::connect('/m/:controller/:action', array('prefix' => 'mobile'))

2. prefix用アクションを作成する

prefixが有効な状態だと、アクションとして[prefix + _ + action]が呼ばれるので、アクションを実装します。

1. の設定で[/m/foo/bar]へアクセスされると、prefix=’mobile’が有効となるので、FooController#mobile_bar()がアクションとして呼ばれます。

[app/controllers/foo_controller.php]

<?php
class FooController extends AppController {
  public function mobile_bar() {
    // 通常のアクションを実装
  }
}
?>

3. prefix用ビューを作成する

2.で実装したアクション同様にビューテンプレートを作成します。

これは通常どおり2.で作成したアクションに対応するビューテンプレートを作成するだけです。

注意点は$html->link()などでURLを指定する際は、アクションにprefixが付いたアクション(mobile_bar)を指定するという事です。下の例なら「/m/foo/bar」がURLとして出力されます。

[app/views/foo/mobile_bar.ctp]

<?php echo $html->link('link', array('controller' => 'foo', 'action' => 'mobile_bar')); ?>

△prefix用Component/Helperが読まれない

携帯電話では入出力文字エンコーディングを変換する事が多いのですが、webservicesを使用すると読み込まれるComponent/Helperで実装していました。

今のところprefixではこの機能が無いようなので、Controller#beforeFilter()/afterFilter()等で自前で実装する必要がありそうです。

そのリクエストで設定されているprefixはController#$params[‘prefix’]で確認できます。(@see 1.2 の WEBSERVICES パラメータ)

○prefix用アクションは直接呼ばれない

prefixに設定したアクションは直接呼ぶことができず、prefixで設定したURLでないとアクセスできません。

つまり1.のような設定の場合、[/foo/mobile_bar]へアクセスしてもprivate methodとして扱われてエラーとなります。

これによりprefixが設定されず、prefixアクション(mobile_bar)が呼ばれるのを防ぎます。

このあたりの挙動はRouting.adminと同じですね。:-D

webservicesは中々便利な機能だったので無くなるのは残念ですが、代替機能があるのは嬉しいことです。

CakePHP DboSourceをPHP5らしく使う

この記事の所要時間: 044

CakePHPではDBアクセスは通常Modelを介して行うので、直接DboSourceを利用する事は無さそうですが、SQL文を自分で構築する際など、意外と使う機会があります。

DboSourceは、通常以下のよう使用します。

<?php
// $hogeをエスケープ
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$db->value($hoge);
?>

もちろんこれでも問題無いのですが、やや冗長な感じもあります。そこでPHP5で以下のように書いてみました。

<?php
ConnectionManager::getDataSource($this->useDbConfig)->value($hoge);
?>

1文にまとまりました。ただ、まだ冗長なので、これをAppModelでまとめてみます。

<?php
class AppModel extends Model {
  public function getDbo() {
    return ConnectionManager::getDataSource($this->useDbConfig);
  }
}
?>

使う際はこうなります。

<?php
// Modelなら
$this->getDbo()->value($hoge);

// Controllerなら
$this->Foo->getDbo()->value($hoge);
?>

スッキリした書き方になりました;-)

そろそろPHP5も普及しつつあるので、CakePHPももっとPHP5らしい書き方に移行したいな、という話でした。

ホーム > PHP > CakePHP

検索
フィード
メタ情報

Return to page top