Home > アーカイブ > 2014-12
2014-12
2014 年に発表したセッションと資料まとめ
2014 年も残すは、あと 1 週間になりました。今年も様々なイベントで登壇しましたので、発表したセッションと資料をまとめてみます。
写真提供:久岡写真事務所
登壇イベント
2014/04/04 「わかってるフレームワーク Laravel」Laravel勉強会福岡
「わかってるフレームワーク Laravel」とうタイトルで発表しました。
Laravel で、とあるプロジェクトの開発が終わった後だったので、Laravel への良さを主観たっぷりでお話しました。
翌日の Laravel Meetup Tokyo と合わせて、一人 Laravel Japan ツアーでした:D
2014/04/05 「知っておくべき Auth オートログイン」 Laravel Meetup Tokyo vol.3
Laravel 4.1.25 以前にあった Auth フィルタ利用時のオートログインの問題点についてお話しました。
いくつかの前提条件は必要ですが、影響度が大きいので、「これはやばい」というのが伝わっていました。
なお、この問題は、4.1.26 にて修正されたので、現在は問題ありません。
Laravel ユーザなら知っておくべきAuthオートログインのこと
2014/04/12 「Vagrant ユーザのための Docker 入門」 第3回コンテナ型仮想化の情報交換会@大阪
Docker が盛り上がってきた時だったので、Vagrant ユーザを対象に Docker 入門をお話しました。
セッション途中でも活発に質問が飛び込んできたり、デモでコンテナ起動の速さに驚きの声があがったりで、盛り上がったのを覚えています。
「VagrantユーザのためのDocker入門」を発表してきました
2014/04/19 「最近なんだか、はてブがおかしい」俺聞け8 in Tokyo
資料未公開
当初は「Webエンジンアとしてご飯を食べていく」という内容を考えていたのですが、事前に、主催の @msng さんから「ブログに関する発表が多くて嬉しい」というコメントがあったので、ブロブに寄せようと内容を変えました:)
会場には、著名なブロガーの方が多く、みなさん同じようなことを感じているようで、発表後の情報交換が捗りました。
2014/04/24 「Vagrant 体験入門」DevLove関西
これから Vagrant を学ぶ方向けに、Vagrant 概要についての発表と体験ハンズオンを行いました。
Vagrant ハンズオンでは、みんなで同一拠点から一斉にプロビジョンを行うので、遅延やエラーが起こったり、環境の違い(Vagrant + VirtualBox が起動するまでの)でトラブルが発生したりで、毎回発見があります。
ハンズオンの資料は Qiita に公開しています。
Vagrant体験入門ハンズオン手順 – 2014/04/24 DevLove関西
2014/06/19 「Heroku で作るスケーラブルな PHP アプリケーション」第16回関西PHP勉強会
Heroku で PHP が正式サポートされたので、スケーラブルな構成をどう組むかという内容でお話しました。
その後、いくつかのプロジェクトで Heroku + PHP を使っているのですが、やはり PaaS として良くできていますね。まだ掴みきれていない部分もあるので、引き続き追いかけていきます。
Heroku で作るスケーラブルな PHP アプリケーション
2014/06/28 「PHPコードではなく PHPコードの「書き方」を知る」PHPカンファレンス関西2014
PHPカンファレンス関西の初心者向けセッションということで、FizzBuzz を題材に、関数化、クラス化、そして自動テストを書くという流れをお話しました。
「PHPコードではなくPHPコードの「書き方」を知る」を発表してきました
2014/07/05 「開発現場で活用する Vagrant」夏の JAWS-UG 三都物語 2014
関西での JAWS-UG イベント、三都物語でのセッションでした。JAWS-UG では久しぶりの発表になりました。
Vagrant を実際に使う例として、デモで実演しながらセッションを進めました。
会場がフランクな雰囲気で、話しやすかったのが印象的でした。あと Yo を使って、バーチャル拍手というかうなずきを送ってもらったりもしましたね。
2014/08/23 「Vagrant 心得 5 ヶ条」DevLove 甲子園 2014 西日本大会
資料未公開
DevLove 甲子園にて、Vagrant についてお話しました。ここでは、Vagrant を活用する上で、気をつけておくと良いことを 5 つにまとめました。
内容は、いずれ blog に書きたいと思います。
2014/10/11 「Ansible ではじめるサーバ作業の自動化」PHPカンファレンス 2014
PHP カンファレンスで、Ansible についてお話しました。発表を聴いた方からは、好意的なフィードバックが多く、「Chef を使っていましたが、Ansible 使ってみます!」という意見もあり、良かったです。
準備している時は、話したいことをどう上手くおさめるかを苦心したので、普段スピーカーをしているコミュニティの仲間から「うまくまとまっていましたね」と言ってもらえ、やはり見ている人には分かるんだなあと感じたりもしました。
このセッションを見た方から、別のイベントでの登壇についてお誘いがあったり、次につながったセッションでもありました。
「Ansibleではじめるサーバ作業の自動化」を発表してきました
2014/12/19 「ビルドサーバで使う Docker」DevLove 関西
年内最後の発表は、やはり Docker でした。今年は、ほんと Docker が躍進した一年でしたね。
Jenkins サーバでの活用例から、導入のポイントや設定方法などをお話しました。
「Jenkinsサーバで使う Docker」を発表してきました
ロクナナワークショップ 「Laravel で学ぶモダン PHP 開発講座」
11 月から、ロクナナワークショップさんにて「Laravel で学ぶモダン PHP 開発講座」という講座を行っています。
みっちり 1 日で、Vagrant や Composer の使い方、Laravel を使った REST API の実装、そして、その自動テストを書くという内容です。
すでに 2 回開催しているのですが、参加された方は熱心に課題に取り組まれていて、講師としても勉強になることが多いです。まとまった時間でじっくり課題をこなしていくので、これから学んでいこうという方にはおすすめです。
次回は 2015/02/17 に開催予定ですので、もし興味がある方は、ご参加下さい。
さいごに
今年を振り返ってみると、自分で名乗り出るよりも、お声がけ頂いて、登壇するという機会が増えました。お声がけ頂けるのは嬉しく、スケジュールやタイミングが許せば、できるだけお引き受けしたいです。
人前で話すというのは、準備も大変ですし、いつも緊張しますが、毎回色々な発見があり、また、終わった後は何ともいえない解放感というか充実感を感じることができます。聴いて頂いた方からのフィードバックも嬉しいものです。
今年も、セッションに参加して頂いた方、イベントにお声がけ頂いた方、本当にありがとうございました。
2015 年は、年始の 1/16 に GoAzure 2015 で登壇する予定です。Websites でスケーラブルな PHP アプリケーションを作るという内容ですので、ご参加お待ちしています!
- コメント (Close): 0
- トラックバック (Close): 0
「Jenkinsサーバで使う Docker」を発表してきました
- 2014-12-22 (月)
- Docker
すっかり年の瀬ですが、今年最後の発表を DevLove 関西にて行いました。
Docker 実践編ということで、CI の一環でビルドサーバに使っている Docker についてお話してきました。
発表資料
Jenkins サーバに Docker を入れており、ビルドの環境として利用しています。構成や使い方は、わりとベーシックな内容です。
プロビジョンには Ansible を使っており、ローカルコネクションで ansible-playbook を実行しています。
使い捨てできる環境なら実用的
勉強会の最後に、発表者への QA の時間があったのですが、多数の質問があり、Docker に対する関心が高いのをあらためて実感しました。
今回、参加された方は、これから Docker を使ってみようという方が多いようで、導入に関することや、安定性などに対する質問が多かったです。(安定性に関しては、私のセッションでトラブルの話をしたせいもありますが。)
セッションでも触れましたが、現状は、開発環境やビルドやジョブワーカーのように、作って、使って、捨てるという環境では、Docker は実用的です。
一方、Web システムのように、常に動き続ける環境では、Docker デーモン自体の運用やイメージリポジトリなど、Docker コンテナをデプロイ、運用していく上で考慮すべきことがあり、まだ様子を見ているというのが率直なところです。
とはいえ、この時期だからこそ、果敢にチャレンジして、ノウハウを貯めるというのも一つの戦略で、トラブルシュート込みで実践してみるというのもアリです。(今回、自前でビルドサーバを構築したのは、こういった意味合いもありますし。)
個人的には、Docker コンテナが動く環境は AWS や GCE などのクラウドサービスに任せて、こちらでは Docker コンテナをビルドして、然るべきところへ送信すれば、後はよしなにやってくれるようになると嬉しいです。
- コメント (Close): 0
- トラックバック (Close): 0
Docker Hub 公式 PHP イメージで、複数バージョンの PHP コンテナを起動する
Docker Hub の 公式 PHP イメージで、PHP 5.4, 5.5, 5.6 のコンテナを起動します。
PHP Advent Calendar 2014 の 12 日目です。昨日は、PHPで簡単に華麗にDIとAOPをキメる でした。
Docker Hub には、公式でミドルウェアや言語のイメージがで公開されています。PHP もその一つで、現在は、5.4, 5.5, 5.6 が公開されています。また、バージョン毎に複数のタグが存在し、cli / fpm / apache(mod_php) といた構成違いのものが存在します。
https://registry.hub.docker.com/_/php/
ここでは、5.x-apache
タグのイメージを使って、複数バージョンのコンテナを起動して、ブラウザから、同一コンテンツを異なる PHP バージョンで確認できるようにします。
boot2docker のインストールと起動
Docker の実行環境として、boot2docker を使いました。
pkg ファイルが公開されているので、これをダウンロードして、インストールすると良いです。
http://docs.docker.com/installation/mac/
インストールできたら、boot2docker を起動しておきます。
$ boot2docker init $ boot2docker up
boot2docker の VM が起動したら、IP アドレスを確認しておきます。
$ boot2docker ip The VM's Host only interface IP address is: 192.168.59.xxx
公式 PHP イメージでコンテナを起動
Docker Hub 公式の PHP イメージを使って、5.4, 5.5, 5.6 の Docker コンテナを起動します。5.4-apache
/ 5.5-apache
/ 5.6-apache
のイメージは、Apache + mod_php の構成になっており、手軽に Web アプリケーションを実行できます。
実行するコンテンツは、OSX 上にあるファイルを利用します。boot2docker では、1.3 から shared_folder で、OSX の /Users
のディレクトリを、VM から /Users
として参照することができます。これにより、VM の docker コンテナからシームレスに OSX のディレクトリをマウントすることができます。
コンテンツは、単純に PHP バージョンを出力するだけです。
$ cat /Users/path/to/index.php <?php echo 'PHP ' . phpversion();
バージョン毎にポートを分けて、以下のようにコンテナを実行します。初回は、各イメージをダウンロードするので時間がかかります。次回以降は、即座に起動します。
$ docker run -v /Users/path/to:/var/www/html -p 8084:80 -d php:5.4-apache $ docker run -v /Users/path/to:/var/www/html -p 8085:80 -d php:5.5-apache $ docker run -v /Users/path/to:/var/www/html -p 8086:80 -d php:5.6-apache
ブラウザから確認
では、ブラウザから起動した Docker コンテナにアクセスします。ポートを変えて、アクセスすると、それぞれの PHP バージョンが出力されます。
- http://boot2docker-vm:8054/
1 | PHP 5.4.35 |
- http://boot2docker-vm:8055/
1 | PHP 5.5.19 |
- http://boot2docker-vm:8056/
1 | PHP 5.6.3 |
さいごに
Docker Hub 公式の PHP イメージを使って、複数バージョンの PHP を起動しました。
このイメージは、使ってみると分かるのですが、拡張は必要最低限となっており(mbstring も pdo_mysql / pdo_pgsql もありません)、実際のアプリケーションを動かすには、拡張を追加して、ビルドする必要があります。
もちろん、こうした状況は考慮されており、イメージには docker-php-ext-install
という拡張を追加するコマンドが用意されています。
実用する場合は、公式イメージをそのまま使うのではなく、これをベースに Dockerfile を書き、独自に拡張を追加したイメージを使うという方法になりますね。
明日の PHP Advent Calendar 2014は、ngyuki さんです。お楽しみに!
- コメント (Close): 0
- トラックバック (Close): 0
自分流 Laravel 4 アプリケーションアーキテクチャ
Laravel Advent Calendar 2014 の 9 日目です。
今年の Advent Calendar では、Laravel 5 リリース目前ということで、Laravel 5 の話題が多いのですが、それは他の方にお任せして、ここでは、Laravel 4 でのアプリケーション実装について書いてみます。
Laravel は自由度の高いフレームワークですので、アプリケーションも自由な構成にすることができます。ただ、この「自由さ」が故に、どういう構成が良いのかというのが悩ましい点でもあります。
このエントリでは、私が実際に構築したプロダクトをベースに構成例をご紹介します。Laravel アプリケーションを構築する上での参考になれば嬉しいです。
1. ディレクトリ構成
まずは、大枠のディレクトリ構成から。アプリケーションや開発環境用の Vagrantfile やプロビジョンファイルなどは、Git リポジトリで管理しています。
ディレクトリ構成としては以下になります。
Dockerfile
は、CI サーバで Docker を使って、自動テストを行っているので、そのためです。
Vagrantfile
は、開発環境用の VM を管理するためのものです。アプリケーションディレクトリにルートに Vagrantfile を置いておくと、アプリケーション内のどのディレクトリからでも vagrant
コマンドできるので便利です。
provision/
は、開発環境、上記 CI サーバでの実行環境、ステージング環境用のプロビジョンファイルが入っています。プロビジョンには、Ansible を使っています。本番環境は、構成管理はこちらでは行わないので、デプロイ用の playbook が格納されています。
src/
が、Laravel アプリケーションのコードが入っているディレクトリになります。以下では、この src/ について見ていきます。
. ├── Dockerfile ├── README.md ├── Vagrantfile ├── docker/ ├── provision/ └── src/
1-1. src/ ディレクトリ
src/
の構成は以下です。
このアプリケーションは、Laravel で REST API を実装して、AngularJS で実装したクライアント側と連携して動作するアーキテクチャになっています。また、クライアント側のコンポーネントの管理に、bower を使っており、そのためのファイルやディレクトリがあります。
Laravel 自体は、ベーシックな構成ですが、アプリケーション独自のコンポーネントを格納するために、package
というディレクトリを作っています。これについては、後述します。
. ├── CONTRIBUTING.md ├── Gruntfile.js ├── _ide_helper.php ├── app/ ├── artisan ├── bootstrap/ ├── bower.json ├── bower_components/ ├── composer.json ├── composer.lock ├── karma.conf.js ├── package/ ├── package.json ├── phpunit.xml ├── public/ ├── readme.md ├── server.php ├── spec/ └── vendor/
2. Laravel アプリケーション の役割
Laravel アプリケーションでは、主に以下の役割を担っています。
- HTML の生成、出力
- JavaScript(AngularJS アプリケーション) / CSS などの生成、出力
- REST API の提供(AngularJS アプリケーションと連携)
初期のビューは、Laravel で生成、出力して、あとは、AngularJS + REST API で連携して動作する SPA(Single Page Application) の形です。このアプリケーションでは、特定ユーザのみが利用するものなので、認証を行い、認可されたユーザのみがアプリケーションを利用できるように、ビューの出力も Laravel で行っています。
ほとんどの機能は、REST API にて提供されるので、以降はこの部分について書いていきます。
3. 処理フロー
REST API への HTTP リクエストは、概ね、以下のフローで処理されます。
- Routing – リクエストされた URI から、処理すべきハンドラを決定します。通常は、コントローラのメソッド、簡単な処理ならネイティヴクロージャに処理を委ねます。
- Controller – 処理の起点となります。バリデーションを行い、正常値であれば、サービスクラスのメソッドに値を渡して、実行します。
- Service – 実処理を行う部分です。ビジネスロジックを実行し、Model(Eloquent)を使って、データベースへの操作などを行います。
- Model(Eloquent) – Eloquent を継承したクラスです。データベースへのアクセスを担うとともに、エンティティ固有の処理なども行います。
各フェーズ(レイヤ)では自身の責務に注力すればよく、上位のフェーズを関知する必要はありません。(必要な情報は受け渡されます。)
HTTP レスポンスボディは、JSON で返すので、blade などのビューテンプレートは、ここでは利用しません。
4. アプリケーションクラスの構成(namespace, autolaod)
ルーティング以外のクラスは、app/
以下ではなく、package/
以下に配置しています。また、namespace で、独立したパッケージとして分離しています。
アプリケーションをフレームワークの構成から独立したパッケージとすることで、フレームワークの一部としてアプリケーションを構成するのではなく、アプリケーションの一部としてフレームワークを利用することをイメージしています。
package/ └── Foo/ ├── DomainA │ ├── Command/ <--- CLI Command │ ├── Controller/ <--- Controller │ ├── Model/ <--- Model(Eloquent) │ ├── Service/ <--- Serivice │ ├── Test/ <--- Test │ └── Validation/ <--- Validation ├── DomainB │ ├── Command/ │ ├── Controller/ │ ├── Model/ │ ├── Service/ │ ├── Test/ │ └── Validation/ OneByOne/ └── Laravel ├── Controller/ ├── Exception/ ├── Service/ └── Test/
namespace のトップレベルには、アプリケーション名(Foo)を、次に、ドメインパッケージ名(DomainA / DomainB)が続きます。アプリケーション内でも業務などのドメイン毎にパッケージを分けています。さらに、それぞれのドメインごとに、Controller や Model など役割に応じた namespace を割り当てています。
例えば、Foo システムの DomainA の Controller にある SampleController であれば、Foo\DomainA\Controller\SampleController
となります。
ここでは、Foo システムの他に、汎用コンポーネントを集積した OnebyOne システムも同梱しています。
アプリケーションクラスは、composer.json
にて、PSR-4 を使って、オートロードで読み込めるようにしています。Laravel におけるアプリケーションは、オートローダで読めれば、どこに配置しても良いので、こうした柔軟な構成が可能です。
- composer.json
{ "autoload": { "psr-4": { "Foo\\": "package/Foo" "OneByOne\\": "package/OneByOne" } }, }
5. Routing
ルーティングでは、URI と処理を行うコントローラのメソッドを Route::verbs()
で連結するだけです。
一部、単純にデータベースから値を読み込んで、JSON として返す程度のものは、ネイティヴクロージャで実装しています。
認証が必要な API については、認証用のフィルタを実装しておいて、それを、Route::group()
で指定します。Route::group()
のネイティヴクロージャ内に認証が必要な API の定義を記載します。
Route::when()
では、POST
, PUT
, DELETE
リクエスト場合は、csrf フィルタを適用するようにしています。
Route::pattern()
では、/foo/{id}
の {id}
を受け入れるパターンを定義していまます。こうしておけば、各ルーティングで、正規表現を定義する必要がなく、便利です。
- app/config/routes.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php use Foo\DomainA\HogeController; $apiPrefix = '/api/v1' ; // csrf filter Route::when( '*' , 'csrf' , [ 'POST' , 'PUT' , 'DELETE' ]); // patterns Route::pattern( 'id' , '[0-9]+' ); Route::group( [ 'before' => 'auth' ], function () use ( $apiPrefix ) { $controller = HogeController:: class ; Route::get( $apiPrefix . '/foo' , $controller . '@read' ); Route::post( $apiPrefix . '/foo' , $controller . '@create' ); Route::put( $apiPrefix . '/foo/{id}' , $controller . '@update' ); Route:: delete ( $apiPrefix . '/foo/{id}' , $controller . '@delete' ); } ); |
6. Controller
コントローラでは、入力値のバリデーション、サービスクラスの実行、そして、HTTP レスポンスを構築します。
下記は、典型的なコントローラの例です。
コントローラクラスでは、フレームワークの Contoller クラスを継承しています。あと、API 共通処理(JSON レスポンス等)を ApiControllerTrait
というトレイトに実装しているので、これを use
しています。
- package/Foo/DomainA/Controller/SampleController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?php namespace Foo\DomainA\Controller; use Foo\DomainA\Service\SampleService; use Foo\DomainA\Validation\SampleValidatorBuilder; use OneByOne\Laravel\Controller\ApiControllerTrait; class SampleController extends Controller { use ApiControllerTrait /** * @var SampleService */ protected $service ; /** * @param SampleService $serivice */ public function __contruct(SampleService $service ) { $this ->service = $service ; } public function create() { // validation $validator = ( new SampleValidatorBuilder())->create(Input::all()); if ( $validator ->fails()) { return $this ->validationError( $validator ->messages()); } $this ->service->create(Input::all()); return $this ->created(); } } |
下記が、ApiControllerTrait の一部です。ここでは、JSON レスポンスを返すメソッドをまとめています。
- package/OneByOne/Laravel/Controller/ApiControllerTrait.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <?php namespace OneByOne\Laravel\Controller; use Response; use Symfony\Component\HttpFoundation\Response as Res; trait ApiControllerTrait { /** * ok * * @param mixed $message * @return Illuminate\Http\JsonResponse */ public function ok( $message = '' ) { return Response::json( $message ); } /** * validationError * * @param mixed $messages * @return Illuminate\Http\JsonResponse */ public function validationError( $messages = []) { return $this ->badRequest( 'validation' , $messages ); } /** * BadRequest * * @param string $error * @param array $messages * @return Illuminate\Http\JsonResponse */ public function badRequest( $error = null, $messages = []) { $response = [ 'error' => $error , 'messages' => $messages , ]; return Response::json( $response , Res::HTTP_BAD_REQUEST); } (snip) } |
6-1. サービスクラスのインジェクト
SampleController に戻ります。コンストラクタでは、処理を担うサービスクラスを引数に取ります。コントローラは、フレームワークによって、インスタンス化されるので、Laravel の IoC コンテナにより、SampleService クラスのインスタンスが自動でインジェクトされます。
もちろん、テストなどの際は、自らインスタンス化して、モックオブジェクトを差し込むことも可能です。
6-2. バリデーション
ルーティングによりリクエスト URI に合致したメソッドが実行されます。
ここでは create
メソッドが実行されるとします。コントローラのメソッドでは、まず、入力値のバリデーションを行います。
SampleValidatorBuilder クラスにて、バリデーションクラスのインスタンスを生成します。
SampleValidatorBuilder の実装例は下記です。バリデーションクラスの生成(ルールの設定)をクラス化しておくことで、コントローラからバリデーションルールの設定を逃がすことができ、同じルールを異なる箇所で共有できます。当然、テストからも、バリデーションクラスが生成できるので、バリデーションのテストが書くのが容易です。
このバリデーションビルダのインターフェースとテスト用のトレイとをまとめて、packagist に公開しています。
shin1x1/laravel-validator-builder
- package/Foo/DomainA/Validation/SampleValidatorBuilder.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?php namespace Foo\DomainA\Validation; use Shin1x1\ValidatorBuilder\ValidatorBuilder; use Validator; /** * Class SampleValidatorBuilder * @package Foo\DomainA\Validation */ class SampleValidatorBuilder implements ValidatorBuilder { /** * @param array $inputs * @return IlluminateValidationValidator */ public function create( array $inputs ) { return Validator::make( $inputs , [ 'name' => 'required' , 'email' => 'required|email' , ] ); } } |
6-3. サービスクラスの実行
入力値が妥当なものであれば、サービスクラスのメソッドを実行します。
サービスクラスのインスタンスは、コントローラのコンストラクタでセットされているので、これを利用します。
処理が正常であれば、created
メソッド(ApiControllerTrait に実装)を呼び出して、201 Created
を返します。
現在は、コンストラクタインジェクションでサービスクラスをインジェクトしているので、1コントローラ=1サービスの形が多いです。(App::make() などで、メソッド内で、サービスクラスのインスタンスを取得する場合もあります。)
6-4. Laravel 5 でのメソッドインジェクション
今後の話ですが、Laravel 5 では、コントローラのメソッドについても、IoC コンテナによる DI が可能になります。これにより、各アクションメソッド毎にサービスやバリデーションビルダがインジェクトできるので、より使いやすくなります。
7. Service
サービスクラスでは、ビジネスロジックなどのドメイン固有の処理を実行します。フレームワークのクラスは継承せず、単なる POPO として実装しています。
ユースケースに対応して実装することが多いので、名称から内容が想起できるもにしています。例えば、予約を扱うアプリケーションなら、ReservationService をクラス名とし、各メソッド名は、予約業務のユースケースである「予約する(book)」「予約を更新する(update)」「予約を取り消す(cancel)」としています。
下記では、Bar クラスのインスタンスを作って、入力値をデータベースに保存しています。
入力値のバリデーションは、コントローラで終わっているので、このクラスで処理する場合は、引数は正当な値であるとみなします。ただ、値の整合性(一意制約などのアプリケーション制約)など、事前条件のチェックはここで行います。もし事前条件に違反している場合は例外を投げて処理を中断します。
- package/Foo/DomainA/Service/SampleService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php namespace Foo\DomainA\Service; use Foo\DomainA\Model\Bar; class SampleService { /** * @param array $inputs */ public function create( array $inputs ) { $bar = new Bar(); $bar ->name = array_get( $inputs , 'name' ); $bar ->email = array_get( $inputs , 'email' ); $bar ->save(); } } |
8. Model(Eloquent)
モデルは、フレームワークの Eloquent クラスを継承しており(正確には、Eloqent を継承した AppModel クラスを継承)、通常の Laravel アプリケーションで利用するものと同じです。
エンティティ固有の処理などは、こちらに実装します。
- package/Foo/DomainA/Model/Bar.php
1 2 3 4 5 6 7 8 | <?php namespace Foo\DomainA\Model; use OneByOne\Laravel\Model; class Bar extends AppModel { } |
さいごに
Laravel 4 で構築したアプリケーションの構成について見てきました。
考え方の基本は、本文でも書いたとおり、アプリケーションが主で、フレームワークはそれを実現するためのものツールに過ぎないということです。そのため、アプリケーションはフレームワークとは、独立したパッケージとして構成しています。
この考え方をより進めるなら、package/ 以下のクラスについては、フレームワーククラスを利用する箇所はより抽象化していく(例えばリポジトリパターンを利用して、Eloquent と分離するなど)方法もあるのですが、抽象化を進めすぎて、かえって複雑になるのを避けるために、現在の状態に留めています。
構築するアプリケーションにもよるとは思いますが、これまでは、この形がしっくり来ています。(もちろん、今後変わる可能性もあります。)
こうしたアプリケーションを主体にした構成にしておけば、フレームワークのバージョンが変わっても、柔軟に対応できそうです。Laravel 5 では、アプリケーションディレクトリの構成が変わりますが、そもそも別構成にしているので、オートローダで読み込めれば問題ありません。
まだまだ改良していく余地はありますが、こうした柔軟な構成が取れるのも Laravel の面白さですね。
Laravel Advent Calendar 2014、明日は neronplex さんです。お楽しみに!
- コメント (Close): 0
- トラックバック (Close): 0
Composer を倍速にした、たった 1 行のコード
- 2014-12-04 (木)
- PHP
まだ 12 月早々ですが、PHP ユーザに素敵なクリスマスプレゼントが届きました。
いまや使うのが当たり前となった Composer ですが、複雑な依存解決に実行時間がかかるのがネックでした。
これは日本国内だけでなく、海外のユーザも同じで、皆がしょうがないと思いつつも、小さな不満を持ちながら使っていました。
そんな、ある日、わずか 1 行のコードが追加されたことで、実行時間が、わずか半分になるという現象が起こりました。
Composer を倍速にするには?
composer self-update
を実行して、最新版にするだけです。
$ composer self-update
実際の効果
このコードの効果を見てみましょう。composer コマンドの --profile
オプションを使って、実行時間と使用メモリ量を出力します。
$ composer update --dry-run --profile
まずは、比較のために、変更される前の composer で実行します。3 回続けて実行した結果が以下です。
# composer = 1.0.0-alpha8 Memory usage: 199.44MB (peak: 854.47MB), time: 397.92s Memory usage: 199.44MB (peak: 854.68MB), time: 353.29s Memory usage: 200.05MB (peak: 855.5MB), time: 338.84s
続いて、変更後の composer で実行します。
# composer = 1.0-dev (37ec0bde9dd6826591308e7a1ad55cb5e38ef117) Memory usage: 122.17MB (peak: 209.3MB), time: 67.82s Memory usage: 122.17MB (peak: 209.29MB), time: 16.34s Memory usage: 122.17MB (peak: 209.3MB), time: 16.77s
なんと、メモリピークは 1/4 、そして実行時間は、6-20 倍ほど早くなっています!!
Github のコメントなどで見ると、効果のほどは、定義されている依存関係や環境によっても異なり、メモリ使用量の変化は小さい場合もあるようですが、実行時間に関しては概ね半分程度にはなるようです。
たった 1 行のコード
では、どんな魔法を使って、この改善を行ったのでしょうか。
その答えが、下記の commit です。
https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799
変更したコードは、わずか 1 行です。(実際には、空行の追加があるので、2 行)
1 2 3 | public function run() { + gc_disable(); |
これは、PHP 5.3 にて、循環参照によるメモリ・リークを解消するために導入されたガベージコレクションを無効にする関数です。デフォルトでは有効になっているのですが、今回は、これを無効にしたことで、パフォーマンスが大幅に改善されました。
このガベージコレクションについては、PHP マニュアルに記述があります。
http://php.net/manual/ja/features.gc.php
また、今回の事象を踏まえて、さらに詳細な内容がまとまっていますので、こちらもどうぞ。
ircmaxell’s blog: What About Garbage?
PHP ユーザの熱狂
この、あまりの効果に PHP ユーザが熱狂しました。Twitter でも多くのコメントがありますが、最も分かりやすいのが、Github です。
この修正を行った commit のページには、数多くの賞賛コメントが付いており、祭りの様相を呈しています:D
Gif アニメーションが多数貼られており、ブラウザで開き切るのも時間がかかる状況です。もはや、悪ノリ感もありますが、いかに多くの PHP ユーザがこの改善を喜んでいるかが分かりますね。
https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799
さいごに
わずか、1 行のコードで、問題が大幅に改善される。
特にパフォーマンスの問題に関しては、こうした体験をした人は多いのではないでしょうか。その 1 行が生まれるまでには、多くの時間が必要なのです。Github 上でもディスカッションが行われていました。
そして、結果として書いたコードは、たったの 1 行でした。
早くなった composer を動かしつつ、やはり大事なのはコードを書いた量ではなく、問題を解決することなんだな、としみじみ感じた出来事でした。
なお、速くなるのは、依存解決の部分なので、ダウンロードの時間は変わりません。あしからず。
- コメント (Close): 0
- トラックバック (Close): 0
CakePHP2 アプリケーションを Heroku で動かす設定
CakePHP2 アプリケーションを Heroku 上で動かす設定についてです。
以前のエントリにも書きましたが、Heroku で Web アプリケーションを動かす際に重要なのは、Web サーバ自体(Heroku では、Dyno)に、アプリケーションの状態(データ、セッション情報、ログ等)を保持させないということです。
Heroku の Dyno は、デプロイの際や、定常的な再起動により、破棄されるため、記録されたファイルは消えてしまいます。よって、こうしたデータファイルは、アドオンなど外部に記録する必要があります。
Heroku では、アドオンを活用するのがポイントですので、ここでは、主に CakePHP アプリケーションからこうしたアドオンと連携する方法を見ていきます。
Environments Library as a plugin
まず、開発環境と Heroku 環境で設定値を切り替えるために Environments Library as a plugin を使います。
https://github.com/josegonzalez/cakephp-environments
これは、CAKE_ENV
という環境変数の値で、環境に応じて、データベースやキャッシュなどの設定を切り替えるものです。昨今のフレームワークではお馴染みの機能ですね。
ここでは、下記の設定について切り替えを行います。
- ログ
- データベース
- キャッシュ / セッション
このプラグインでは、まず Config/bootstrap/environments.php
を作成します。このファイルで、環境毎の設定ファイルの読み込みを行います。下記では、Heroku 環境用の heroku.php
と、開発環境用の development.php
を読み込んでいます。
- Config/bootstrap/environments.php
1 2 3 4 5 6 7 8 | <?php CakePlugin::load( 'Environments' ); App::uses( 'Environment' , 'Environments.Lib' ); include dirname( __FILE__ ) . DS . 'environments' . DS . 'heroku.php' ; include dirname( __FILE__ ) . DS . 'environments' . DS . 'development.php' ; Environment::start(); |
次に、Heroku 環境用の Config/bootstrap/environments/heroku.php
です。
環境の設定は、Environment::configure()
で行います。第一引数に環境名(CAKE_ENVで指定するもの)、第二引数は適用条件(true なら、CAKE_ENV に合致しなければデフォルトとして適用)、第三引数に設定値の連想配列(Configure::write() する)、第四引数にコールバックを指定します。
第三引数で設定する方法もあるのですが、より柔軟な設定を行うために、ここでは、第四引数のコールバックを指定します。実際の設定は、以降で順に指定していきます。
- Config/bootstrap/environments/heroku.php
1 2 3 4 5 | <?php Environment::configure( 'heroku' , false, [ ], function () { // Heroku 用設定 }); |
開発環境用の Config/bootstrap/environments/development
です。コールバックで、デフォルトの設定を記述しています。
- Config/bootstrap/environments/development.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <?php Environment::configure( 'development' , true, [ ], function () { // Log settings App::uses( 'CakeLog' , 'Log' ); CakeLog::config( 'debug' , array ( 'engine' => 'File' , 'types' => array ( 'notice' , 'info' , 'debug' ), 'file' => 'debug' , )); CakeLog::config( 'error' , array ( 'engine' => 'File' , 'types' => array ( 'warning' , 'error' , 'critical' , 'alert' , 'emergency' ), 'file' => 'error' , )); // Database settings Configure::write( 'DATABASE_OPTIONS' , [ 'datasource' => 'Database/Postgres' , 'persistent' => false, 'host' => 'localhost' , 'login' => 'shin' , 'password' => '' , 'database' => 'app' , ]); Configure::write( 'TEST_DATABASE_OPTIONS' , [ 'datasource' => 'Database/Postgres' , 'persistent' => false, 'host' => 'localhost' , 'login' => 'shin' , 'password' => '' , 'database' => 'app_test' , ]); // Cache settings Cache::config( 'default' , array ( 'engine' => 'File' )); }); |
次に、database.php
での接続情報を、環境ごとに設定できるように下記のようにします。各環境ファイルで Configure::write()
した値を、DATABASE_CONFIG クラスに設定します。
- Config/database.php
1 2 3 4 5 6 7 8 9 | class DATABASE_CONFIG { public $default = []; public $test = []; public function __construct() { $this -> default = Configure::read( 'DATABASE_OPTIONS' ); $this ->test = Configure::read( 'TEST_DATABASE_OPTIONS' ); } } |
このプラグインを有効にするために、bootstrap.php
を以下のように変更します。コメント部分は削除しています。
- Config/bootstrap.php
1 2 3 4 5 6 7 8 9 | <?php require_once __DIR__ . '/../vendor/autoload.php' ; include __DIR__ . '/bootstrap/environments.php' ; Configure::write( 'Dispatcher.filters' , array ( 'AssetDispatcher' , 'CacheDispatcher' )); |
Heroku で環境変数 CAKE_ENV をセット
このアプリケーションが、Heroku にデプロイした際に、heroku.php を利用するように、環境変数CAKE_ENV
にheroku
をセットします。これで、デプロイしたアプリケーションは、heroku
設定で動作するようになります。
$ heroku config:set CAKE_ENV=heroku
ログ
Heroku 環境でのログの設定です。ログは、ファイルではなく、標準出力もしくは標準エラー出力に出力します。
こうしておくと、heroku logs
コマンドでログが確認できます。また、Papertail や FlyData のようにログを各サービスへ転送するアドオンを使うと、出力されたログを閲覧したり、S3 などに転送することができます。
ログは、まずは標準出力もしくは標準エラー出力にして、あとそれをどう活用するかは、アドオンに任せるという考え方です。
下記では、アプリケーションログを標準出力へ、エラー出力を標準エラー出力へ出力しています。
- Config/bootstrap/environments/heroku.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php Environment::configure( 'heroku' , false, [ ], function () { // Log settings App::uses( 'CakeLog' , 'Log' ); App::uses( 'ConsoleOutput' , 'Console' ); CakeLog::config( 'debug' , [ 'engine' => 'ConsoleLog' , 'stream' => new ConsoleOutput(), ]); CakeLog::config( 'error' , [ 'engine' => 'ConsoleLog' , 'stream' => new ConsoleOutput( 'php://stderr' ), ]); }); |
あとは、アドオンと追加すれば、出力されたログがアドオンで利用できます。例えば、Papertrailを以下のコマンドで追加して、アプリケーションを動作させると、Papertrail の画面でログが確認できます。
$ heroku addons:add papertrail
データベース
Heroku のデータベースといえば、PostgreSQL なので、Heroku Postgres を使います。(もちろん、MySQL が良ければ、ClearDB なり、RDS なり使えます。)
$ heroku addons:add heroku-postgresql
Heroku 内では接続情報が環境変数で渡されるので、これをパースします。パースされた情報を、heroku.php で、Configure::write() で、セットしていきます。
- Config/bootstrap/environments/heroku.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php Environment::configure( 'heroku' , false, [ ], function () { // Log settings (snip) // Database settings if ( empty ( getenv ( 'DATABASE_URL' ))) { throw new CakeException( 'no DATABASE_URL environment variable' ); } $url = parse_url ( getenv ( 'DATABASE_URL' )); Configure::write( 'DATABASE_OPTIONS' , [ 'datasource' => 'Database/Postgres' , 'persistent' => false, 'host' => $url [ 'host' ], 'login' => $url [ 'user' ], 'password' => $url [ 'pass' ], 'database' => substr ( $url [ 'path' ], 1), ]); }); |
キャッシュ / セッション
キャッシュ / セッションのデータストアには、Redis を使います。
ここでは、Redis サービスのアドオンとして、Redis To Go を利用します。
$ heroku addons:add redistogo
データベースと同じく、接続情報が環境変数で渡されるので、それをパース(ホスト、ポート、パスワード)して、セットするだけです。セッションハンドラには、Cache を使いたいので、その指定も行います。
これで、キャッシュもセッションも、Redis To Go に保存されます。
- Config/bootstrap/environments/heroku.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?php Environment::configure( 'heroku' , false, [ ], function () { // Log settings (snip) // Database settings (snip) // Cache settings if ( empty ( getenv ( 'REDISTOGO_URL' ))) { throw new CakeException( 'no REDISTOGO_URL environment variable' ); } $url = parse_url ( getenv ( 'REDISTOGO_URL' )); Cache::config( 'default' , [ 'engine' => 'Redis' , 'server' => $url [ 'host' ], 'port' => $url [ 'port' ], 'compress' => false, 'password' => $url [ 'pass' ], 'serialize' => 'php' ]); // Session Settings Configure::write( 'Session' , [ 'defaults' => 'cache' , ]); }); |
さいごに
ベーシックな部分だけですが、Heroku で CakePHP アプリケーションを動かす設定を見てきました。
ここで紹介した以外では、メールやデータファイル(画像等)など、他にもアプリケーションでは必要なものがありますが、ほとんどのものは、ここで見てきたようにアドオンを活用することで実現できます。
こうした構成にしておけば、Dyno が再起動したり、インスタンス数を増減しても、アプリケーションデータはアドオンに保持されているので、動作に支障はありません。こうしたスケーラブルナ構成にしておくのが、Heroku のような PaaS を活用する肝ですね。
このエントリは、CakePHP Advent Calendar 2014 の 3 日目でした。まだ少し空き枠があるので、CakePHP な何かを書いてみてはどうでしょうか。
- コメント (Close): 0
- トラックバック (Close): 0
Home > アーカイブ > 2014-12
- 検索
- フィード
- メタ情報