Cakephp1.3にアップグレードした時にしたこと(翻訳)

ざっくり翻訳しました。
http://mark-story.com/posts/view/updating-to-cake-1-3
ページネーションの条件引継ぎで今までviewでセットしていた$paginator->optionsが不要になったみたいですね。これでPaginationで検索条件が引き継げないという質問がなくなるかな。
マイグレーションで時間かかりそうなのは、Viewのヘルパー周りかなぁ。

下記、翻訳してまとめた内容です。

  • mark-story.comのサイトをcakephp1.3にした。超簡単だったので、そのアップグレードノウハウを書いておきます。
  • マイグレーションガイドはとりあえず見ておく
  • webroot/index.php, test.phpをcakephp1.3のものに更新
  • 古いバリデーションルール定義はcake1.3からなくなるので、変更
    • VALID_NOT_EMPTYとかを置き換える
  • HtmlHelperの$escapeTitleパラメータを、$options['escape']に置き換える
  • findAllをfind('all')に置き換える
    • findAllは1.3から削除
  • $this->params[Configure::read('Routing.admin')]は、$this->params['admin']に置き換え?(この箇所は理解してないので原文を読んでください。。。)
  • Model::del()などの削除されたメソッドを書き換える(Model::delete()に)
  • ページネーションで条件引継ぎの際に指定していた下記の記述は必要なくなるので削除
    • $paginator->options(array('url' => $this->passedArgs));
  • アップグレードは、1.1から1.2の時に比べて簡単。今回の自分のサイトのアップグレード修正は、この記事を書く時間を含めて2時間ぐらい。

Cakeのコアコンポーネントを拡張する方法の記事

「Extending CakePHP’s core components」
http://cakebaker.42dh.com/2009/09/08/extending-cakephps-core-components/

基本的には、importして継承する。
重要なのは記事の最後にある、constructClasses()の箇所で継承した方で上書きしてるところ。

// app/app_controller.php
class AppController extends Controller {
    public $components = array('MyRequestHandler');
	
    public function constructClasses() {
        parent::constructClasses();
        $this->RequestHandler = $this->MyRequestHandler;
    }
}

Cake3の方向性が分かる記事の紹介(日本語要約)

Debuggable.comのfelixgeが書いた「Cake 3 interview with Nate Abele」が面白かったので、日本語で要約したものを載せます。かなり要約してるので、本文をみた方が良いです。
http://debuggable.com/posts/Cake_3_interview_with_Nate_Abele:4a665a5e-5bfc-4e42-96ee-6d284834cda3

記事を読んだ感じ、Cake3はクロージャを使ったフィルターシステムに力を入れてるっぽいです。


下記、要約です。

  • Cake2
    • PHP5のみサポート
    • CakePHP1.2や1.3と互換性を持たせる
      • 現行アプリの移行作業は少なく簡単に移行できる
    • Cake2を使うだけで1.2などに比べて25%ぐらい高速になる
  • Cake3
    • ネームスペース
      • コアをいくつかのパッケージに分けて独立させる(パッケージ間の依存関係を持たせない)
      • コアコードをモジュールっぽくCake以外のアプリからも利用可能なように
    • クロージャ
      • フィルターシステム : 独自機能をクラスやメソッドに入れ込める
      • フィルターシステムのインターフェースは標準化させる(学習のローコスト化)
        • コアクラスのフィルターを1回書けば、他のコアクラスのフィルター方法もすぐに書けるようになる
      • 現在フィルターシステムはインスタンスに対してのみ使えるけど、将来はクラスにも適用できるようにしたい
    • 簡単にSQLシンタックスを修正、拡張できるようにする
  • Cake3のサンプルコード
    • 発行したSQLクエリを全てロギングしたい場合
      • 1.2だとDatabaseクラスに手を入れる必要有(本質と関係ない処理まで入れるのは嫌だ)
      • Cake3だと、SQLを実行する_execute()メソッドに対してロギングをするフィルターを簡単にかませることが出来る
