YiiはPHP5.4にするといろいろ良くなる


いまだにPHP5.1互換を維持している珍しいフレームワークYiiですが、PHP5.4で使うとどういうメリットがあるか考えたまとめです。すでに5.3のクロージャを使うメリットはCookbook(Kindleで買おう)の中に書かれているので、その先をいきます。

## ショートアレイ・シンタックス

PHP5.4には、`array(…)` を `[…]` と書くことができる文法が増えました。いつも見ているCRUDのビューにあるメニュー。

これが:

```php
<?php
$this->menu=array(
	array('label'=>'List Entry',  'url'=>array('index')),
	array('label'=>'Create Entry','url'=>array('create')),
	array('label'=>'Update Entry','url'=>array('update','id'=>$model->id)),
	array('label'=>'Delete Entry','url'=>'#','linkOptions'=>array(
		'submit'=>array('delete','id'=>$model->id),
		'confirm'=>'Are you sure you want to delete this item?')
	),
	array('label'=>'Manage Entry','url'=>array('admin')),
);
```

こうなる:

```php
<?php
$this->menu=[
	['label'=>'List Entry',  'url'=>['index']],
	['label'=>'Create Entry','url'=>['create']],
	['label'=>'Update Entry','url'=>['update','id'=>$model->id]],
	['label'=>'Delete Entry','url'=>'#','linkOptions'=>[
		'submit'=>['delete','id'=>$model->id],
		'confirm'=>'Are you sure you want to delete this item?'
	]],
	['label'=>'Manage Entry','url'=>['admin']],
];
```

だいぶすっきりします。arrayでのプロパティ設定が多いYiiでは、ショートアレイがかなり効きそうです。メソッドのコールと混ざった時なんて、これまで

```php
<?php

… ))); // どのカッコがarrayでどのカッコがメソッドの末尾だよ
```

ってなってたのが、

```php
<?php

… )]); // コールの末尾引数がarrayで、arrayの最後の要素は何かの戻り値か
```

というふうになって、わかりやすいですね。

## トレイト

### その前にYiiのビヘイビアについて

Yiiの全クラス共通の基底クラスである `CComponent` には、 `attachBehavior` もしくは `attachBehaviors` というメソッドがあります。これは、`IBehavior` を実装したクラスからメソッドをミックスインしてくる機能となっています。

ミックスインがあるならトレイトは要らない? と思ったら大間違い、ビヘイビアには大きな落とし穴があります。それは…

**既存のメソッドをオーバーライドできないこと。**

PHPのマジックメソッド `__call` は、そのインスタンスに **コールできるメソッドがなかったとき** に呼び出されます。つまりコールできるメソッドがあるとこれが呼ばれない。それで、Yiiのような独自の仕組みでメソッドコールをごにょごにょするものには、そういう制約がかかってきてしまう。

ビヘイビアは **まだ存在しない名前のメソッド** を追加するもの… というわけで、これがどういうとき困るかというと、アクションのセットが共通する複数のコントローラ間で、`accessRules` を外に出して共有したいみたいな場合には使えないということ。というのも、`CController` には空の `accessRules` がすでに実装されちゃってるので、ビヘイビアに追い出してそれを共有しても、意図したようにはコールされません。

https://github.com/yiisoft/yii/blob/1.1.12/framework/web/CController.php#L244

そんなの、抽象コントローラクラスに `accessRules` を書いて、それを継承し分ければいいじゃん、とか考えると、こんどは意図しないものまで巻き込んで、全部共通かそうじゃないかの二択になってしまいます。そういうのは困るんですよ。`accessRules` メソッドの実装 **だけ** を共有したいのです。

### ぜんぶトレイトで解決だ

というわけでやっと登場、5.4のトレイトです。ちゃんとしたPHPの文法なので、マジックメソッドに行く必要なし、なのでメソッドのオーバーライドもきちんとできて、メソッドのセットをいろいろ組んで、それらの組み合わせが自由ときたもんだ。

やってみましょう。ディレクトリを `components/accessRules` とでもして、こんなトレイトを作ります。

```php:protected/components/accessRules/MembersCanEdit.php
<?php
namespace application\components\accessRules;

trait MembersCanEdit {
    /**
     * メンバーだけ編集できるというアクセスルール
     * @return array
     */
    public function accessRules()
    {
        return [
            ['allow', // みんな見ていいよ
                'actions'=>['index','view'],
                'users'=>['*'],
            ],
            ['allow', // メンバーだけ編集できる
                'actions'=>['create','update'],
                'users'=>['@'],
            ],
            ['allow',
                'actions'=>['admin','delete'],
                'users'=>['admin'],
            ],
            ['deny',
                'users'=>['*'],
            ],
        ];
    }
}
```

