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ビルトインサーバを利用することが多いです。なんといっても手軽です。

```
$ php -S 0.0.0.0:8080 -t public/
```

もちろん、Vagrantでステージングサーバを構築して、試験することも併用しています。
また、プロダクションサーバへのデプロイメントもVagrantで作成したステージングサーバで調整・検証しています。
プロビジョニングには、Ansibleを利用しています。
今後は、LaravelアプリもDockerコンテナ化して、瞬時にデプロイできるように考えています。


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

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

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

```
$ mkdir -p srcs/app/Http
```

```php:composer.json

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

```

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

```
$ composer dump-autoload
Generating autoload files
```

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

```./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
```
```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、タイムゾーン、ロケールの設定とプロジェクト固有のサービスプロバイダを一つ以上設定しています。

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

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

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

プロジェクト固有のサービスプロバイダには、ストレージパスの設定や、bladeで利用するカスタムなディレクティブ、フォームバリデーションで利用する共通のバリデータなどを定義しています。
まず、bootメソッドでストレージパスを変更しています。いままではシンボリックリンクでデプロイごとのストレージが変わらないようしていましたが、今後Dockerコンテナ化も考慮して、任意のパスに変更するようにしました。

### 注記
>このタイミングでstorage_path()を変更しても、storage/framework/{cache,sessions,views},storage/logsのパスには反映されないことがわかりました。
>適切かどうか不明ですが、```bootstrap/app.php```で```$app```を返す前に、```$app->useStoragePath('/tmp/storage')```してやると反映されました。

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

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

```
$ mkdir srcs/app/Providers
```

```srcs/app/Providers/ServiceProvider.php
<?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
    );
  }

}
```

```php:srcs/app/Controller/Service.php
public function __construct(ServiceContract $service) // Serviceクラスのインスタンスがインジェクトされる。
{
  $this->service = $service;
}
```
ストレージパスが変更できているかtinkerで確認します。
tinkerはREPL(Read-Eval-Print Loop)と呼ばれるインタラクティブシェルのようなものです。Laravel環境で、小さなコードをインタラクティブに動作させることができます。ちょっとしたデバッグやテストに最適で便利です。

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

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