Technote

by sizuhiko

CakePHP2のプラグインをTravis.ciで継続的インテグレーションする

春先ぐらいからBlogの更新が滞っていましたが、その要因となったアレが大分落ち着いてきたので、たまっていたネタを順番に書き出します。

CakePHP2のプラグインを作成/公開していて、継続的インテグレーションってどうするの?と思っていました。 もちろんユニットテスト書いてあるし、素晴らしい協力者の方がPull Requestを送ってくれれば、developブランチに取り込んでテストしたりします。

Githubを徘徊していると、よく見る

これを表示したいと思ったのです。

ただCakePHP2のプラグインは当然それだけではユニットテストを実行できません。CakePHPのアプリケーションがあって、テスト用データベースとその設定があって…、ぬーん。 早い話、テスト用のアプリケーション作って、そこからテスト実行すれば良いの?ということをずっと思っていました。

CakePHPの有名プラグインはどうしているのだろう?

と思い、最初にチェックしたのはCakeDC/searchプラグイン。 大抵のアプリケーションで使う、もっともメジャーなプラグインではないかと思います。 Searchプラグインにも、あのビルド成功やダウンロード数、バージョン番号の画像が表示されています。

.travis.yml ファイルがあるので、内容を確認してみます。 すると、

before_script:
  - git clone https://github.com/burzum/travis.git --depth 1 ../travis
  - ../travis/before_script.sh
  - if [ "$PHPCS" != 1 ]; then
            echo "
                require_once APP . DS . 'vendor' . DS . 'phpunit' . DS . 'phpunit' . DS . 'PHPUnit' . DS . 'Autoload.php';
            " >> ../cakephp/app/Config/bootstrap.php;
    fi

https://github.com/burzum/travis.gitをcloneしてきて、何かやっているようです。

CakePHPプラグインを簡単にTravis.ciで継続的インテグレーションできる

早速、ページにアクセスしてみると、どうもそれはあのFriendsOfCakeからForkされたもののようです。 FriendsOfCake元リポジトリを確認します。

Easy travis setup for CakePHP plugins This repository helps easy travis integration for CakePHP plugins, primarily focused on FriendsOfCake projects, but can be used within any plugin when satisfying the requirements.

おぉ、正に私の求めていたもの。

Fabricateに導入してみた

最近作った中で、最も思い入れのあるCakePHPプラグインFabricateを継続的インテグレーションするため、上記のFriendsOfCake/travisを導入してみます。

Quick Installに書いてるとおり、以下の手順で進めます。

cd ~/develop/fabricate

git clone https://github.com/FriendsOfCake/travis.git

export PLUGIN_NAME="Fabricate"

travis/setup.sh

rm -rf travis

私がやったときはこの手順ではなかったのですが、最近変わったみたいです… まぁ細かい事は気にせずですね。

実行すると、以下のファイルが自動生成されます。

  • .travis.yml : Travis.ciで継続的インテグレーションを実行するための設定ファイル
  • Test/Case/AllFabricateTest.php : プラグインのユニットテストをすべて実行するテストスイート。すでにこの命名規則で生成されていれば作られません。

以前のバージョンでは

  • .editorconfig
  • .semver
  • .travis.yml
  • .AllPluginNameTest.php
  • composer.json
  • CONTRIBUTING.markdown
  • LICENSE.txt
  • README.markdown

が生成されていましたが、継続的インテグレーションには必要がないファイルも混在していたので、整理されたものと思います。

後はTravis.ciにサインアップして、リポジトリを追加するだけです。

実は、これが最初のビルド。PLUGIN_NAMEをexportしていなかったので、正しくテストの実行が動いていませんでした。PLUGIN_NAMEはちゃんとexportするようにした方が良いです。 .travis.ymlのPLUGIN_NAMEFabricateに設定して実行した結果が以下の画面です。

現在のtravis.gitではPHP5.4以上のようなので、問題ないのですが、当時(と言っても数ヶ月前)はPHP5.3もテスト対象になっていたので、ショートArrayシンタックスを使っている私のコードはテストが失敗していました。.travis,ymlのPHP部分を以下のように変更して

php:
  - 5.4
  - 5.5

pushすると、ビルド結果は以下のようになります。

PHPCS以外は成功しています。

PHPCSではCakePHPのコード標準がチェックされます。 CakePHPの標準コードチェックだと厳しい部分だったり、パラメータを変更してチェックさせたくないディレクトリがあったりすると思うので、PHPCS_ARGSでphpcsを実行するときのパラメータを上書きできるようになっています。

