atijust's blog

技術的なこととか。

Laravel4でView Composerの設定をする場所

View Composerの設定をする場所の私案。特にデフォルトの場所が決まってるわけではないので好きな場所で設定すればいいんだけど、かといってroutes,.phpに書くのもダサいので、収まりのいい場所を模索。

ServiceProviderで

app/Atijust/ViewComposerProvider.php

<?php
namespace Atijust;

use Illuminate\Support\ServiceProvider;
use View;

class ViewComposerProvider extends ServiceProvider
{
    public function register()
    {
        /* NOP */
    }

    public function boot()
    {
        View::composer('sidebar', 'Atijust\SidebarViewComposer');
    }
}

app/composers.php

app/composers.php

<?php
View::composer('sidebar', 'Atijust\SidebarViewComposer');

app/start/global.php

<?php
require app_path().'/composers.php';

app/composers.phpを作成しView Composerを定義。 app/start/global.phpでrequireする。

なんとなくapp/composers.phpのほうが収まりがいい気がする。app/routes.phpでコントローラ、app/composers.phpでコンポーザーみたいな。ただ、数が多くなった場合は、ServiceProviderで整理したほうがいいかもしれない(おそらくこれはルートにも言える)。

Laravel4でブログチュートリアルっぽいのをやってみた - その2

Laravel4でブログチュートリアルっぽいのをやってみた - その1では、記事の一覧と表示を実装した。今回は記事の投稿を実装する。編集、削除については実装しないことにした。編集と投稿はほとんど同じだし、削除はコードが短く特に面白いところもないので。

リファクタリング

機能を実装していく前に、テストしやすいように予めリファクタリングしておくことにする。

app/controllers/PostController.php

<?php

class PostController extends BaseController
{
    public function index()
    {
        $posts = Post::all();
        return View::make('post.index', ['posts' => $posts]);
    }

    public function show($id)
    {
        $post = Post::findOrFail($id);
        return View::make('post.show', ['post' => $post]);
    }
}

前回作成したPostControllerではPostモデルのstaticメソッドを呼び出しておりテストがやりにくい。

Postモデルをモックできるようにコンストラクタで注入するようにする。

app/controllers/PostController.php

<?php

class PostController extends BaseController
{
    protected $post;

    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    public function index()
    {
        $posts = $this->post->all();
        return View::make('post.index', ['posts' => $posts]);
    }

    public function show($id)
    {
        $post = $this->post->findOrFail($id);
        return View::make('post.show', ['post' => $post]);
    }
}

コンストラクタに渡されたPostモデルをプロパティに保存しておき、アクションではそれを使うようにした。このようにしておけば、コンストラクタにダミーのインスタンスを渡すことにより、スムーズにテストを行うことができる。

アプリケーションの実行時は、LaravelのIoCコンテナによりPostモデルが渡される。IoCコンテナはリフレクションでコンストラクタのタイプヒントを調べて、対応するクラスのインスタンスを自動的に注入してくれる。なので、Postモデルを注入するコードを自前で書く必要はない。

バリデーション

記事の投稿ではユーザから入力されたデータをバリデーションする必要がある。アクションを実装するまえにバリデーションを用意しておくことにする。

バリデーションをどこに実装するべきかについては諸説あるようだが、今回はモデルにバリデーション機能を持たせる(楽だから)。

app/models/Post.php

<?php

class Post extends Eloquent
{
    protected $fillable = ['title', 'body'];
    protected $errors;

    public function validate(array $params)
    {
        $validator = Validator::make($params, [
            'title' => 'required',
            'body'  => 'required',
        ]);

        if ($validator->passes()) {
            return true;
        } else {
            $this->errors = $validator->messages();
            return false;
        }
    }

    public function errors()
    {
        return $this->errors;
    }
}

Laravelのバリデーション機能はValidatorから利用できる。titleとbodyを必須に指定している。失敗した場合はエラーメッセージをプロパティに保存しておき、モデルのerrors()メソッドで取得できるようにしておく。