<?php
Connections::get('default')->applyFilter('_execute', function($self, $params, $chain) {
  $out = fopen('php://stderr');
  fwrite($out, $params['sql']);
  fclose($out);
  return $chain->next($self, $params, $chain);
});
?>
  • Collectionクラス
    • 配列のように振舞うが、そこに含まれているオブジェクトのメソッドを呼び出せる
    • Collectionクラスを継承したRecordSetオブジェクトを使うと、DBの結果を取得する際に、Lazy-fetchすることができる
  • 非リレーショナルDB
    • より簡単に、柔軟に対応できるようにする

クロージャについて分かりやすく書いてある記事
http://ryus.co.jp/modules/d3blog/details.php?bid=24

リファクタリングを考える時期の記事紹介

自分の英語力アップと、技術力アップを兼ねて、好きな開発者の書いた記事をある程度翻訳して載せていこうと思います。今までMarkとかMcurryとかの興味深い記事をざっとは読んでたけど、自分が読むだけだと流し読みになりやすくて、読んだつもりになっただけで何も残らず、いかんなぁと思ってました。
全訳ではなく、自分が記事を理解して、ざっくりと翻訳して発信していくようにすれば、まず記事の内容を理解し、それを分かりやすく短く書くようになるので、良いのではないかと思いました。一人勉強会みたいなもんです。
これを続けていき、ある程度の段階になったら自分も英語で定期的に発信していきたい。


mark-storyの記事
5 signals that can indicate its time to re-factor


リファクタリングすべき時期はいつごろか?その指標となる5項目を挙げています。

1. メソッドのパラメータが多くなった時
2. ひとつのメソッドが多機能になってしまった時
3. 同じようなコードがいくつも存在している時
4. 名前の付け方が悪い時
5. あるべき場所(階層)にあるべきコードを書く

では、簡単に紹介。


1. メソッドのパラメータが多くなった時

パラメータが多くなると、パラメータや順序を覚えてなきゃいけないので、ミスが多くなる。
Cake1.1と1.2のfindメソッドで比較してみよう

<?php
//Cake1.1の場合
$this->Post->findAll($conditions, $fields, $order, $limit, $page, $recursive);

//Cake1.2の場合
$this->Post->find($type, $options);
?>

Cake1.1の場合はパラメータ6個!!で順番覚えるのも大変。
Cake1.2ののModel::find()の例は、必要な情報をoptionsに連想配列渡しするので、順番とか覚えてなくてもいいという例。

市川補足
Cake1.2ののModel::find()のパラメータ例は、model::findのコメント欄で書いている用法。実際のAPIマニュアルとは違う。

/**
 * Returns a result set array.
 *
 * Also used to perform new-notation finds, where the first argument is type of find operation to perform
 * (all / first / count / neighbors / list / threaded ),
 * second parameter options for finding ( indexed array, including: 'conditions', 'limit',
 * 'recursive', 'page', 'fields', 'offset', 'order')
 *
 * Eg: find('all', array(
 * 					'conditions' => array('name' => 'Thomas Anderson'),
 * 					'fields' => array('name', 'email'),
 * 					'order' => 'field3 DESC',
 * 					'recursive' => 2,
 * 					'group' => 'type'));
 */


2. ひとつのメソッドが多機能になってしまった時

メソッドに2つ以上の機能を入れてしまうと、テストがしにくいことと、使う場合に迷ってしまうことがある。
もし自分の作ったメソッドを説明する時に、and, or , butという言葉が入るならメソッドを2つ以上に分けたほうが良い。

<?php
public function getNode($id = null, $addView = true){
    $conditions = array();
    if (is_numeric($id)) {
        $conditions['Node.id'] = $id;
    } elseif(is_string($id)) {
        $conditions['Node.slug'] = $id;
    }
    $node = $this->nodeInfo($conditions);
    if ($addView && !empty($node)) {
        $this->id = $node['Node']['id'];
        $this->saveField('views', ($node['Node']['views'] +1));
    }
    return $node;
}
?>

上記の例だとgetNodeという名前なのに、後半でViewのカウントをインクリメントしている。
この場合は、次のように、viewカウントをインクリメントする機能を別メソッドに切り出したほうが良い。

<?php
public function getNode($id){
    $conditions = array();
    if (is_numeric($id)) {
        $conditions['Node.id'] = $id;
    } else {
        $conditions['Node.slug'] = $id;
    }
    return $this->nodeInfo($conditions);
}
 