matrix:
  include:
    - php: 5.4
      env:
        - COVERALLS=1
    - php: 5.4
      env:
        - PHPCS=1
        - PHPCS_ARGS="-p -s --extensions=php --standard=ruleset.xml --ignore='*/Test/*,*/Vendor/*' ."

拡張子phpのファイルを対象とし、TestやVendorのディレクトリを対象外として実行します。 ruleset.xmlは以下のようにしました。

<ruleset name="Custom Standard">
    <rule ref="CakePHP">
        <exclude name="CakePHP.NamingConventions.ValidVariableName.PrivateNoUnderscore" />
        <exclude name="CakePHP.NamingConventions.ValidFunctionName.PrivateNoUnderscore" />
        <exclude name="CakePHP.NamingConventions.ValidVariableName.ProtectedNoUnderscore" />
        <exclude name="CakePHP.NamingConventions.ValidFunctionName.ProtectedNoUnderscore" />
        <exclude name="CakePHP.NamingConventions.ValidFunctionName.ScopeNotCamelCaps" />
        <exclude name="CakePHP.NamingConventions.ValidVariableName.MemberVarNotCamelCaps" />
        <exclude name="CakePHP.WhiteSpace.TabAndSpace" />
    </rule>
</ruleset>

CakePHPのコード標準ではprivateやprotectedの変数、関数の先頭にアンダースコアが求められるので、それを無効にしたのと、キャメルケースではない変数や関数名を使いたい箇所があったので、それを無効にしています。 最後のCakePHP.WhiteSpace.TabAndSpaceでは、以下のように=のインデント位置を合わせたコードがエラーになってしまうので、無効にしています(わかりづらいですが、=の位置が揃っています)。

public function __construct($name) {
    $this->name  = $name;
    $this->items = [];
}

どうなっているのか?

プラグインのリポジトリには.travis.ymlが追加されるぐらいです。 この設定内で、FriendsOfCakeのtravisリポジトリがcloneされ、以下に記述されている手順が実行されます。

before_script:
  - git clone -b master https://github.com/FriendsOfCake/travis.git --depth 1 ../travis
  - ../travis/before_script.sh

script:
  - ../travis/script.sh

after_success:
  - ../travis/after_success.sh

実行されるパターンは.travis.ymlに記述されているphpenv-matrixの組み合わせと、matrixのパターンになります。

  • PHP5.4, DB=mysql CAKE_VERSION=2.4
  • PHP5,4, DB=mysql CAKE_VERSION=2.5
  • PHP5.5, DB=mysql CAKE_VERSION=2.4
  • PHP5,5, DB=mysql CAKE_VERSION=2.5
  • PHP5.4, COVERALLS=1
  • PHP5.4, PHPCS=1

この6パターン毎に、before_scriptscriptafter_successが呼び出されます。

事前準備

before_script.shは実行準備のためのスクリプトで、PHPCSが1にセットされている場合は、以下のとおりpearチャンネルからCakePHPのコード標準とphpcsが依存関係としてインストールされます。

if [ "$PHPCS" = '1' ]; then
    pear channel-discover pear.cakephp.org
    pear install --alldeps cakephp/CakePHP_CodeSniffer
    phpenv rehash
    exit 0
fi

続いて、CakePHP本体が1つ親のディレクトリにインストールされます。Travis.ciではプラグインのリポジトリが最初にcloneされるのですが、結果として以下のようなディレクトリ構成となります。

  • $HOME/sizuhiko/Fabricate : プラグイン本体
  • $HOME/sizuhiko/travis : FriendsOfCakeのtravis.gitクローン先
  • $HOME/sizuhiko/cakephp : CakePHP本体
  • $HOME/sizuhiko/cakephp/app/Plugin/$PLUGIN_NAME : プラグイン本体がコピーされる先

上記ディレクトリ構成ができた後で、composer で依存関係を解決します。COVERALLS用のカバレッジレポートも生成されるようにファイルが準備されます。

実行

script.shがPHPCSが1の場合はphpcsを、それ以外の場合はユニットテストを実行します。

実行が成功したら

after_success.shがCOVERALLSのカバレッジレポートを生成します。

そして現在

最初のスクリーンショットがFabricateの現在の状態です。すべて成功しています。

最後にREADMEにバッジを表示させる方法を紹介します。

Travis.ciのバッジを表示する

Travis.ciの画面にアクセスして