ここでは自前で実装しているが、Eloquentを拡張してモデルにバリデーション機能をつけてくれるパッケージなんかもあるので、そういうのを使うのもいいかもしれない。

記事の投稿

記事を投稿するフォームを表示するgetCreateアクションと、実際に記事をDBに保存するpostCreateアクションの2つを作成する。

app/controllers/PostController.php

<?php
    public function getCreate()
    {
        return View::make('post.create');
    }

    public function postCreate()
    {
        $attrs = Input::only(['title', 'body']);

        if (!$this->post->validate($attrs)) {
            return Redirect::action('PostController@getCreate')
                ->withErrors($this->post->errors())
                ->withInput();
        }

        $this->post->create($attrs);

        return Redirect::action('PostController@index');
    }

HTTPリクエストに付随する各種情報にはInputからアクセスできる。Inputにはかゆいところに手が届く便利メソッドが色々と用意されており、ここでは指定した名前のパラメータだけを取り出せるonly()を使っている。

バリデーションが失敗したら投稿フォームにリダイレクトする。投稿フォームにエラーメッセージと前回の入力値を表示するために、withErrors()withInput()でエラーメッセージと入力値をセッションに保存している。

記事の保存にはPostモデルのcreate()を使っている。引数に渡した配列からエンティティを作成しDBにINSERTしてくれる。create()でセットできるのはモデルの$fillableに指定したものだけなので注意。create()に限らず配列で一気にエンティティに値をセットする系のメソッドは$fillableで明示的に許可されたプロパティ以外には使えない。もし許可されてないプロパティに値をセットしようとすると例外が発生する。

投稿フォームのViewを作成する。

app/views/post/create.blade.php

<!doctype html>
<html lang="ja">
<head>
  <title>Demo Blog</title>
  <meta charset="UTF-8">
</head>
<body>
<h1>記事投稿</h1>
@foreach ($errors->all() as $error)
  <p>{{{ $error }}}</p>
@endforeach
<form action="{{ action('PostController@postCreate') }}" method="post">
  {{ Form::token() }}
  <input type="text" name="title" value="{{{ Input::old('title') }}}" placeholder="タイトル"><br>
  <textarea name="body" rows="3">{{{ Input::old('body') }}}</textarea><br>
  <input type="submit" value="投稿">
</form>
</body>
</html>

アクションのリダイレクト処理においてwithErrors()でセッションに保存したエラーメッセージはViewでは$errorsで参照できる。 withInput()で保存した前回のユーザ入力はInput::old()で取得できる。

{{ Form::token() }}でフォームの隠しパラメータにCSRF対策のためのトークンをセットしている。このトークンは後述のルートフィルタでチェックする。

最後にルーティングを定義する。

app/routes.php

<?php
Route::get('/create', 'PostController@getCreate');
Route::post('/create', ['before' => 'csrf', 'uses' => 'PostController@postCreate']);

POSTの/createcsrfルートフィルタを設定している。csrfはLaravelに元から用意されているフィルタで、トークンをチェックしてくれる。

以上で記事の投稿機能は完成。組み込みサーバを立ち上げ、http://localhost:8000/createにアクセスすれば、投稿フォームが表示される。

最後に

取り敢えず、機能としてはこれで完成。Laravelにも多少馴れてきたような気がする。 テストについてはエントリをあらためて書きたいと思う。

Laravel4でブログチュートリアルっぽいのをやってみた - その1

手を動かして覚えたいので、ちょうどいい課題としてCakePHPのブログチャートリアルっぽいのをLaravel4でやってみた。

動作要件

Lravel4の動作要件は以下の2つ。

手元ではPHP5.5.5と組み込みサーバで動作を確認している。

インストール

Composerを使ってインストール。

$ composer create-project laravel/laravel --prefer-dist demo-blog
Installing laravel/laravel (v4.0.9)
  - Installing laravel/laravel (v4.0.9)
    Loading from cache