コントローラではそれを使います。

```php:protected/controllers/EntryController.php
<?php
use application\components\accessRules\MembersCanEdit;

class EntryController extends Controller
{
    use MembersCanEdit; // オーバライドできちゃった

    public function filters()
    {
        return [
            'accessControl',
        ];
    }
	...
}
```

これで既存のメソッドをオーバーライドすることができました。

長い決まりきったコードをコントローラから追い出せるので、だいぶ見通しが良くなりそうですね。しかも、決まった名前のメソッドなので中身を読まないと意味がわからなかった `accessRules` なんてものが、`MemberCanEdit` という意味のある名前になりました。

モデルにも似たパターンのコードがあるなら応用できそうです。また、トレイトからインスタンスのフィールドを読むこともできる(なんとprivateでも可なのはさすがのPHP)ので、

```php
<?php
trait Hoge
{
    public function fuga()
    {
		// 基本は option1 と option2 を返すけど、
        return array_merge([
            'option1'=>123,
            'option2'=>456,
        ], $this->customFuga ?: [ ]);
		// use元でカスタムパラメータを追加できる余地もあるよ。
    }
}
```

というふうに、まったく同じ実装ではなく、**同じようなパターンの実装** 間で共有することもできたりします。いいですね。

## 名前空間

すでにやっちゃってますが、Yii 1.1.5 から名前空間に入っている通常のクラスをうまくロードするオートローダーが付いてるみたいです。

http://www.yiiframework.com/doc/guide/1.1/ja/basics.namespace#sec-5

5.4特有の機能ではないですが、トレイトを使いまくる場合は名前空間で整理してオートローダで読み込みたいですよね、というわけでオススメします。名前空間ベースのローダだと、自分のコードで `Yii:app()->import()` なんちゃらと動的にやるずっと前の、PHPランタイムがソースをパースする時点で効いてくれるのがありがたいです。これだと、継承やトレイトの使用なんかの、かなり早い段階でパス解決が必要なものにとって都合がいいんですよね。

ほか、自作のコンポーネントなども、名前空間を使ったほうが分けやすい気がします。機能分けのために `modules` や `extensions` に入れてしまうと、それがアプリケーションと密結合な機能なのに、なんだか遠い感じがするので。できれば `components/hogehoge` なんかの、近場の任意のパスに入れたいなぁと、ただそれだけなんですけどね。

もし名前空間が嫌いなら `config/main.php` の `import` に自動ロード対象のパスを追加しても、トレイトはロードできます。ご心配なく。

あ、ただ、コントローラやモデルなどの特殊なクラスは名前空間に入れると面倒が増える (Giiが出力するコードとの相性やら、コントローラ名前空間内からの別クラスのロードが思い通りにいかなかったりなど) 気がするので、そいつらだけは、1.x のうちは無理に名前空間化しなくてもいいと思います。

## ビルトインサーバ

PHP5.4にはビルトインサーバという機能があります。別途Webサーバがなくても、PHPのページを表示するための簡易サーバです。

```
php -S localhost:8080
```

ただ困ったことに、mod_rewriteに相当することをやるには、それに代わるディスパッチスクリプトを書かなければいけません。また、 `$_SERVER` の中身もちょっと変わっていて、ルーティングまわりで複雑なことをやっている場合はちょっと敷居が高くなります。

ところが、Yiiの新規プロジェクトはまず最初、`index.php?r=site/index` のようなルーティングになるかたちで生成されます。つまり、Webサーバとして見るとPHPページに見えてくれます。

なので、まだ `protected/config/main.php` で `'urlManager'=>array(…)` がコメントアウトしてあるうちは、とくにスクリプトを書いてないビルトインサーバで問題なく動いてくれます。これは初期のプロトタイピングにすごく使えますね。

DBの設定も、最初はsqliteになっていて、そのデータファイルが `protected/data/` に入っています。ということはDBサーバもなくていい。簡単なデータ構造でまずはモデルを作って、そのCRUD用のUIがどういう感じになるかローカルで動かして試してみる、というところまでなら、PHPインタプリタだけで可能です。

まあ、やろうと思えば他のフレームワークでも可能ですが、Yiiがいいのは、初期プロジェクトがすぐにビルトインサーバで動くということ。

```
$YII_HOME/framework/yiic webapp myapp-proto
cd myapp-proto
php -S localhost:8080
```

`$YII_HOME` はYiiをダウンロードして展開したパスの意味です。

はい、これでもうApacheもMySQLもなしでYiiのアプリケーションがちゃんと動きます。ログインもお問い合わせもできるし、`protected/config/main.php` のGiiのコメントアウトを解除してパスワードを付ければすぐに `tbl_user` テーブルのCRUDを作りはじめられます。