public function updateViewCount($id) {
    return $this->updateAll(
        array('Node.views' => 'Node.views + 1'),
        array('Node.id' => $id)
    );
}
?>

こうするとテストもしやすくなるし、メソッド名から処理内容が分かるし、コードが読みやすくなる。



3. コードが重複している時
アップデートし忘れがあるので、重複コードは排除した方が良い。
元記事の例では、渡すパラメータの値が違うだけなので、それぞれのパラメータを事前に配列でセットして、foreachで回している例が載ってます。


4. 名前の付け方が悪い時

あまり変数名とか略しすぎないほうが良いとか。名前は重要。

市川感想
誰でもわかりやすくて的確な機能をもつ名前、変数をつけるのは、日本人にとっては英語力が結構いる気がする。分かりやすくても長い名前にするとスペルミスとかするし(エディタ補完が必須になる)。


5. あるべき場所(階層)にあるべきコードを書く(超勝手に訳しましたw)
オリジナルのタイトルはMixed levels of abstraction and muddled areas of concernです。
あるべき階層に、あるべきコードを書く。考える範囲を超えてコードをごちゃまぜにしない。例えばコントローラ内でSQL文を書くとかしない。SQL文はモデルに入れて、それをコントローラから呼び出すようにする。


まとめ
リファクタリングしてよりシンプルなコードに、テストしやすく、メンテナンスが楽になるのが理想。

PHP Reflectionの話

記事紹介
Using the PHP Reflection API for fun and profit


PHP5から存在するReflectionClassというのがあって、これを使うとメソッド名とか、PHPdocとか、メソッドの引数とか、色々なクラス内の情報を取得できる。

http://api.cakephp.org のAPIマニュアルも、API Generatorプラグインを使って表示してると思うんだけど、ここでもReflectionClassを多様して動的にAPIドキュメントを生成している。

例えば、

$modelReflector = new ReflectionClass('Model');
$method = $modelReflector->getMethod('find');
$parameters = $method->getParameters();
$doc = $method->getDocComment();

とかやれば、$parametersにModelクラスのFindメソッドのパラメータリストが入るし、$docにはphp docのコメント情報が入る。


API Generatorプラグインが動的にAPI情報画面を生成するっていうのをCakePHP勉強会@福岡で聞いたときは、どうやってるんだろと思ったけど、こいうのを使ってる訳ね。


追記
ちゃんとyandodさんのCakePHP福岡の発表資料にReflectionって書いてあった。
http://d.hatena.ne.jp/yandod/20090316/1237218756

Modelを2個以上読み込む場合は、$usesを使わない方がスピードアップする

CakePHPのスピードアップTIPs。
http://www.pseudocoder.com/archives/2009/04/16/one-more-tip-for-speeding-up-cakephp-apps/


コントローラの中で、$usesでモデルをいくつも書いておけば、
$this->Model1->find();
$this->Model1->Model2->find();
みたいにして快適にモデルにアクセスできるのですが、この$usesの配列にモデル名を加えていくと、一つにつき、4-6%表示時間が多くかかるとのこと。試しに7モデルを$usesで読み込むようにしたら、40%も表示時間が増えたそうです (追記:たぶんこの比較は、あるアクションで使うモデルが1つだった場合に、$usesで7モデル定義した場合と、$usesには何も定義せず、該当アクション内で1つのモデルだけloadModelした場合で比較してるっぽい)


コントローラでは、必要な個所で下記のようにモデルを呼び出すのが良いそうです。

$this->loadModel('Comment');
$comments = $this->Comment->findAllByPostId($id);

loadModelの他に、ClassRegistry::initを使う方法もあるけど、$usesで定義した方法と同じように$this->Modelでアクセスできるから、そっちの方が良いいかも。


それと、下記のメーリングリストの中で、モデルを読み込む場合は、App::import()はお勧めしないと書いてあります。
http://groups.google.com/group/cake-php/msg/794c451038c0c798

saveAllとかMySQL Explainなどの記事

  • saveAll() with multiple records AND for multiple models
    • 英語記事
    • 複数モデルの複数レコードを一気に保存
    • Viewのinputのnameは、先頭に数字を持ってくる
      • ただし数字は1から始めること。0からだと name="data[User][User][name]" になるので