Created project in demo-blog
Loading composer repositories with package information
Installing dependencies (including require-dev)
### 中略 ###
Writing lock file
Generating autoload files
Generating optimized class loader
Application key [nROO9YVu3ZmwxXMlRDI32sz1l0mxo71n] set successfully.

zipをダウンロードしてきてインストールすることもできるみたいだけど、どのみちComposerが必要なのであまり意味はない。

動作確認

インストールが成功したかどうか確認。 プロジェクトのトップディレクトリで./artisan serveを実行する。

$ cd demo-blog
$ ./artisan serve
Laravel development server started on http://localhost:8000

PHPの組み込みサーバが立ち上がるので、ブラウザでhttp://localhost:8000にアクセスする。Laravelのロゴが表示されれば成功。

artisanはLaravelのコマンドラインツールで、開発に便利な各種機能を提供している。ここでは単にPHPの組み込みサーバを立ち上げるのに使ったが、DBのマイグレーションやシーディングなど、なにかとお世話になるコマンド。

Laravelの設定

タイムゾーン

app/config/app.php

    'timezone' => 'Asia/Tokyo',

デフォだとUTCなので変更しておく。

データベース

app/config/database.php

    'mysql' => array(
        'driver'    => 'mysql',
        'host'      => 'localhost',
        'database'  => 'demo_blog',
        'username'  => 'root',
        'password'  => '',
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    ),

ブログ用にdemo_blogというデータベースを使うことにする。 MySQL以外にもPostgres、SQLiteSQL Serverがサポートされているので、どれを使うかは好みで。設定方法は設定ファイルのコメントに書いてある。

データベースの作成

Laravelのマイグレーション機能を使ってテーブルを作成する。 demo_blogデータベースはコマンドラインなりツールであらかじめ作成しておく。

まずはartisanでマイグレーションのひな形を作成する。

$ ./artisan migrate:make create_posts_table --table=posts --create
Created Migration: 2013_10_30_002418_create_posts_table
Generating optimized class loader

スキーマビルダーを使ってテーブルを組み立てる。

app/database/migrations/2013_10_30_002418_create_posts_table.php

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('title', 50);
            $table->text('body');
            $table->dateTime('updated_at');
            $table->dateTime('created_at');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('posts');
    }

}

updated_atcreated_atというカラムがあれば、ORMがモデルの更新、作成時に自動的に時刻を設定してくれる。

マイグレーションを実行。

$ ./artisan migrate
Migration table created successfully.
Migrated: 2013_10_30_002418_create_posts_table

これでdemo_blogデータベースにpostsテーブルが作成された。

Postモデルの作成

LaravelにはEloquentというORMが含まれている。オーソドックスなActiveRecordタイプで使いやすい。

モデルのクラスファイルはオートローダでロードできるならどこに置いてもいいけど、取り敢えず最初から用意されているapp/modelsディレクトリを使うことにする。

app/models/Post.php

<?php

class Post extends Eloquent
{
}

モデルは規約によってクラス名の複数形のテーブルと対応する。もちろん規約外のテーブル名を使うことも出来る。

ダミーデータの用意

データベースにブログ記事のダミーデータを用意する。 コマンドライン、あるいは適当なツールでデータベースに直接データを入れてもいいのだが、Laravelに用意されているSeed機能を使ってみることにする。

app/database/seeds/DatabaseSeeder.phpにデフォルトのSeederクラスが用意されているのでこれを使う。

app/database/seeds/DatabaseSeeder.php

<?php

class DatabaseSeeder extends Seeder {

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();
        DB::table('posts')->truncate();
        Post::create(['title' => 'はじめてのLaravel', 'body' => 'Hello, Laravel!']);
    }

}

Postモデルを使ってレコードを作成。

artisanで実行する。

$ ./artisan db:seed
Database seeded!

Postコントローラの作成

コントローラはapp/controllersに設置する。例によってオートローダでロードできるならここ以外のディレクトリに設置することもできる。

app/controllers/PostController.php

