Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

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

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

これが:

<?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
$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

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

ってなってたのが、

<?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 とでもして、こんなトレイトを作ります。

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'=>['*'],
            ],
        ];
    }
}

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

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
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ランタイムがソースをパースする時点で効いてくれるのがありがたいです。これだと、継承やトレイトの使用なんかの、かなり早い段階でパス解決が必要なものにとって都合がいいんですよね。

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

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

あ、ただ、コントローラやモデルなどの特殊なクラスは名前空間に入れると面倒が増える (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を作りはじめられます。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away