CakeFest2015に参加してきた
Sunday, June 14, 2015 01:44:00 PM
今年はCakePHP生誕10周年ということもあり、かねてよりアニバーサリーCakeFestとも言われていました。
開催場所は世界一の都市ニューヨークです。 私個人でも初のニューヨークです。
今年の第一印象
初参加が多い。CakeFestでは毎年会う馴染みの人もいるのですが、今年は特に初参加が多かったようです。 参加者が多い。さすが10周年というべきか、ニューヨークなのでアメリカの人が多かったようです。
印象に残ったセッション
昨年までと同様に、CakePHPに関する主なセッションはコアチームから、その他Webに関するトピックをCFPで通ったスピーカーからという流れでした。 今年はコミュニティマネージャのJamesが急に来れなくなってしまい、イベントの仕切りに不安を覚えるスタートだったのですが、Larryが八面六臂の働きで素晴らしいCakeFestになりました。LarryがLTのタイムキーパーやったり、抽選会の司会やったり、それはそれは大活躍でした。
コアチームの中でも
- Mark Storyのセッション CakePHP 3.0.0 and beyond (Conference)
- Jose Lorenzo Rodriguezのセッション Agile Database Access with CakePHP 3 (Conference)
についてはCakePHPを利用する人には一読を薦める内容です。
Mark Storyのセッション
とは言えMarkのスライドはいつもの通りお題しか書いていないので、少し詳細にふれておきます。
- CakePHP3の開発期間3年はとても長かった
- 周辺の変化
- PHPのバージョンも変わったし、機能も変わった
- 3年前のPHPは5.4.4(Released: 14 June 2012)あたりです。
- 現在は5.6.9で、その間にはジェネレータや可変引数などが導入されました。
- 他のフレームワークの流れも変わった
- PHPのバージョンも変わったし、機能も変わった
- CakePHP3は2ヶ月で8万ダウンロードされた。すごいね。
- 今後のバージョンの話(詳しくはgithubのロードマップを見てね)
- PSR-7対応は3.2の予定
- 大きく変わったところ
- Mailers:メール送信がこれまでよりも簡単になる
- CLI:出力フォーマットの機能が増える。例えばプログレスバーとか簡単に出せるようになる
- ORM:Joseが明日話すけど、関連のロードとかマッチングクエリが書けるようになるよ
- ElasticSearch:ORMと似た呼び出し方法が使えるようになる
- 今後注力していくところ
- プラグインのリリース
- 3.x系の開発
- これにより2系については3からのバックポートが中心となる
- 3.x内での互換性は重視している
- PHP7
Jose Lorenzo Rodriguezのセッション
Joseのセッションはコード多めで、解説だけではわかりづらいと思うのでサンプルコードが提供されています。
スライドの最初に出てくる3つのタイプのORM
- いらっとする
- おもちゃみたいな
- 流行の(通の)
- すばらしい
もちろんCakePHP3のORMはすばらしい
ものだよ。という流れで掴みはOKな展開。ちなみにQ&AでDoctrineは?という質問に「Hipster」と即座に回答していました。
ちなみに日本だとAgileはアジャイルと発音するけど、Joseのセッションではずっとエイジールと聞こえていた。
スライドやサンプルを見てもらえると、CakePHP3のORMがかなり強力になっているのがわかると思います。 特に気になったのは
- タイプヒント
- カスタムファインダー
- 集合検索
- 多重階層の集合検索
- 他のDBへのアソシエーション定義
- バーチャルフィールド
あたりです。サンプルを使って実際に動かしてみるのが良いですね。まだ全部試せていないけど、CakePHP3もくもく会で少しずつ試せたら良いなーと思っています。
10周年
ノベルティが久々にたくさんあった。きっとシカゴ以来。
Swag at #CakeFest2015 pic.twitter.com/gVbDL5ctPl
— CakePHP (@cakephp) 2015, 5月 31
で、私はそのシカゴから参加して6回目の参加となりました。 最後の抽選会でシカゴに参加した人、というときに私含め3人しか立たなかった(コアチームは除く)。さらにそのうち1人はマリアーノです。
アニバーサリーということで最後の目玉商品は来年のCakeFest招待券!!。なんと交通費込み(上限あるけど)。 当選者は
The winners of free tickets to #CakeFest2016 pic.twitter.com/FvB3olZCgN
— CakePHP (@cakephp) 2015, 6月 1
奥山さん!!!、来年はVIP待遇ですなー。来年はドイツらしいですよ。
感謝
10周年ということで、ここ数年は安藤さんと私の2人だったのを、もっと多くのCakePHPユーザにCakeFestに参加して欲しいと思い、様々なところでくどいぐらい誘いました(ご迷惑だった皆様すみません)。結果、私含め5人が日本から参加ということで、とても楽しかったです。
安藤さんの紹介で、pivotal lab.にも訪問できたし、ピザ屋さんも(すごい)美味しかったです。
また、日本でCakeRadioGaGaの中継を受け取ってくれたCo-Edoの田中さん、中継を見てくれた皆様、ありがとうございました。 別途報告会(もくもく会の中で?)、できたら良いなーと思っています。
そのほかGoogle社の前で写真撮ったり、チェルシーマーケットでおみやげ買ったり、MOMA行ったり、ちゃんとニューヨークも楽しむことができました。
最後に、CakeRadioGaGaの中継動画を。そういえばMarkとJoseに今年もメッセージちょうだいと言っていたのに、すっかり忘れたのは内緒です…
CakePHP3 のアプリケーションを Behat でテストする
Sunday, May 17, 2015 05:21:00 PM
CakePHP3の変更点として大きく取り上げられるのが、モデル層の変更でしょう。 しかしそれ以上に私たちが受けられる恩恵で大きいのが、PSR-2の採択です。
例えばCakePHP2で単体テストを実行するときは、以下のようにcake
コマンドを使って実行する必要がありました。
Console/cake test app Model/Articles
cakeコマンド内でPHPUnitへの依存関係を解決し、PHPUnitからCakePHPのクラスが参照可能になるように作られていました。
ところがCakePHP3からは、以下のようにphpunit
コマンドを使って実行します。
vendor/bin/phpunit
PSR-0のオートロードに対応したことで、PHPUnitからCakePHP3のクラスが参照可能になるのです。
はじめてみよう
同様の理由で、CakePHP2のアプリケーションをBehatでテストしたい場合は、私が作成したBdd Pluginを使ってBehatのステップ記述からCakePHPのクラスを参照可能になるようにしていました。
しかしBehatにおいても直接実行したステップ定義から、CakePHP3のクラスが参照可能になるのです。
CakePHP3のアプリケーションをどのようにBehatからアクセスするのか、CakePHP3のブログチュートリアルを例にサンプルアプリを作成しました。
サンプルアプリケーションは以前記事にもしたCakeboxを使って構築しました。 またサンプルアプリケーションの実行にもCakeboxを使うと簡単に実行環境を構築することができます。
サンプルアプリケーションのGithubページに書いてあるとおりの手順で進むことができます。 本ブログでは日本語で補足します。
必要なアプリケーションのインストール
以下のアプリケーションをホストOSにインストールします。
- VirtualBox
- Vagrant
- Cakebox
詳しくはCakebox を使ってCakePHP3アプリケーションを作ってみようの記事を参照してください。
サンプルアプリケーションのインストール
CakeboxのゲストOSにログインして、cakeboxコマンドでアプリケーションをインストールします。
localhost:cakebox $ vagrant ssh
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-24-generic x86_64)
vagrant@cakebox $ cakebox application add blog-tutorial.app --source https://github.com/sizuhiko/cakephp3-bdd-example.git --webroot blog-tutorial.app
すると、以下のように表示されます。
Creating application http://blog-tutorial.app
Configuring installer
Creating installation directory
Git installing user specified application sources
Creating virtual host
* Successfully created PHP-FPM virtual host
Creating databases
* Successfully created main database
* Successfully created test database
Configuring permissions
Updating configuration files
Application created using:
database => blog-tutorial_app
framework_human => user specified
framework_short => custom
installation_method => git
path => /home/vagrant/Apps/blog-tutorial.app
source => https://github.com/sizuhiko/cakephp3-bdd-example.git
url => blog-tutorial.app
webroot => blog-tutorial.app
Please note:
=> Configuration files are not automatically updated for user specified applications.
=> Make sure to manually update your database credentials, plugins, etc.
Remember to update your hosts file with: 10.33.10.10 http://blog-tutorial.app
Installation completed successfully
新規アプリケーションの構築と同じように、データベースやNginxの設定ファイルも生成してくれるので、すぐにアプリケーションを実行できる環境が整います。
あとはアプリケーションのルートディレクトリに移動して、不足しているディレクトリを作ってcomposerでライブラリをインストールします。
vagrant@cakebox $ cd Apps/blog-tutorial.app
vagrant@cakebox:~/Apps/blog-tutorial.app$ mkdir tmp
vagrant@cakebox:~/Apps/blog-tutorial.app$ mkdir logs
vagrant@cakebox:~/Apps/blog-tutorial.app$ cp config/app.default.php config/app.php
vagrant@cakebox:~/Apps/blog-tutorial.app$ composer install
サンプルアプリケーションの環境設定
データベース接続設定の変更
config/app.php
のデータベース接続設定をCakeboxで生成された内容に変更します。
以下のとおりusername
とdatabase
の部分のみ変更します(それ以外はそのまま)。
'Datasources' => [
'default' => [
// 省略
'username' => 'cakebox',
'database' => 'blog-tutorial_app',
// 省略
],
'test' => [
// 省略
'username' => 'cakebox',
'database' => 'test_blog-tutorial_app',
// 省略
],
ホストOSのhostsファイルの変更
ホストOSのhostsファイルに指示されたように 10.33.10.10 blog-tutorial.app
の行を追加します。
Cakebox環境のチューニング
Cakeboxのデフォルト設定ではBehatを使ってアプリケーションをテストしようとすると、いくつか動かない箇所があったので、設定値をチューニングします。
まずボックスファイルのメモリを2048Mにアップします(デフォルトは1024M)。
次にxdebug.iniのxdebug.maxnestinglevelの値を調整します。READMEでは500
を設定しています。もう少し値は小さくても大丈夫かもしれないですが、とりあえず500あれば大丈夫です。
具体的な設定例は、githubのREADMEを参照してください。
Webサーバの設定
Behatからアプリケーションをテストするときは、ブラウザから通常操作するのと同じようにWebサーバを通過します。 そのため、アプリケーションが通常操作としてアクセスされたのか、Behatのテストでアクセスされたのかを識別して環境を切り替えてあげないと、データベースのデータがテストによって変更するので、通常操作のデータが失われてしまいます。
このあたりの話(理由や手法)は、過去に何度か記事にしていたり、書籍CakePHPで学ぶ継続的インテグレーションでも詳しく解説していますので、CakePHP2の内容ですが、一度手に取ってみてください。
で、このサンプルはnginx用の設定ファイルをblog-tutorial.app.test
というファイルで用意しておいたので、これをCakeboxのnginxの設定ディレクトリにコピーして再起動するだけで大丈夫です。
環境切り替え用にblog-tutorial.app.test
というホスト名でアクセスされたら、nginxで環境変数CAKE_ENV
にtest
という文字列を設定するようにしています。
CakePHP3のアプリケーションではconfig/bootstrap.php
で環境変数の設定値を見てDBの接続先がtest
になるように設定します。
if (getenv('CAKE_ENV') === 'test') {
ConnectionManager::alias('test', 'default');
}
CakePHP3ではConnectionManagerのaliasという機能でdefaultへ接続しようとしたときに、実際はtestの接続内容を参照するように設定することができるので、この機能を利用し、間違ってdefaultのテーブルが書き変わらないようにしています。
より詳しい手順や、設定ファイルの内容はgithubのREADMEや設定ファイルを参照してください。
Behatから参照可能なホスト名としてblog-tutorial.app.test
をCakeboxのVM側の/etc/hosts
に追加します。
データベースのマイグレーション
データベースの生成はマイグレーションコマンドで一発です。
bin/cake migrations migrate
Behatのテストを実行する
ここまで設定できれば、後はテストを実行するだけです。
vagrant@cakebox:~/Apps/blog-tutorial.app$ vendor/bin/behat
おそらくすべてグリーンで成功するはずです。
うまくいかなかったら、お気軽にgithubのissueに日本語で
書いてください。
どうやったのか?
まずCakePHP3で最初に注目したのは、単体テストがPHPUnitのコマンドから実行できるようになっていたことです。 これは過去に外部の様々なツールやアプリケーションとCakePHPを結合するときに一番悩んでいたところでした。
PHPUnitからCakePHP3にどのように連動しているのか?を調べることから始めました。 PHPUnitは実行すると、カレントディレクトリのphpunit.xml(もしくはphpunit.xml.dist)を参照します。
PHPUnitがCakePHP3を呼び出す仕組みを知る
CakePHP3ではアプリケーションスケルトンを生成すると、ルートディレクトリにphpunit.xml.dist
が生成されます。
<!-- phpunit.xml.dist -->
<phpunit
colors="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/bootstrap.php" // (1)
>
<php>
<ini name="memory_limit" value="-1"/>
<ini name="apc.enable_cli" value="1"/>
</php>
<!-- Add any additional test suites you want to run here -->
<testsuites>
<testsuite name="App Test Suite">
<directory>./tests/TestCase</directory>
</testsuite>
<!-- Add plugin test suites here. -->
</testsuites>
<!-- Setup a listener for fixtures (2) -->
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector"
file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager" />
</arguments>
</listener>
</listeners>
</phpunit>
このファイルを読むと、2つ重要な箇所があるのに気がつきます。
まず(1)の bootstrap="./tests/bootstrap.php"
という部分。
bootstrap属性にはPHPUnitが実行されるとき呼び出されるPHPコードを指定することができます。
ここからCakePHP3アプリケーションをテスト用にロードする場合、このファイルを呼び出せば外部ツールからCakePHP3が操作できるようになることがわかります。
実はこのファイルを実際に見てみると、以下の1行しかありません。
require dirname(__DIR__) . '/config/bootstrap.php';
テストとは関係なく、アプリケーションのconfig/bootstrap.php
をロードしています。
おそらく将来テストに関する何か差分が必要になったときに、テスト側にだけ変更が発生すると思うので、テスト用にCakePHP3をロードする場合はtests/bootstrap.php
をロードしておいた方が良いでしょう。
次に(2)のリスナー設定です。
PHPUnitのリスナーはPHPUnitのフックポイントでコールバックされる処理を記述できるクラスです。
CakePHP3ではフィクスチャ(DBのテストテーブルとデータを準備する仕組み)を投入するのに利用しています。
以下のようにFixtureInjector
クラスのstartTestとendTestでテストケース開始/終了ごとにフィクスチャのロードとアンロードが対応するようになっています。
class FixtureInjector implements PHPUnit_Framework_TestListener
{
/**
* Adds fixtures to a test case when it starts.
*
* @param \PHPUnit_Framework_Test $test The test case
* @return void
*/
public function startTest(PHPUnit_Framework_Test $test)
{
$test->fixtureManager = $this->_fixtureManager;
if ($test instanceof TestCase) {
$this->_fixtureManager->fixturize($test);
$this->_fixtureManager->load($test);
}
}
/**
* Unloads fixtures from the test case.
*
* @param \PHPUnit_Framework_Test $test The test case
* @param float $time current time
* @return void
*/
public function endTest(PHPUnit_Framework_Test $test, $time)
{
if ($test instanceof TestCase) {
$this->_fixtureManager->unload($test);
}
}
}
BehatからCakePHP3を呼び出す仕組みに流用する
ここまでの内容が外部ツールからCakePHP3のアプリケーションをテストするのに重要な部分です。 PHPUnitがCakePHP3を呼び出すのと同じようにする仕組みをBehatのFeatureContextクラスに用意します。
features/bootstrap/FeatureContext.php
というBehatが読み込むファイルに記述します。
CakePHP2とBDDプラグインによるインテグレーションではBehatのバージョンが2系でしたが、CakePHP3との連携では最新の3系を利用しています。
Behat3からはBehat1系、2系で利用していたファイル構成と異なっています。従来、support/bootstrap.php
やsupport/hooks.php
あたりに書いていたコードはすべてContextクラス内に記述することになります。
Behat3からはFeatureContextにブートストラップ記述を、それ以外のコンテキストは用途に応じて別のコンテキストクラスに分割する方がスマートに記述できそうです。
Behat2では複数のコンテキストクラスを使う場合、FeatureContextでインクルードしないといけなかったのですが、Behat3ではbehat.yml
上で記述できるのでより簡単になっています。
class FeatureContext implements Context, SnippetAcceptingContext
{
public function __construct()
{
require_once dirname(dirname(__DIR__)) . '/tests/bootstrap.php'; // (1)
// Always connect test database
ConnectionManager::alias('test', 'default'); // (2)
Fabricate::config(function($config) { // (3)
$config->adaptor = new CakeFabricateAdaptor([
CakeFabricateAdaptor::OPTION_FILTER_KEY => true,
CakeFabricateAdaptor::OPTION_VALIDATE => false
]);
});
$this->fixtureInjector = new FixtureInjector(new FixtureManager()); //(4)
$this->fixture = new BddAllFixture();
}
}
- (1)は、phpunit.xmlのbootstrapと同様にCakePHP3の
tests/bootstrap.php
を呼び出します。 - (2)は、Behatのステップ定義からテストデータを投入するときに、testの接続設定を参照するようにエイリアスを設定します。
- (3)は、テストデータジェネレータFabricateの初期設定です。FabricateもCakePHP3対応されています。
- (4)は、phpunit.xmlのリスナー部分を模して、Behatのシナリオ毎にフィクスチャが動くようにFixtureInjectorのインスタンスを生成しています。
BehatからCakePHP3のフィクスチャを利用する
(4)で書いたとおり、FixtureInjectorのインスタンスを生成したので、Behatのフックポイントを使ってシナリオ開始時にフィクスチャをロードし、シナリオ終了時にフィクスチャをアンロードするようにします。
/** @BeforeScenario */
public function beforeScenario(BeforeScenarioScope $scope)
{
$this->fixtureInjector->startTest($this->fixture);
}
/** @AfterScenario */
public function afterScenario(AfterScenarioScope $scope)
{
$this->fixtureInjector->endTest($this->fixture, time());
}
実際にフィクスチャを利用するためには、$this->fixture
のクラスがCakePHP3のTestCaseでなければならないので、$fixtures
という利用するフィクスチャファイルの配列を定義しただけのクラスを用意してFixtureInjectorに渡すようにします。
class BddAllFixture extends TestCase {
public $fixtures = [
'Categories' => 'app.categories',
'Articles' => 'app.articles',
'Users' => 'app.users',
'Categories' => 'app.categories'
];
}
このあたりの話も、書籍CakePHPで学ぶ継続的インテグレーションでも詳しく解説していますので、CakePHP2の内容ですが、一度手に取ってみてください。 CakePHP3になって、メソッドやクラスが一部変わりましたが、BehatとCakePHPをインテグレーションするためにおさえておかないといけないポイントはほとんど変わっていません。
後はBehat3のドキュメント、CakePHP3のドキュメントを見ながら進めていくと、エンド to エンドのテストが容易に記述できるようになります。
さいごに
GithubのREADMEに書いた内容をすべて日本語にした訳ではないのですが、要所をかいつまんで重要な部分を解説しました。 より詳しい内容などはREADMEを見ていただければと思います。
また、BDDプラグインのサンプルアプリにはあった、日本語のシナリオや、JavaScriptを使ったテストなど、Behat3になって大きく変わってはいませんが、サンプルアプリケーションに少しずつ載せられたらなぁと思っています。 何かうまく動かないなどあれば、気軽にGithubのissueに投稿お願いします(日本語でOKです)。
AngularJSでngDialog中の値をngModelでバインドしたいとき注意すること
Saturday, May 02, 2015 05:19:00 PM
AngularJS でモーダルダイアログを表示するために、何を使うでしょうか? 多くの場合 ngDialog というコンポーネントを使うのではないかと思います。
で、ダイアログ上の値は、それを表示したコントローラのスコープにバインドする、という良くあるシナリオを想定してください。
まずうまく動作するサンプルを紹介します。
Open Dialog
というリンクをクリックして、ダイアログを表示したら、チェックボックスをON/OFFしてください。
ダイアログ背景のページで check: true
と check: false
がトグルするはずです。
<div data-ng-app="myApplication">
<div data-ng-controller="MainController">
<a href="" ng-click="ShowNgDialog()">Open Dialog</a>
<span>check: {{FormData.allcheck}}</span>
</div>
</div>
var myApplication = angular.module('myApplication', ['ngDialog']);
myApplication.controller('MainController', function ($scope, ngDialog) {
$scope.FormData={allcheck: false};
$scope.ShowNgDialog = function () {
ngDialog.open({
template: '<div><input type="checkbox" ng-model="FormData.allcheck"/></div>',
plain: true,
scope:$scope
});
}
});
とても簡単な例ですが、AngularJSを使ってモーダルダイアログを表示して、チェックボックスの値をコントローラのスコープ変数 FormData.allcheck
にバインドしています。
なぜか変数だとバインドされない
一方で、こちらは動作しないサンプルです。
Open Dialog
というリンクをクリックして、ダイアログを表示したら、チェックボックスをON/OFFしてください。
ダイアログ背景のページは check: false
のままです。
<div data-ng-app="myApplication">
<div data-ng-controller="MainController">
<a href="" ng-click="ShowNgDialog()">Open Dialog</a>
<span>check: {{allcheck}}</span>
</div>
</div>
var myApplication = angular.module('myApplication', ['ngDialog']);
myApplication.controller('MainController', function ($scope, ngDialog) {
$scope.allcheck = false;
$scope.ShowNgDialog = function () {
ngDialog.open({
template: '<div><input type="checkbox" ng-model="allcheck"/></div>',
plain: true,
scope:$scope
});
}
});
変わったのは、コントローラのスコープ変数にバインドするオブジェクトです。
うまく動作するのは $scope.FormData={allcheck: false};
のようにスコープのプロパティはオブジェクトで、オブジェクトに値を保持しているケースです。
一方うまく動作しないのは $scope.allcheck = false;
のようにスコープのプロパティに変数で値を保持しているケースです。
ngDialogでなければ変数でバインドできる
ngDialogでなく普通に表示される範囲にある場合は、動作するサンプルです。
チェックボックスをON/OFFしてください。ページで check: true
と check: false
がトグルするはずです。
<div data-ng-app="myApplication">
<div data-ng-controller="MainController">
<div><input type="checkbox" ng-model="allcheck"/></div>
<span>check: {{allcheck}}</span>
</div>
</div>
var myApplication = angular.module('myApplication', ['ngDialog']);
myApplication.controller('MainController', function ($scope, ngDialog) {
$scope.allcheck = false;
});
まとめ
ngDialogを使うときのちょっとした小ネタなのですが、解決策を見つけるまで結構時間がかかりました。 もし、ngDialogを使ってうまくデータバィンディングできない!という人の参考になればと思います。
gulp-rev-replace を使ってリビジョン管理をするときに注意したいこと
Saturday, May 02, 2015 03:51:00 PM
JavaScriptでモダンな開発をするとき、gulpというビルドシステムを使うと、簡単にビルド過程を自動化できます。 さらに、JavaScriptでアプリケーションを作ったとき、ライブラリはCDNなどから取得するとして、自分で作ったスクリプトファイルは1つのファイルにまとめてミニファイズする、ということをgulpのタスクで書くでしょう。 一般的にJavaScriptやCSSをHTML上に記述するとき、以下のようにします。
<script type="text/javascript" src="/js/app.js"></script>
<link rel="stylesheet" href="/css/app.css" type="text/css">
リリースしたのにファイルの変更が読み込まれない
良くあるシーンとして、JavaScriptファイルやCSSファイルを差し替えたのに、変更がブラウザに反映されない、というケースです。 これはブラウザのキャッシュが有効になっていて、JavaScriptやCSSのファイルをWebサーバへ取得しに行かないために発生します。
そこで、この課題に対応するため、以下のどちらかの方法を採用すると思います。
- ファイル名のGETパラメータに、乱数を付加して
/js/app.js?_リビジョン番号
のようにする - ファイル名にリビジョン番号を入れて
/js/app-リビジョン番号.js
のようにする
こうすると、リビジョンが変更になった(リリースした)ときにファイルが必ず読み込まれるようになります。
GETパラメータの付加は推奨されない
前記の対応のうち、GETパラメータにリビジョン番号を追加する方法はあまり推奨されません(参照:High Performance Web Sites
)。
この方法は、ブラウザやWebサーバがキャッシュを利用しないため、サイトの負荷につながります。
もちろん毎回リクエストが来ても問題ないサイトや、利用者が想定されていれば問題ないかもしれないですが、利用できるのであればキャッシュが有効になっていてページが速く表示できた方が良いことはいうまでもありません。
gulp-revを使ってGETパラメータにリビジョン番号を入れたい場合は、gulp-rev-appendを使うとクエリ文字列としてハッシュ値を入れられるようになります。
ビルド時にファイル名を変更する
推奨される方法は /js/app-リビジョン番号.js
のように、ファイル名を変更することです。
gulpのタスク上に、gulp-rev
のREADMEに書いてあるとおりの方法で対応します。
var gulp = require('gulp');
var rev = require('gulp-rev');
gulp.task('default', function () {
// by default, gulp would pick `assets/css` as the base,
// so we need to set it explicitly:
return gulp.src(['assets/css/*.css', 'assets/js/*.js'], {base: 'assets'})
.pipe(gulp.dest('build/assets')) // copy original assets to build dir
.pipe(rev())
.pipe(gulp.dest('build/assets')) // write rev'd assets to build dir
.pipe(rev.manifest())
.pipe(gulp.dest('build/assets')); // write manifest to build dir
});
プロジェクトによっては、このように単純な構成ではなく、複数のストリームを使ってビルドすることもあるでしょう。
gulp.task('build_js', function () {
return gulp.src('src/*.js')
.pipe(sourcemaps.init())
.pipe(concat({path: 'bundle.js', cwd: ''}))
.pipe(rev())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('dist'))
.pipe(rev.manifest())
.pipe(gulp.dest('dist'));
たとえばjsとcssを分けてビルドしなくてはいけないようなケースですね。
元ファイル名と、置き換えられたファイル名のマッピングを出力するために、rev.manifest()
というAPIを呼び出します。
出力先は、そのあとのdest
API呼び出しで指定します。ファイル名は省略時には manifest.json
というファイル名になります。
{
"app.css": "app-098f6bcd.css",
"app.js": "app-273c2cin.js"
}
ファイル名の変更を反映する
HTMLファイルのjsやcssのファイル名を書き換えるのに使うのが、gulp-rev-replaceです。 マニフェストファイルを入力として、HTMLファイルのビルド(コピー)過程で差し込むことができるようになっています。
gulp.task("revreplace", ["revision"], function(){
var manifest = gulp.src("./" + opt.distFolder + "/rev-manifest.json");
return gulp.src(opt.distFolder + "/index.html")
.pipe(revReplace({manifest: manifest}))
.pipe(gulp.dest(opt.distFolder));
});
revReplace()
というAPIを使って、指定したマニフェストの内容と一致する部分を置換します。
<script type="text/javascript" src="/js/app-273c2cin.js"></script>
<link rel="stylesheet" href="/css/app-098f6bcd.css" type="text/css">
とても便利、でも…
私が遭遇したケースで説明しましょう。
ビルド済みファイル名が domain.js
というファイル名でそれにリビジョン番号を追加する必要がありました。
さらにドメイン名のチェック用に、is-valid-domain.jsというライブラリも読み込んでいました。
<script type="text/javascript" src="/lib/is-valid-domain.js"></script>
<script type="text/javascript" src="/js/domain.js"></script>
ここでビルドしたところ
<script type="text/javascript" src="/lib/is-valid-domain-リビジョン番号.js"></script>
<script type="text/javascript" src="/js/domain-リビジョン番号.js"></script>
のようになってしまいました。domain.js
だけでなく、is-valid-domain.js
も変わってしまいます。
なぜこうなるか、ソースを見てみました。
renames.forEach(function replaceOnce(rename) {
contents = contents.split(rename.unreved).join(rename.reved);
if (options.prefix) {
contents = contents.split('/' + options.prefix).join(options.prefix + '/');
}
});
まぁですよね。ファイルを読み込んで domain.js
に一致するところで分割、domain-リビジョン番号.js
を追加して繰り返す、という実装です。
ファイルのどこに入っているか厳密に識別するのは困難(正規表現を使えばできなくはないかもしれないけど)です。
で、このようなケースにならなそうなら、そのまま gulp-rev-replace を使ってもらえば問題ないと思います。 ライブラリの挙動がわかっていれば利用するのも安心ですね。
私は gulp-template を使いました
で、私は gulp-rev-replace 使うのやめました。
ちょっと予期しない動作をするのは怖かったので、リビジョン番号が入って欲しいところを明示するようにしたかったのです。 そこで使ったのが gulp-template です。
<h1>Hello <%= name %></h1>
<%= =>
で囲んだ部分に値を差し込むことができるので、以下のように記述します。
<script type="text/javascript" src="/lib/is-valid-domain.js"></script>
<script type="text/javascript" src="/js/<%= data['domain.js'] %>"></script>
そこにマニフェストJSONをfs-extra
で読み込んで、template
APIに流し込むようにします。
そのままだと、変換前JSファイル名が変数名になって取り出しずらいので、variable
オプションを指定してdata
という変数名にバインドするようにします。
var gulp = require('gulp');
var template = require('gulp-template');
var fs = require('fs-extra');
gulp.task('build_html', function () {
var manifest = fs.readJsonSync('./' + opt.distFolder + '/rev-manifest.json', {throws: false})
return gulp.src('src/*.html')
.pipe(template(manifest, {variable: 'data'}))
.pipe(gulp.dest('dist'));
});
まとめ
静的ファイルのリビジョン管理って結構面倒なんですが、gulp使うと便利なライブラリあって簡単に実装できます。 今回は私が遭遇した特殊なケースかもしれないので、そのままgulp-revだけで完結できることも多々あるでしょう。 gulp-revや、その関連ライブラリには便利な機能がまだあるので、一度使ってみてください。
Cakebox を使ってCakePHP3アプリケーションを作ってみよう
Sunday, April 19, 2015 03:19:00 PM
CakePHP3の開発環境を構築するのは、以前にも書いたとおり FriendsOfCake/vagrant-chef を便利に使っていたのですが、先日 Twitter の TL に流れてきた Cakebox というのが気になっていたので、使ってみました。
結論としては「CakePHPで何かつくってみたいなら、使わない理由がない
」ということです。
Cakeboxとは
CakeboxはAlt<3 Because projects need loveというオランダのプロジェクトのリポジトリにあり、アムステルダムのbravo-kernel氏が中心になって作っているようです。またCakePHPのコアデベロッパでもあるceeram氏もcontributeしているので、アムステルダムでは著名なプロジェクトなのかもしれません。
Cakeboxの詳しいドキュメントに書いてあるとおり、chefでubuntuベースのboxファイルを生成し、cakeboxではそれを使ってvagrantとvirtualboxで起動する流れです。
後で説明しますが、便利なコンソールアプリがあり、それ自体がCakePHP3でできているので、CakePHP3のアプリケーションサンプルとしても役立つのではないかと思います。
boxファイルに入っているソフトウェアは上記ドキュメントに詳しく書いてあるので、そちらを参照してください。
なんと簡単、環境構築
Cakeboxのインストール
最初にCakeboxをダウンロード(クローン)します。以下はCakeboxのREADMEに書いてあるままの内容です。 注意点として、以下の前提条件が必要となります。
- VirtualBox 4.0 以上
- Vagrant 1.6.0 以上
どちらかが満たされていないと、まったく起動しないので注意してください(私は踏みましたw)。
git clone https://github.com/alt3/cakebox.git
cd cakebox
cp Cakebox.yaml.default Cakebox.yaml
vagrant up
設定ファイル(yaml)には、詳細な設定を指定できるようですが、いったん何も設定しなくても問題はありませんでした。 以下のようなログが出力されます。 最初はCakeboxのboxファイルをCDNからダウンロードするので時間がかかります。 予め時間があり、回線に余裕があるときにvagrant upだけは済ませておくと良いですね。
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'cakebox'...
# ... 省略
==> default: ---------------------------------------------------------------
==> default: Please wait... installing Cakebox Commands and Dashboard
==> default: ---------------------------------------------------------------
==> default: * Self-updating Composer
==> default: * Updating Composer cache permissions
==> default: * Creating project
==> default: * Composer installing
==> default: Installation completed successfully!
==> default: Running provisioner: shell...
default: Running: inline script
==> default: ---------------------------------------------------------------
==> default: CakePHP v3.0.0 Console
==> default: ---------------------------------------------------------------
==> default: Self-updating cakebox
==> default: Self-updating Composer
==> default: * Updating cache permissions
==> default: Updating Cakebox Commands and Dashboard
==> default: * Detecting branch
==> default: * Updating git repository
==> default: * Updating composer packages
==> default: Updating CakePHP Code Sniffer
==> default: * Composer updating
==> default: Updating HHVM configuration
==> default: * Creating system start/stop links
==> default: * Correcting HHVM session.save_path
==> default: * Restarting service
==> default: Updating Elasticsearch configuration
==> default: * Decreasing required memory
==> default: * Updating initialization script
==> default: * Stopping service
==> default: Self-update completed successfully
==> default: Running provisioner: shell...
default: Running: inline script
==> default: ---------------------------------------------------------------
==> default: CakePHP v3.0.0 Console
==> default: ---------------------------------------------------------------
==> default: Setting Cakebox Dashboard protocol to http
==> default: Command completed successfully
==> default: Running provisioner: shell...
default: Running: inline script
# ... 省略
==> default: Machine 'default' has a post `vagrant up` message. This is a message
==> default: from the creator of the Vagrantfile, and not from Vagrant itself:
==> default:
==> default: Your box is ready and waiting.
==> default:
==> default: => Login to your Dashboard by browsing to http://10.33.10.10
==> default: => Login to your virtual machine by running: vagrant ssh
boxファイルのインストールが終わると、ダッシュボードアプリをインストールし、そのアプリのCLIを使ってアプリ自身と、各種モジュールを設定するようです。
ここまで終わったら、指示どおり http://10.33.10.10
にアクセスしてみましょう。
かっこいいダッシュボード画面が表示されました。こういうのがあるとテンション上がりますよね!
アプリケーションの構築
Cakeboxを使って開発環境が構築できたら、CakePHP3アプリケーションを構築してみましょう。vagrant ssh
でCakeboxにログインします。
localhost:cakebox $ vagrant ssh
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-24-generic x86_64)
CakeboxのREADMEに書いてあるとおり、cakebox
コマンドを使ってアプリケーションを生成します。とりあえずCakePHP3のブログチュートリアルを作ってみたいと思います。
vagrant@cakebox:~$ cakebox application add blog-tutorial.app
---------------------------------------------------------------
CakePHP v3.0.0 Console
---------------------------------------------------------------
Creating application http://blog-tutorial.app
Configuring installer
Creating installation directory
Composer installing CakePHP 3.x application sources
Creating virtual host
* Successfully created PHP-FPM virtual host
Creating databases
* Successfully created main database
* Successfully created test database
Configuring permissions
Updating configuration files
Application created using:
database => blog-tutorial_app
framework => cakephp
framework_human => CakePHP 3.x
framework_short => cakephp3
installation_method => composer
majorversion => 3
path => /home/vagrant/Apps/blog-tutorial.app
source => cakephp/app
url => blog-tutorial.app
webroot => /home/vagrant/Apps/blog-tutorial.app/webroot
Remember to update your hosts file with: 10.33.10.10 http://blog-tutorial.app
Installation completed successfully
はい、終わり。hostsファイルに追加してね
というメッセージが出ているので、hostsファイルに追加します。
$ vim /etc/hosts
# この行を追加します
10.33.10.10 blog-tutorial.app
さっそく http://blog-tutorial.app
にアクセスしてみましょう。
うわー、すげー。
ディレクトリの権限とか、DBの設定とか全部できちゃっているよ。
CakePHP3で composer create-project
やったことあればわかると思うのですが、ここまで設定するのもちょっと面倒です。
Cakeboxのダッシュボード画面も見てみましょう。
アプリケーションが1つ、データベースが2つ、バーチャルホストが2つ(最初は1なので1つ増えてます)になっています。
つまり cakebox application add
実行すると
- CakePHP3の
composer create-project
でスケルトン作って - nginxのsite-availablesにバーチャルホスト追加して
- DB(mysql)にdefaultとtestの2つのDBを作って
- CakePHP3のconfigをモロモロ設定、パーミッションも設定
してくれるというわけです。なんと便利、そして簡単なんでしょう。
blogチュートリアルの1ページ目のうち、Creating the Blog Database
のテーブル生成以外のステップは(nginxの設定まで)コマンド一つで終わりです。
ブログチュートリアルのテーブル作成
せっかくなので、マイグレーションプラグインを使って、テーブルを生成します。
vagrant@cakebox:~$ cd Apps/blog-tutorial.app/
vagrant@cakebox:~/Apps/blog-tutorial.app$ ./bin/cake migrations create CreateArticles
Welcome to CakePHP v3.0.1 Console
---------------------------------------------------------------
App : src
Path: /home/vagrant/Apps/blog-tutorial.app/src/
---------------------------------------------------------------
using migration path /home/vagrant/Apps/blog-tutorial.app/config/Migrations
using migration base class Phinx\Migration\AbstractMigration
using default template
created ./config/Migrations/20150419074519_create_articles.php
upとdownを以下のように記述します。
<?php
public function up()
{
$table = $this->table('articles');
$table->addColumn('title', 'string', ['limit' => 50])
->addColumn('body', 'text')
->addColumn('created', 'datetime')
->addColumn('modified', 'datetime')
->create();
}
public function down()
{
$this->dropTable('articles');
}
?>
マイグレーションを実行してテーブルを作成します。
vagrant@cakebox:~/Apps/blog-tutorial.app$ ./bin/cake migrations migrate
Welcome to CakePHP v3.0.1 Console
---------------------------------------------------------------
App : src
Path: /home/vagrant/Apps/blog-tutorial.app/src/
---------------------------------------------------------------
using migration path /home/vagrant/Apps/blog-tutorial.app/config/Migrations
using environment default
using adapter mysql
using database blog-tutorial_app
== 20150419074519 CreateArticles: migrating
== 20150419074519 CreateArticles: migrated 0.1178s
All Done. Took 0.1780s
これでチュートリアルの1ページ目は終了です。
ブログチュートリアルを進めよう
チュートリアルって環境構築ではまるケースが多い(特に今まで使ったことないフレームワークとか特に)のですが、このように簡単に始められるのは大きいですね。 あとはパート2の内容を、進めていけば大丈夫です。 ここではチュートリアルそのものを解説するわけではないので、ワープします。 指定されたファイルに、そのままコピペしていけば大丈夫です。
コピペしたあとにテストという投稿を追加してみた結果が以下のとおりです。
さいごに
Cakeboxを使ってブログチュートリアルを進めてみましたが、もし今までCakePHPを使ったことがなくても、CakePHP2は使っていたけど、3はまだ、という人にも環境構築のステップが簡略されているのは、とても大きいと思います。
実はCakebox Multi-Framework PHP Development Environment
と書いてあるとおり、CakePHP3だけのためにあるわけではないようです。
# Fresh preconfigured PHP framework applications
$ cakebox application add mycake3.app
$ cakebox application add mycake2.app --majorversion 2
$ cakebox application add mylaravel.app --framework laravel
なんとCakePHP2もいけるし、今話題の laravel5 の環境も作れるみたいですよ! これは現時点でのサポート状況ということで、今後増えていくことも想定されます。 これはもう Cakebox を試してみるしかないですね。
そんなCakePHP3ですが、CakePHP3 もくもく会(勉強会) #14 が 2015-04-28(火)19:00 - 21:30 に Co-Edoで開催されます。 PHP勉強会と日程かぶっていますが、もしCakePHP3に興味があればこちらにも参加してみてください。
Recent Articles
- tsyringe を TypeScript 5 で使う方法 2023/05/02
- LocalStack を使って aws-sdk の Integration Test を実行する 2023/04/19
- AWS SDK v3 のモジュールと利用方法 2023/04/18
- ts-jest が esbuild/swc をトランスフォーマーに使って高速化していた 2023/04/13
- aws-sdk v3 を使うライブラリを作ったときは、なるべく peerDependencies に設定しよう 2023/04/11
- aws-sdk v2 が 2023 年中にメンテナンスモードになる 2023/04/06
- Node.js v18 / aws-sdk v3 の Lambda アプリが突然動かなくなる 2023/04/05
- aws-sdk v3 でコンパイルエラーになる - その2 2023/04/04
- aws-sdk v3 で TS2345 が出てコンパイルエラーになる 2023/04/03
- aws-sdk-client-mock はどのように aws-sdk をモックしているのか? 2023/02/02