<?php

class PostController extends BaseController
{
    public function index()
    {
        $posts = Post::all();
        return View::make('post.index', ['posts' => $posts]);
    }

    public function show($id)
    {
        $post = Post::findOrFail($id);
        return View::make('post.show', ['post' => $post]);
    }
}

記事一覧を表示するindexアクションと、記事を表示するshowアクションを用意した。先ほど作成したPostモデルからブログ記事を取得し、View::make()でビューをレンダリングしている。

次にアクションとURLを結びつけるためにルーティングを設定する。ルーティングの設定はapp/routes.phpに記述する。もちろんこれ以外の場所(ServiceProviderとか)で設定することもできる。

app/routes.php

<?php

Route::get('/', 'PostController@index');
Route::get('/{id}', 'PostController@show');

Postビューの作成

LaravelにはBladeというシンプルなテンプレートエンジンが含まれている。今回はこれを使うことにする。 もちろんだけど、Blade以外にPHP、Twig、Smartyなどなんでも好きなものを使うことができる。Packagistで検索するとLaravel用のパッケージが見つかる。

まずはPostController@indexに対応する記事一覧のビューファイル。

app/views/post/index.blade.php

<!doctype html>
<html lang="ja">
<head>
  <title>Demo Blog</title>
  <meta charset="UTF-8">
</head>
<body>
<h1>Demo Blog</h1>
@foreach ($posts as $post)
  <a href="{{ action('PostController@show', $post->id) }}">{{{ $post->title }}}</a> {{ $post->created_at }}<br />
@endforeach
</body>
</html>

制御構文は@からはじまる。 {{}}で囲まれた部分はechoされる。{{{}}}で囲まれている場合はエスケープされてからechoされる。 URLの生成には各種ヘルパーメソッドが用意されている。ここではコントローラのアクションに引数を指定してURLを生成している。

次はPostController@indexに対応する記事を表示するビュー。

app/views/post/show.blade.php

<!doctype html>
<html lang="ja">
<head>
  <title>Demo Blog</title>
  <meta charset="UTF-8">
</head>
<body>
<h1>{{{ $post->title }}}</h1>
<p>{{{ $post->body }}}</p>
</body>
</html>

以上でブログの記事一覧と記事表示の機能は完成。 コマンドラインからartisanでPHPの組み込みサーバを立ち上げ、http://localhost:8000にアクセスすれば記事一覧が表示されるはず。

記事の投稿、編集、削除とユニットテスト

ちょっと疲れたので、記事の投稿、編集、削除については後日あらためて作成することにする。いまどきテストを書かないわけにもいかないので、テストもしっかり用意したいと思う。

最後になったが、Laravelの学習には日本語ドキュメントを使わせていただいた。翻訳して公開してくださった川瀬氏に感謝。

SSH代替のmoshでMac OSX10.8(Mountain Lion)からEC2インスタンスに接続

WiMAX経由でEC2インスタンスSSH接続して作業していると、時間帯によってはシェルの反応がもっさりだったり、接続が切れちゃったりでストレスマッハでハゲる。

そこで、回線品質が低い場合でも快適に作業できると噂のmoshを導入。

まずはMBA。インストーラがあるみたいだけど、素直に(?)brewでインストールする。

$ brew install mobile-shell

なるほど。moshはMobile Shellの略なわけね。

次はEC2。OSはAmazon Linux。パッケージないみたいなのでソースからビルドする(あとで気づいたんだけどEPELにあった。試してないけど。)。./configureしたらProtocol Bufferが必要だといわれたので、あらかじめ入れておく。Purotocol Buffer以外にも依存しているモジュールがあるようだが、すでに導入済みだったみたい。お試しなので、インストール先は/optに。

$ wget http://mosh.mit.edu/mosh-1.2.4.tar.gz
$ tar xvf mosh-1.2.4.tar.gz
$ cd mosh-1.2.4
$ sudo yum install protobuf-devel
$ ./configure --prefix=/opt/mosh
$ make
$ sudo make install