build:failingのように表示されている画像をクリックします。

ブランチを選択して、表示形式からマークダウンを選択したら、表示内容をREADME.mdに貼り付けます。

カバレッジのバッジを表示する

カバレッジはCOVERALLSというサービスを使って表示します。

サイトにアクセスして、サインアップはGithubのアカウントでできるの簡単です。

サインアップ後に、上部のメニュー表示からREPOSを選択して、

ADD REPOをクリックしてリポジトリを追加します。 その後、追加したリポジトリのページを表示します。

今度は表示される画像ではなく、GET BADGE URLというボタンをクリックします。このあたりのUIは統一されると嬉しいですね…

いろいろな記述形式のコードが表示されるのでマークダウンのコードを選択してREADME.mdに貼り付けます。

ダウンロード数、バージョンを表示する

ここからはCIとは関係ないのですが、よく見るバッジであるダウンロード数とバージョン番号も表示してみたいと思います。

Badge Poserというサイトにアクセスします。

ダウンロード数やバージョン番号は、Packagistで表示されている情報を画像に変換するので、リポジトリがPackagistに登録されていることが条件となります。 Show the markdown for your Badgesにダウンロード数を表示したいPackagistのパッケージ名を入力します。

表示形式からマークダウンを選択して、コードをREADME.mdに貼り付けます。

最後に

CakePHPのプラグインを継続的インテグレーションする方法は、実はとても簡単でした。 これを機に、まだプラグインを継続的インテグレーションしていないそこのあなたも、Travis.ciとCOVERALLSを使って継続的インテグレーションをしてみましょう。

CakePHP3 もくもく会#5 に参加して Model の新機能を試してきた

2013/6/16(月) に コワーキングスペース茅場町 Co-Edo(コエド) で行われた「CakePHP3 もくもく会 #5」に参加しました。

前回のイベントではBakeすることに成功したので、今回は大きく変更となったモデル周辺についてテストコードから把握してみる目標をたてました。

環境構築する(前回との差分)

3回目に参加した記事では

環境構築でも利用した Friends Of Cake のリポジトリにはCakePHPのアプリケーションスケルトンを作成する app-template というリポジトリがあります。 すでにCakePHP3用のスケルトンも(cake3ブランチとして)用意されているので、GitHubのREADMEに書いてあるとおりのコマンドでCakePHPのアプリケーション環境を構築します。

composer -sdev create-project friendsofcake/app-template app dev-cake3

のように記述したのですが、その後devが上がったところでこちらのスケルトンは最新追従していなかったので、CakePHPの公式スケルトンを使っています。

composer create-project -s dev cakephp/app dev-cake3

CakePHP3におけるモデル

CakePHP3では従来モデル(Model)と呼んでいたクラスはなくなり、テーブル(Table)とエンティティ(Entity)という2つのクラスに別れます。従来のモデルがテーブルに変更になりfindなどの責務を持っています。find結果は従来arrayで返却されてきたのですが、CakePHP3からはエンティティと呼ばれるオブジェクトで返却されます。 CakePHP1から2にかけてbasukeさんが作られたCakeEntityというプラグインがあります。このプラグインはCakePHP3の新しいモデルのベースになったものでもあり、利用したことがあればそれほど違和感なく移行できるのではないかと思います。

Bakeされた結果を見てみる

前回bakeコマンドで生成したモデル層のファイルを見てみると

App/Model/Table/PostsTable.php

<?php
namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Validation\Validator;

/**
 * Posts Model
 */
class PostsTable extends Table {

/**
 * Initialize method
 *
 * @param array $config The configuration for the Table.
 * @return void
 */
    public function initialize(array $config) {
        $this->table('posts');
        $this->displayField('title');
        $this->primaryKey(['id']);
        $this->addBehavior('Timestamp');

    }

/**
 * Default validation rules.
 *
 * @param \Cake\Validation\Validator $validator
 * @return \Cake\Validation\Validator
 */
    public function validationDefault(Validator $validator) {
        $validator
            ->add('id', 'valid', ['rule' => 'numeric'])
            ->allowEmpty('id', 'create')
            ->allowEmpty('title')
            ->allowEmpty('body');

        return $validator;
    }

}

App/Model/Entity/Post.php

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

/**
 * Post Entity.
 */
class Post extends Entity {

/**
 * Fields that can be mass assigned using newEntity() or patchEntity().
 *
 * @var array
 */
    protected $_accessible = [
        'title' => true,
        'body' => true,
    ];

}

