作った経緯
Advent Calendar番外編みたいな感じで書きなぐります
もう数ヶ月前の話ですが、Laravelレシピ日本語版 というものを作りました
もともとはLaravel Recipes ですが、
折角なので日本語版を、といっても当時リポジトリにあったドキュメントを日本語訳にしまして、
実装自体は全く別物にしたかったのと、
これからLaravelを利用する方々のヒントになればと思い、1から開発しました
デザインセンス皆無なのと実装自体も大した内容ではありませんが・・
ソースコード
カレンダーシーズンに突入、Laravelを選んで使っている企業さんも増えてきたと思いますので、
折角なのでネタばらしと、ヒントになるかもしれない点を
構成
折角なので動かしてる環境をば
web server: nginx(1.6系)
データストレージ: MySQL(master, slave), Redis, memcached
PHP: 5.6
デプロイはcapistrano利用で、特に変わったところはありませんね!
cache, sessionなどは全てmemcachedで、
Redisはレシピそれぞれのアクセス数とランキング等に使ってる普通の感じです
なかみ
パッケージ関連
Laravelのパッケージとしては、barryvdh/laravel-ide-helper
のみです
その他に利用しているライブラリは汎用的なものを選んで、実装してます
パッケージ使わなくても普通でしょ?という意味がこもってます(!?)
パッケージ等に合わせずに使い慣れたものを使いましょう
ディレクトリ構造
Laravel4系のデフォルトではなく、5っぽい感じにしました
これはフォルダ構成は自由に決めれるよ!という意味も込めてそうしてます
それとオートローダーはclassmapではなく、psr-4へ変更
不要になったClassLoaderの一部は削除
実装部分は一目でわかるような構造にしました モデルのディレクトリはありません
"autoload": {
"classmap": [
"app/database/migrations",
"app/database/seeds"
],
"psr-4": {
"App\\": "app/App"
},
"files": [
"app/common.php"
]
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests"
}
},
このため、ファサード関連はすべて完全修飾名(頭に)、またはエイリアスを使ってます
認証系
一応管理画面的なものはありますが、ほぼ使ってませんオマケみたいなもんです
認証はデフォルトのものではなく、独自のドライバーになってます
githubで認証する様になっていて、指定したアカウント以外はログインできない様な実装になってます
これも独自の認証を作るのは簡単だよ!という意味が・
デフォルトの認証の場合、Authコンポーネントを利用する度にDBにアクセスするので
Cacheを併用してます
というか、このサイトのデータアクセスのほとんどがCacheを利用してます
DB操作系
残念ながらEloquentは全く使用せずに、
クエリービルダーと、複雑なクエリはベーシックなものを使ってます
それと前述した様にCacheを併用してます
DBは2台なので、connectionはreadとそれ以外で指定してます
そのうちRDBMSではない他のものに変更する予定の為、
基本的にはconnectionを変更するだけで変更できる様な実装に。
ユニットテストや、他の実装にも関連しますが、
DIで簡単に処理を変更できる様にしましたので
海外のLaravelユーザーの中ではスタンダードっぽい、所謂リポジトリーパターン風になってます
public function find($id, array $columns = ['*'], $lifeTime = 120)
{
if(\Cache::has($this->cacheKey . $id)) {
return \Cache::get($this->cacheKey . $id);
}
$result = \DB::connection($this->slave)->table($this->table)
->where($this->primary, $id)->first($columns);
if($result) {
\Cache::put($this->cacheKey . $id, $result, $lifeTime);
return $result;
}
return null;
}
単純なリードの処理はこんな感じに
rememberは空の場合もキャッシュされてしまうため、
結果のあるものだけをキャッシュするようにした為、この様になっています
rememberの方が使い勝手が良い場合は、
同じ様にキャッシュキーを付けて利用すると良いと思います
migrate
特に変わった方法は利用してませんが、例えばFKを指定したい場合や、
defaultにcurrent timestampを利用する様にしたい!などがあれば、と思いそのようにしてます
$table->timestamp('updated_at')->default(\DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
$table->index(['category_id', 'position', 'recipe_id'], 'RECIPE_INDEX');
$table->foreign('category_id')->references('category_id')
->on('categories')->onDelete('cascade')->onUpdate('cascade');
current timestampと複合インデックスとFKサンプル
View
ViewはBladeですが、いくつかソースコードを読まないとわからないものを使ってます
\View::inject('title', e(str_replace(["\r\n","\r","\n"], '', $title)));
これはbladeのsectionなどに直接値を挿入します
bladeにsectionを書かなくても、意のままに操作することができます
コンテンツの挿入も、View::makeではない他の方法を利用してます
view composerも併せて利用して、Controllerや他の処理が出来るだけごちゃごちゃしない様に
構造が見え易い様にしてますが、果たして・・
Markdown
レシピは全てMarkdownですが、
記述方法だけ海外版と似せてあります(考えるのが面倒だった・・)
ビジュアル表示に関する処理、プレゼンターとしてまとめられている処理で
Markdownから表示への変換はcacheを利用してますので都度これらの変換処理は実行されません
レシピが追加されたときや上書きされたときはそれまでのcacheを破棄して、
新しい情報を表示する様になってます
artisan
レシピはmarkdownですので、それらをインポートするのと、
それぞれのレシピへのアクセス数計測関連の操作にartisanのコマンドを利用してます
アクセス数操作関連は、cronで実行してます
実装は、よくある
Artisan::Add("Command");
ではなく、サービスプロバイダーで実装してます
フィルター処理やさきほどのview composerなんかもすべてサービスプロバイダーです
$this->app['jp-recipe.add'] = $this->app->share(function($app) {
return new AddRecipeCommand(
$app->make("App\Repositories\CategoryRepositoryInterface"),
$app->make("App\Repositories\RecipeRepositoryInterface"),
$app->make("App\Repositories\TagRepositoryInterface"),
$app->make("App\Repositories\RecipeTagRepositoryInterface")
);
}
);
レスポンス拡張
APIとしてjsonでレシピを返却しますが、簡易的なHATEOASも
ユニットテスト
カバレッジ45%しかないのもあれですが、
スタブ等簡単に差し替えられる様になってますのでDBを使わなくてもOKです
public function setUp()
{
parent::setUp();
\App::bind("App\Presenter\FeedInterface", "App\Presenter\FeedStub");
}
もちろんMockeryを利用したテストコードも含んでますので、
やり方や使い方が分からない方もヒントになるかもしれません
\Input::shouldReceive('get')->andReturn(1);
ざっとこんな感じで構成されてます
あまりファサードにも依存しない様にした実装の一例ですので
Laravel5移行も簡単に済む様にしたつもりなので、
デフォルトから一歩踏み出したい方などのヒントになればと思います
好みにあった実装しやすい方法などを見つけてみてください