通信にUDPポート60000-61000を使うので、Security Groupを操作して開けておく。

f:id:atijusts:20130505200153p:plain

以上で準備は完了。moshはサーバ側であってもデーモンを常駐させておく必要がないので、upstartのスクリプトを書いたりしなくてもよい。

クライアント側のMacからアクセスしてみる。

$ mosh --ssh="ssh -i aws.pem" --server="/opt/mosh/bin/mosh-server" ec2-user@hogehoge.compute.amazonaws.com

ログイン自体はSSH経由で行うようだ。ログインに使うSSHのキーを指定したい場合は--sshオプションを使う。サーバ側は非標準な場所にインストールしたので、mosh-serverコマンドのパスを指定する必要がある。

f:id:atijusts:20130505201259p:plain

繋がった。レスポンスが心持ち良くなったかも?

moshは低品質回線での使い勝手を良くする以外にも、Ctrl-Cの挙動を改善してくれたりするので(でっかいファイルをcatしてしまってCtrl-Cが効かなくなったことありません?)、メインのPCでもSSHの代わりにいいかもしれない。

PHPのHTTPクライアントライブラリ「Guzzle」を使ってみる(その2)

その1のつづき。

Guzzleには、Service Descriptionといって、インターフェースをJSON(PHP配列でも可)で定義しておけば、コードを一行も書くことなくWebサービスにアクセスできる機能がある。

JSONでAPIのインターフェースを定義して

{
    "operations": {
        "getProfile": {
            "httpMethod": "GET",
            "uri": "/api/restful/v1/people/{+guid}/{+selector}{?fields,format}",
            "parameters": {
                "guid": {
                    "location": "uri"
                },
                "selector": {
                    "location": "uri"
                },
                "fields": {
                    "location": "query"
                },
                "format": {
                    "location": "query",
                    "default": "json"
                }
            }
        }
    }
}

こんなふうに使う。

<?php
require_once 'vendor/autoload.php';

use Guzzle\Plugin\Oauth\OauthPlugin;
use Guzzle\Service\Client;
use Guzzle\Service\Description\ServiceDescription;

$client = new Client('http://sb.sp.app.mbga.jp/');

$client->addSubscriber(new OauthPlugin(array(
    'consumer_key'    => 'xxxxxxxxxxxxxxxxxxxx',
    'consumer_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
)));

$client->setDescription(ServiceDescription::factory(__DIR__.'/people.json'));

$result = $client->getCommand('getProfile', array(
    'guid'     => '123456',
    'selector' => '@self',
    'fields'   => 'id,nickname',
))->execute();

var_dump($result);

結果は配列以外にも定義次第でいろんな形式で返せる。

array(4) {
  'startIndex' =>
  int(1)
  'person' =>
  array(2) {
    'nickname' =>
    string(7) "hoge"
    'id' =>
    string(17) "sb.mbga.jp:123456"
  }
  'itemsPerPage' =>
  int(1)
  'totalResults' =>
  int(1)
}

動作確認のサンプルなので、ここでは基本的なことしかやってないけど、ドキュメントを読む限りService Descriptionはかなり柔軟で強力な機能のよう。AWS SDK for PHPの実装にも使われていたりするので、実用的なWebサービスクライントの作成も十二分に可能だろう。

PHPのHTTPクライアントライブラリ「Guzzle」を使ってみる(その1)

Mobageのドキュメントデベロッパー以外にも公開されたみたいですね。これでブログに気兼ねなくMobageネタが書けます。というわけで、さっそく。

GuzzleというHTTPクライアントライブラリが良さげなので、MobageAPIにアクセスしてみました。OAuthプラグインもあって超お手軽。そして、お手軽でありながら、高機能。パラレルリクエストもできるっぽい。

取り敢えずPeopleAPIを叩いてみる。

<?php
require_once 'vendor/autoload.php';

use Guzzle\Http\Client;

$client = new Client('http://sb.sp.app.mbga.jp/');