のようになっています。

PostsTable::initialize()は従来publicプロパティとして定義していたテーブル名などの情報です。 validationDefault()は$validatesで定義していたバリデーション定義です。

Postクラスはエンティティで$accesibleに利用可能なフィールド名を列挙します。これはRailsでattraccessorを定義するとプロパティにアクセス可能になるのと同様の効果を持つようになると推測されますが、現時点では何も実装はされていないようです。

コアのテストコード見てみる

<?php
    public function testRewind() {
        $query = $this->table->find('all');
        $results = $query->all();
        $first = $second = [];
        foreach ($results as $result) {
            $first[] = $result;
        }
        foreach ($results as $result) {
            $second[] = $result;
        }
    }

とてもザックリしたところを切り抜きましたが、まぁそういうことです。 これを2系のコードで似せて書くと

<?php
    public function testRewind() {
        $results = $this->Model->find('all');
        $first = $second = [];
        foreach ($results as $result) {
            $first[] = $result;
        }
        foreach ($results as $result) {
            $second[] = $result;
        }
    }

えっ!行数3の方が増えてないか?!とかいうツッコミはいらないですよ!! なんか聞いている程変わってないですよね。

ではもう少し複雑な例を

<?php
    public function testFindAllNoFieldsAndNoHydration() {
        $table = new Table([
            'table' => 'users',
            'connection' => $this->connection,
        ]);
        $results = $table
            ->find('all')
            ->where(['id IN' => [1, 2]])
            ->order('id')
            ->hydrate(false)
            ->toArray();
        $expected = [
            [
                'id' => 1,
                'username' => 'mariano',
                'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO',
                'created' => new Carbon('2007-03-17 01:16:23'),
                'updated' => new Carbon('2007-03-17 01:18:31'),
            ],
            [
                'id' => 2,
                'username' => 'nate',
                'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO',
                'created' => new Carbon('2008-03-17 01:18:23'),
                'updated' => new Carbon('2008-03-17 01:20:31'),
            ],
        ];
        $this->assertEquals($expected, $results);
    }

どうもテーブルには findやwhere,orderなどのメソッドがあり、それをチェインさせてクエリを組み立てるようです。これも他のActiveRecord系の記述とほぼ変わらない形式になります。 hydrate(false)を指定するとエンティティでなく従来の配列形式で値を戻すことができるようになっています。 メソッドチェインの最後のtoArray()はエンティティを配列形式で取得するという意味ではありません。CakePHP3からはfindの戻りがイテレータになります(配列ではないので注意が必要)。なのでイテレータからすべて配列形式で取得するという意味になります。

ではいくつかそれ以外の例を見てみましょう。

<?php
        $query = $table->find('all')
            ->select(['id', 'username'])
            ->where(['created >=' => new Carbon('2010-01-22 00:00')])
            ->hydrate(false)
            ->order('id');

たとえば取得するカラムを絞り込むためにはselect()を利用するようです。従来はfieldsで指定していたカラム名の配列です。

<?php
        $query = $table->find('list', ['fields' => ['id', 'username']])
            ->hydrate(false)
            ->order('id');

とはいえ、従来っぽい書き方もできるようです。

<?php
        $results = $table->find('all')
            ->find('threaded')
            ->select(['id', 'parent_id', 'name'])

!!!! findがチェインの中で2回呼ばれています。

これがCakePHP3のモデルだ!

findが2回とはどういうことなのか、ソースを調べてみました。 まずTable::find()とは何者なのか

<?php
    public function find($type = 'all', $options = []) {
        $query = $this->query();
        $query->select();
        return $this->callFinder($type, $query, $options);
    }

どうもクエリオブジェクトを取得して、それにselect()をかけて….. callFinder って何ですか?を返すみたいです。

<?php
    public function callFinder($type, Query $query, array $options = []) {
        $query->applyOptions($options);
        $options = $query->getOptions();
        $finder = 'find' . ucfirst($type);          // ココ
        if (method_exists($this, $finder)) {    // とココに注目
            return $this->{$finder}($query, $options);
        }

        if ($this->_behaviors && $this->_behaviors->hasFinder($type)) {
            return $this->_behaviors->callFinder($type, [$query, $options]);
        }

        throw new \BadMethodCallException(
            sprintf('Unknown finder method "%s"', $type)
        );
    }

