2016 年 4 月 28 日

Laravel5.2のスタートアッププラクティス

laravel5.2のスタートアッププラクティス

Laravel5で開発を始める前に行っていることをまとめました。各設定ファイルのディレクトリ構成のカスタマイズや、Laravelを拡張するためのコードの配置場所を設定しています。

この方法がスタンダードということはありませんが、開発チームで予め標準化しておくと、保守性も向上するのではないでしょうか。
データベースのマイグレーションやカスタムミドルウェアの設定、Javascript/CSSのビルドなどは割愛しています。

環境

  • OS: osx10.11.4
  • PHP: PHP5.6.x
  • Laravel: 5.2.31

Laravelインストール

現時点では、5.2.31がリリースされているようです。

$ composer create-project --prefer-dist laravel/laravel
$ cd laravel
$ ./artisan --version
Laravel Framework version 5.2.31

gitリポジトリ作成

gitリポジトリを作成します。

$ git init
$ git add .
$ git commit -m 'initial commit'

動作確認

簡単に動作を確認します。
実際の開発時も、PHPビルトインサーバを利用することが多いです。なんといっても手軽です。

もちろん、Vagrantでステージングサーバを構築して、試験することも併用しています。
また、プロダクションサーバへのデプロイメントもVagrantで作成したステージングサーバで調整・検証しています。プロビジョニングには、Ansibleを利用しています。

今後は、LaravelアプリもDockerコンテナ化して、瞬時にデプロイできるように考えています。

$ php -S 0.0.0.0:8080 -t public/

プロジェクトネームスペースの定義

プロジェクトの固有のソースコードの配置先を変更し、ネームスペースを分離しています。
まず、プロジェクトのソースコードディレクトリを作成します。

ここではHttpディレクトリだけ作成していますが、概ねLaravelのapp/のディレクトリ構成をそのまま、プロジェクトのネームスペースで作成しています。
editorはお好きなものに置き換えてください。筆者は、atomエディタで開発しています。

ディレクトリを作成したらpsr4でオートロードできるようにcomposer.jsonに追加します。

$ mkdir -p srcs/app/Http
$ vi composer.json

"autoload": {
  "classmap": [
    "database"
  ],
  "psr-4": {
    "App\\": "app/",
    "Project\\": "srcs/app/"
  }
},

その後、オートロードファイルを再生成します。

$ composer dump-autoload
Generating autoload files

autoload_classmap.phpを参照すれば、追加されていることが確認できます。

$ vi ./vendor/composer/autoload_classmap.php

'Project\\' => array($baseDir . '/srcs/app'),

routes.phpの移動

通常、app/Http/routes.phpに配置されていますが、よく参照・変更するファイルなので、プロジェクトのHttpへ移動します。
好みの問題でもありますから、必須というわけではありません。

$ cp app/Http/routes.php srcs/app/Http
$ vi app/Providers/RouteServiceProvider.php
protected function mapWebRoutes(Router $router)
{
  $router->group([
    'namespace' => $this->namespace, 'middleware' => 'web',
  ], function ($router) {
    // require app_path('Http/routes.php'); routes.phpのロケーションを変更します。
    require base_path('srcs/app/Http/routes.php');
  });
}

コンフィグレーションの設定

日本語ファイルを英語のものから作成しておきます。多国語対応する必要がなくても、メッセージの保守性を向上するために言語ファイルに全てのメッセージを定義しています。

$ cp -a resources/lang/en resources/lang/ja

次に、アプリケーションのコンフィグレーションを設定します。
url、タイムゾーン、ロケールの設定とプロジェクト固有のサービスプロバイダを一つ以上設定しています。

$ vi config/app.php
'url' => 'http://www.project.com',
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
'fallback_locale' => 'ja',

'providers' => [
...
     Project\Providers\AppServiceProvider::class,
]

サービスプロバイダの定義

先ほど定義したサービスプロバイダを作成します。プロジェクト固有のサービスプロバイダには、ストレージパスの設定や、bladeで利用するカスタムなディレクティブ、フォームバリデーションで利用する共通のバリデータなどを定義しています。

まず、bootメソッドでストレージパスを変更しています。いままではシンボリックリンクでデプロイごとのストレージが変わらないようしていましたが、今後Dockerコンテナ化も考慮して、任意のパスに変更するようにしました。

その後、Bladeのカスタムディレクティブやカスタムバリデータを定義しています。数が多くなれば、それぞれ別のプライベートメソッドへ移動したり、別のサービスプロバイダを作成します。
サンプルとして、日付をフォーマットするディレクティブ、電話番号をバリデートするものを用意しました。

registerメソッドでは、サービスインタフェースに対して、DI(Dependency Injection)したいクラスを設定しています。コントローラの引数にインタフェースをタイプヒンティングしておけば、ここでbindされたクラスインスタンスが自動的にインジェクションされます(コンストラクタインジェクション)。

$ mkdir srcs/app/Providers
<?php namespace Project\Providers;

use Blade;
use Validator;
use Log;
use Input;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
  /**
   * Bootstrap any application services.
   *
   * @return void
   */
  public function boot()
  {
    app()->useStoragePath('/tmp/storage');

    Blade::directive('date', function($expression) {
      return "$expression ? (new DateTime{$expression})->format('Y/m/d') : trans('app.none')";
    });

    Validator::extend('tel', function($attribute, $value, $parameters, $validator) {
      $value = mb_convert_kana($value, 'n', 'utf-8');
      return preg_match("/^[0-9]+$/u", $value);
    });

  }

  /**
   * Register any application services.
   *
   * This service provider is a great spot to register your various container
   * bindings with the application. As you can see, we are registering our
   * "Registrar" implementation here. You can add your own bindings too!
   *
   * @return void
   */
  public function register()
  {
    $this->app->bind(
      \Project\Services\ServiceContract::class, \Projet\Services\Service::class
    );
  }

}
public function __construct(ServiceContract $service) // Serviceクラスのインスタンスがインジェクトされる。
{
  $this->service = $service;
}

ストレージパスが変更できているかtinkerで確認します。

tinkerはREPL(Read-Eval-Print Loop)と呼ばれるインタラクティブシェルのようなものです。Laravel環境で、小さなコードをインタラクティブに動作させることができます。ちょっとしたデバッグやテストに最適で便利です。

追記:このタイミングでstorage_path()を変更しても、storage/framework/{cache,sessions,views},storage/logsのパスには反映されないことがわかりました。

適切な位置かどうか不明ですが、bootstrap/app.phpで$appを返す前に、$app->useStoragePath('/tmp/storage')してやると有効になりました。

$ ./artisan tinker
Psy Shell v0.7.2 (PHP 5.6.19 — cli) by Justin Hileman
>>> storage_path()
=> "/tmp/storage"
>>>

個人的な小さなプロジェクトなら、直接app/にファイルを追加していってもよいのですが、チームで継続して開発するなら、予めこのようなプラクティスを決めておくと、開発効率や保守性が向上するのではないでしょうか。何かの参考になれば、幸いです。