$client->addSubscriber(new Guzzle\Plugin\Oauth\OauthPlugin(array(
    'consumer_key'    => 'xxxxxxxxxxxxxxxxxxxx',
    'consumer_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
)));

echo $client->get(array('/api/restful/v1/people{/guid}{/selector}{?fields,format}', array(
    'guid'     => '123456',
    'selector' => '@self',
    'fields'   => 'id,nickname,thumbnailUrl',
    'format'   => 'json',
)))->send()->getBody();

これで実行すると、、、

Fatal error: Uncaught exception 'Guzzle\Http\Exception\ClientErrorResponseException' with message ' in /Users/atijust/Develop/mbga_api/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/BadResponseException.php on line 48

Guzzle\Http\Exception\ClientErrorResponseException: Client error response
[status code] 401
[reason phrase] Unauthorized
[url] http://sb.sp.app.mbga.jp/api/restful/v1/people/123456/%40self?fields=id%2Cnickname%2CthumbnailUrl&format=json
(略)

ファッ!?

URLテンプレートで置き換えてる部分の@がパーセントエスケープされとる。。。

URI template RFCによると、Path Segment Expansion: {/var}では予約文字はパーセントエスケープされるそう。

えー、、、

Reserved Expansion: {+var}を使えば予約文字のパーセントエスケープを回避できるっぽいので、取り敢えずそれでやってみる。

<?php
echo $client->get(array('/api/restful/v1/people/{+guid}/{+selector}{?fields,format}', array(
    'guid'     => '123456',
    'selector' => '@self',
    'fields'   => 'id,nickname,thumbnailUrl',
    'format'   => 'json',
)))->send()->getBody();

今度はうまくいった。

{
   "startIndex" : 1,
   "person" : {
      "nickname" : "hoge",
      "thumbnailUrl" : "http://sb-sp.mbga.jp/img_u/123456/0.1.gif",
      "id" : "sb.mbga.jp:123456"
   },
   "itemsPerPage" : 1,
   "totalResults" : 1
}

(もにょるなぁ)

その2へつづく。

HerokuにNginx+PHP5.4+MySQLな環境を構築する

HerokuといえばRubyのためのPaaSという印象が強いのですが、ひっそりとPHPも使えます。しかし、PHPのバージョンが5.3.10と少し古いのが難点。そこで、サードパーティのbuildpackを使ってPHP5.4環境を構築してみました。buildpackというのは環境構築スクリプトのパッケージで、サードパーティのbuildpackを使えば、公式にサポートされていない環境も簡単にセットアップして使うことができます。サードパーティのものには、CやらCommon LispやらErlangやらなんでもありです。

Third-Party Buildpacks

今回はHeroku上に最新のPHP5.4 + Nginx環境を30秒で構築するで紹介されているiphoting / heroku-buildpack-php-tylerを使いたいと思います。

特徴としては

  • Nginx+PHP-FPM
  • PHP5.4(apc, memcache, memcached, mysql, pgsql, phpredis, mcrypt, newrelic, sqlite)
  • Composerによる依存性管理

Nginx+PHP-FPMな構成で、Apacheがわからない(っていうか、Nginxでいいじゃん的な)ゆとりエンジニアの自分にも設定いじれそうなのがいいですね。Composerで環境構築時に自動的にパッケージをインストールしてくるのも有難い。phpinfoはこんな感じ

セットアップ

取り敢えず、動作確認用のindex.phpを作成しgitのリポジトリを作成します。

$ mkdir heroku-test
$ cd heroku-test
$ vi index.php
<?php
echo 'Hello, Heroku!';
$ git init
$ git add .
$ git commit -m '最初の登録'

Herokuコマンドにbuildpackを指定してアプリを作成します。

$ heroku create -s cedar -b git://github.com/iphoting/heroku-buildpack-php-tyler.git
Creating radiant-badlands-4098... done, stack is cedar
BUILDPACK_URL=git://github.com/iphoting/heroku-buildpack-php-tyler.git
http://radiant-badlands-4098.herokuapp.com/ | git@heroku.com:radiant-badlands-4098.git
Git remote heroku added