find(‘all’) と呼ぶとfindAllメソッドがあるかどうか(ビヘイビアも含めて)調べて、なければ例外になる。 ということはTableにfindAllとかあるの?

<?php
    public function findAll(Query $query, array $options) {
        return $query;
    }

あるよ!

そういうことか、これがCakePHP3のモデルか。

  • 条件やら何やらメソッドチェインである
  • 旧来の配列形式に変換できる
  • findXxxで独自の条件などをメソッドチェインに組み込める(今ココ)

独自の条件をメソッドチェインに組み込む

まずPostsテーブルにfindOrderIdDescというIDの降順で検索するメソッドを作成します。

<?php
    public function findOrderIdDesc(Query $query, array $options) {
        return $query->order(['id'=>'desc']);
    }

でそれを使うときは

<?php
    $this->Posts = TableRegistry::get('Posts', $config); 
    $posts = $this->Posts->find('all')->find('orderIdDesc')->all();

みたいに書けるわけです。 もちろんいくつチェインしても良いです。 例えば様々なテーブルで利用できるファインダ(findXxxメソッド)をtraitで共通化するもの面白そうです。

このように新しいモデルはかなり期待の持てる作りにもなっています。 CakePHP3もAlphaになりました。 Alphaを使ってみての感想などはまた書きたいと思います

Test the Web Forward Meetup (仮), Tokyo#2 に参加して Shadow DOM のテストを書いてきた

html5jテスト部の活動「Test the Web Forward Meetup (仮), Tokyo#2」に参加してきました。

当日は1回目の反省をふまえて以下の仕様について実施することとなっていました。

  • HTMLのルビ(のパーサ周り)
  • CSS Text Decoration
  • Shadow DOM

私は Shadow DOM を選択しました。理由はGoogle JapanのHayatoさんがSpecを書いているので、テストがマージされやすいんじゃないか?!という不純な動機と、仕様が難しいので参加者が少ないかな?という妄想からでした(ただ実際は一番人気でした!)。

そもそもこの活動は何なのか?というと、みんなでHTML5やCSS、API仕様のテストケースを書こうというミートアップですと書いてある通りです。 私たちWebに関わるデベロッパーであれば必ず利用するHTMLやCSS、ブラウザによって実装がマチマチでとか、IEがぁーとか言ってますよね。

なぜそうなるのか?

まずHTMLとかCSSはW3Cの仕様があってそれを各ブラウザベンダが実装するわけです(もちろん先に実装されているケースもあります)。ですが結果としてどのように見えるのか?という部分に関して言うと、特にこうなるべきという具体的な結果が書いてある訳ではありません。そうなると各ベンダは自分の実装は正しいと思ってリリースするのですが、それぞれバラバラだったという結果が起きるのです。 そのため、W3Cではテストの充実に力を入れているのですが、現時点ではリソース不足であったり、そもそもスペックの理解不足という自体が起こります。W3Cのスペックを読んだことがあると何となく想像できるかな、と思います。 そこでTest the Web Forwardという公式イベントや、今回のようなコミュニティ主導のミートアップでテストコードを充実させようという機会が増えてきています。

テストの準備をする

Test the Web Forwardのドキュメントを見れば、今時のWebデベロッパーであれば問題なく環境を構築することができます。 pythonのバージョンが2.7系だったり、pip install html5lib が必要なのに漏れていた(これについては今回のミートアップでPRが出されてマージされたはずです)り、ちょっと面倒なのですがまぁ問題ないでしょう。GitHub便利。そのうちVagrantファイルでも作ろうかなーと思っています。 ここまでは事前の準備としてやっておきました。

当日やったこと

W3Cのスペックを見ましょう。 当日はHayato Itoさんから仕様の説明を受けて進めるという流れだったのですが、それにしても仕様がむずい。 新しいテストケースはちょっと置いておき、既存のテストを流してみることにしました。すると1つエラーがあって、typoであることがわかったのでPull Request。これはすぐ取り込まれました。嬉しい!W3Cへの貢献ですよ!!

続けて Fail しているテストについて、スペシャリストの方に確認して修正方針を聞いたりして、テストを黙々と書いていきます。 1日作業でしたが、2つのテスト失敗を修正してタイムアップ。

テストのやり方

テストには

  • refテスト 期待値:新しい仕様で書いたものと同じになるように既存の仕様で書いたもの、実際:新しい仕様で書いたもの、をスクリーンショットで比較する
  • domテスト testharness.js という xUnit ベースのテスティングフレームワークが使える

2通りのやり方があって、Shadow DOMはtestharness.jsでDOMのテストをする段階でした。 testharness.jsはxUnit系なんですが、equals(:actual, :expected) の記法になっていて、キモい!という話をしたら、開発者がmozilla系の方でfire方面ではそういう記述になっているという話を(FireFoxのcontributorの方から)伺うことができました。 これもミートアップでこその体験です。

モダンなテストの書き方という意味では、サーバサイドスクリプトとか今時のJavaScriptのテスティングフレームワークの方がイケてる感じにできますが、これも歴史的な背景(DOMの細かい評価をするのにtestharness.jsが最適であるという理由)ということです。今後についてはわからないけど、各ブラウザベンダなどがテストするときに利用しているということで変更(例えばJasmineに変えるとか)には高い壁があるのかもしれないです。

最後に

W3Cの活動に貢献する作業は今後も続けて行きたいので、ミートアップなくてもテストコードを書き続けて行きたいとおもいました。 ただmetaデータの書き方とかちょっと特殊なところもあったので、新しくやる場合は既存のテストコードをいくつか参照して、書き方のテンプレート部分を把握した方が良いと思いました。

それと今回はW3Cのテストスペシャリストとして Gérard Talbot 氏が初来日ということで基調講演を聴く事ができました。2年間かかって作ったという最高傑作テストケースを見てスペックのテストとは!を感じれたのも良かった。早速 Ahem フォントもインストールしました。

ミートアップ終了後は懇親会もあったのですが、体調不良のため最後までは持たずに帰宅いたしました….

会場を提供いただいた Adobe さん(ノベルティまでいただき感謝!)、スポンサー各社様の協力でとても楽しいイベント(部活)でした。

CakePHP3 もくもく会#3 に参加して Bake してきた

2013/4/4(金) に コワーキングスペース茅場町 Co-Edo(コエド) で行われた「CakePHP3 もくもく会 #3」に参加しました。

CakePHP3もdev preview2、せっかくの機会なので初Cake3を体験しようということです。

環境構築する

イベントページからリンクされていたCo-Edo謹製Vagrantを使っても良かったのですが、私はCakePHPのコアデベロッパも参加するコミュニティ Friends Of CakeVagrant Chef を使いました。

事前に用意するものは

です。概ね最新版で大丈夫でしょう。 ダウンロード&インストールしたら、任意のディレクトリに先程のVagrant Chefをクローン(またはダウンロード)します。後は

cd vagrant-chef
vagrant up

を実行してしばらく待ちましょう。Chefでインストールされるものは Vagrant Chef のREADMEに書いてあるので、そちらを参照してください。

INFO: Chef Run complete in 541.542816 seconds
INFO: Running report handlers
INFO: Report handlers complete

なメッセージが出て、コンソールが戻ってきたらインストールの完了です。 動作確認として、http://192.168.13.37/ にアクセスしてみてください。

デフォルト画面が出たら環境構築の完了です。

CakePHP3をインストールする

Vagrant ChefにはCakePHPをインストールするレシピも入っているのですが、Friends Of CakeにCakePHPの環境を作るスケルトンコマンドが別途用意されているので、レシピの編集は必要ないです。 生成したVagrant環境にログインします。

vagrant ssh

ログイン直後

vagrant@precise64:/vagrant$ ls -la
total 20
drwxr-xr-x  1 vagrant vagrant  306 Apr 19 05:37 .
drwxr-xr-x 25 root    root    4096 Apr 19 05:37 ..
drwxr-xr-x  1 vagrant vagrant  102 Apr 19 05:37 app
drwxr-xr-x  1 vagrant vagrant  510 Apr 19 05:30 cookbooks
drwxr-xr-x  1 vagrant vagrant  442 Apr 19 05:30 .git
-rw-r--r--  1 vagrant vagrant   12 Apr 19 05:30 .gitignore
-rw-r--r--  1 vagrant vagrant 5489 Apr 19 05:30 README.markdown
drwxr-xr-x  1 vagrant vagrant  102 Apr 19 05:31 .vagrant
-rw-r--r--  1 vagrant vagrant 1179 Apr 19 05:30 Vagrantfile

のようになっていて、すでにappディレクトリが存在します。ここには先程のデフォルト画面が入っているのですが、今回は新しくアプリを作成するのでappディレクトリは削除します。

rm -rf app