あとはpushするだけ。環境構築に数十秒かかります。

$ git push heroku master
Counting objects: 3, done.
Writing objects: 100% (3/3), 261 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)

-----> Fetching custom git buildpack... done
-----> PHP app detected
-----> Fetching Manifest
       https://s3.amazonaws.com/heroku-buildpack-php-tyler/manifest.md5sum
-----> Installing Nginx
       Bundling Nginx v1.2.7
       https://s3.amazonaws.com/heroku-buildpack-php-tyler/nginx-1.2.7-heroku.tar.gz
-----> Installing libmcrypt
       Bundling libmcrypt v2.5.8
       https://s3.amazonaws.com/heroku-buildpack-php-tyler/libmcrypt-2.5.8.tar.gz
-----> Installing libmemcached
       Bundling libmemcached v1.0.7
       https://s3.amazonaws.com/heroku-buildpack-php-tyler/libmemcached-1.0.7.tar.gz
-----> Installing PHP
       Bundling PHP v5.4.11
       https://s3.amazonaws.com/heroku-buildpack-php-tyler/php-5.4.11-with-fpm-heroku.tar.gz
-----> Installing newrelic
       Bundling newrelic daemon v2.9.5.78
       https://s3.amazonaws.com/heroku-buildpack-php-tyler/newrelic-2.9.5.78-heroku.tar.gz
-----> Copying config files
-----> Installing boot script
-----> Done with compile
-----> Discovering process types
       Procfile declares types -> (none)
       Default types for PHP   -> web

-----> Compiled slug size: 35.8MB
-----> Launching... done, v5
       http://radiant-badlands-4098.herokuapp.com deployed to Heroku

To git@heroku.com:radiant-badlands-4098.git
 * [new branch]      master -> master

もうこれで動きます。簡単すぎ!

$ heroku open

アドオンでMySQLを使えるようにする

HerokuといえばPostgreSQLですが、サードパーティMySQLアドオンがいくつかあるようです。

MySQLでアドオンを検索したところこの3つが出てきました。特に決め手はないのですが、取り敢えずClear DB MySQL Databaseを使ってみたいと思います。無料のigniteプランを使用します。

インストールはこれだけ。

$ heroku addons:add cleardb:ignite
Adding cleardb:ignite on radiant-badlands-4098... done, v8 (free)
Use `heroku addons:docs cleardb:ignite` to view documentation.

heroku configで接続情報が見れます。

$ heroku config | grep CLEARDB_DATABASE_URL
CLEARDB_DATABASE_URL => mysql://adffdadf2341:adf4234@us-cdbr-east.cleardb.com/heroku_db?reconnect=true
※接続情報は公式ドキュメントのサンプルより

mysqlコマンドで接続可能。

$ mysql --host=us-cdbr-east.cleardb.com --user=adffdadf2341 --password=adf4234 heroku_db

PHPからつなぐ場合は、環境変数から接続情報を取得してパースします。公式ドキュメントのUsing ClearDB with PHPを参照。接続情報をハードコーディングすることも出来るのですが、ソース公開するとき困るので、環境変数使う感じで。

$cleardb = parse_url(getenv('CLEARDB_DATABASE_URL'));
$conn = new PDO(
    sprintf("mysql:dbname=%s;host=%s", substr($cleardb['path'], 1), $cleardb['host']),
    $cleardb['user'],
    $cleardb['pass']
);

以上、HerokuでNginx+PHP5.4+MySQLな環境を構築してみました。いくつかコマンドを実行するだけでサクサクと環境が構築できてしまうのはスゴイです。VPSを借りて自分で管理するのも面白いのですが、ちょっとしたものを公開するときにお手軽な選択肢があるのはいいですね。

次はSymfony2でも入れてみようかな。Treasure Dataのアドオンも面白そう。