環境構築でも利用した Friends Of Cake のリポジトリにはCakePHPのアプリケーションスケルトンを作成する app-template というリポジトリがあります。 すでにCakePHP3用のスケルトンも(cake3ブランチとして)用意されているので、GitHubのREADMEに書いてあるとおりのコマンドでCakePHPのアプリケーション環境を構築します。

composer -sdev create-project friendsofcake/app-template app dev-cake3

app の部分がアプリケーション名です。Vagrant Chefを使った場合はappを指定してください。

Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? 

最後に上記のような確認メッセージが出てくるので、履歴が気にならなければそのままEnterを押して終了します。 インストールの確認として、http://192.168.13.37/ にアクセスしてみてください。

おなじみのCakePHP画面が表示されます。tmpディレクトリが書き込みできないと警告されます。これはwebサーバのデフォルトuserがvagrantでなくwww-dataであるためですが、開発環境用なの気にせず

sudo chmod -R 777 app/tmp

などのコマンドで権限を書き換えてください。

データベースを設定する

vagrant-chefを使うとデフォルトで databasename, testdatabase_name というデータベースが生成されています。 ただ文字コードの指定などの問題もあると思うので、ここは自分でデータベースを作成します。

mysql -u root -p

vagrant-chefのMySQL rootパスワードは、vagrant-chefのREADMEを参照ください(現時点ではbananasです)。

mysql> CREATE DATABASE cake3_mokumoku CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
mysql> CREATE DATABASE test_cake3_mokumoku CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;

続けておなじみのpostsテーブルを作成します。

mysql> use cake3_mokumoku;
mysql> CREATE TABLE `posts` (
          `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
          `title` varchar(50) DEFAULT NULL,
          `body` text,
          `created` datetime DEFAULT NULL,
          `modified` datetime DEFAULT NULL,
          PRIMARY KEY (`id`)
        ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

app-templateで作成したCakePHPアプリケーションは、php-dotenvというライブラリを使って環境設定できるようになっています。 app/App/Config/.env.default ファイルをコピーして app/App/Config/.envファイルを作成します。

// app/App/Config/.env
export DATABASE_URL="mysql://my_app:secret@localhost/my_app?encoding=utf8"
export DATABASE_TEST_URL="mysql://my_app:secret@localhost/test_myapp?encoding=utf8"

の部分を

// mysql://ユーザ名:パスワード@URL/データベース名
export CAKEDATABASE_URL="mysql://root:bananas@localhost/cake3_mokumoku?encoding=utf8"
export CAKEDATABASE_TEST_URL="mysql://root:bananas@localhost/test_cake3_mokumoku?encoding=utf8"

のように書き換えます。データベースの詳しい設定方法は AD7six/php-dsnを参照ください。 exportの名前を変更しているのは、nginxのfastcgiパラメータとしてvagrantインストール時に

fastcgi_param DATABASE_URL          "mysql://root:<%= node[:mysql][:server_root_password] %>@localhost/database_name?encoding=utf8";
fastcgi_param DATABASE_TEST_URL     "mysql://root:<%= node[:mysql][:server_root_password] %>@localhost/test_database_name?encoding=utf8";

のような指定がされているため、設定値が重複してしまいfastcgi側が優先される仕組みとなっています。 このため異なる名前に変更しておきます。 合わせて、app.phpでDATABASEURLを利用している箇所もCAKEDATABASEURLに変更します。

// app/Config/app.php#L211-
/**
 * Connection information used by the ORM to connect
 * to your application's datastores.
 */
    'Datasources' => [
        'default' => DbDsn::parse(env('CAKEDATABASE_URL')),

        /**
         * The test connection is used during the test suite.
         */
        'test' => DbDsn::parse(env('CAKEDATABASE_TEST_URL'))
    ],

Cakeシェルを利用する

コンソールから

cd app/App
Console/cake -h

とヘルプを表示してみたいのですが、もし

Exception: Shell class for "-working" could not be found. in [/vagrant/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php, line 178]

というエラーが表示されたら、app/App/Console/cake を編集して

exec php -q "$CONSOLE"/cake.php -working "$APP" "$@"

の行を

exec php -q "$CONSOLE"/cake.php "$@"

のように変更してください。現時点では -working オプションがあるとうまく動作しないためです。 うまく動作した場合は

$ Console/cake -h

Welcome to CakePHP v3.0.0-dev2 Console
---------------------------------------------------------------
App : App
Path: /vagrant/app/App/
---------------------------------------------------------------
Current Paths:

* app: App
* root: /vagrant/app
* core: /vagrant/app/vendor/cakephp/cakephp

Available Shells:

[CORE] bake, i18n, server, test

[app] console

To run an app or core command, type cake shell_name [args]
To run a plugin command, type cake Plugin.shell_name [args]
To get help on a specific command, type cake shell_name --help

のように表示されるはずです。

Bakeする

では早速Bakeしましょう

$ Console/cake bake model Posts

Welcome to CakePHP v3.0.0-dev2 Console
---------------------------------------------------------------
App : App
Path: /vagrant/app/App/
---------------------------------------------------------------
One moment while associations are detected.

Baking table class for Posts...

Creating file /vagrant/app/App/Model/Table/PostsTable.php
Wrote `/vagrant/app/App/Model/Table/PostsTable.php`

Baking entity class for Post...

Creating file /vagrant/app/App/Model/Entity/Post.php
Wrote `/vagrant/app/App/Model/Entity/Post.php`

Baking test fixture for Posts...

Creating file /vagrant/app/Test/Fixture/PostFixture.php
Wrote `/vagrant/app/Test/Fixture/PostFixture.php`

Baking test case for App\Model\Table\PostsTable ...

Creating file /vagrant/app/Test/TestCase/Model/Table/PostsTableTest.php
Wrote `/vagrant/app/Test/TestCase/Model/Table/PostsTableTest.php`

うまく動きます!!

続けてコントローラとビューも

$ Console/cake bake controller Posts
$ Console/cake bake view Posts

で生成します。

一通りbakeしたら、http://192.168.13.37/posts/ にアクセスしてみます。

おなじみのBake画面が表示されます。追加、変更、削除などもうまく動作するようです(執筆時においては)。

最後に

このブログはもくもく会から2週間ぐらい経過してしまいましたが、今回再度新しい環境を作って試しています。 もくもく会のときも同様の手順で進めていたのですが、いくらかbake周辺でコードの変更があったようで、うまく動いていた箇所が動かなくなっていたり、動かなかった箇所が動いていたりします。 開発者用のプレビュー2ということで、今後もコードは変わって行くと思いますが、Bakeが動く事で取り急ぎ動作するプログラムのベースは作れるようになっています。 実際にアプリケーションを作ってみてCakePHP3の新機能について感想を書いてみたり、要望を出してみたりすることも実リリースまでの期間としては大切なことだと思うので、今回簡単ではありますがブログとして公開してみました。

Co-Edo では今後も CakePHP3もくもく会が開かれると思うので、イベントページをチェックしておくと良いと思います。私も可能な範囲で参加していこうと思っています。

Composerのautoloadを使いこなす

Composerにはautoloadを自動生成する機能があり、これを利用するとrequire_onceなどを使わなくとも自動的にソースコードがロードされます。言葉のとおりautoloadですね。

例えばCakePHPではApp::uses()という記述で利用するクラスがどこにあるのか識別して、クラスをロードできるようにするのですが、これを使わなくても Composer のautoload機能を使うとクラスが利用可能になります。

app/composer.json に以下のような定義を記述してみましょう。

{
    "autoload": {
        "classmap": ["Model", "Controller"]
    },
}

ここで autoloadだけ を更新するコマンドを実行します。

composer dumpautoload

コマンド名からはちょっと想像がつきにくいのですが、これを実行すると Generating autoload files という結果が表示されてautoload定義が更新されます。

// app/Vendor/composer/autoload_classmap.php
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'AppController' => $baseDir . '/Controller/AppController.php',
    'AppModel' => $baseDir . '/Model/AppModel.php',
    'PagesController' => $baseDir . '/Controller/PagesController.php',
    'Post' => $baseDir . '/Model/Post.php',
    'PostsController' => $baseDir . '/Controller/PostsController.php',
);

今回は予めpostsテーブルからbakeしてPostモデルとPostsコントローラを生成しておきました。 こうすると、App::uses()を記述しなくても、composerのautoloadがクラス名からPHPファイルを自動解決してくれます。App::uses()の記述が多過ぎてコードの見通しが悪い場合などに利用を検討されてはいかがでしょうか?

この他にもComposerのautoload機能は

  • PSR-4
  • PSR-0
  • Files(ファイルを直接指定できる)
  • include-path(どうしてもphp.ini的なincludeパスで解決しなくてはならない時のためにある)

といったパス解決方法を用意していますので、namespaceが入ったものなどのサポートも万全です。

詳しくは Composerの公式ドキュメントのautoloadを参照ください。