Aim Developer

i.e. blog

この記事について

Laravelについて社内で共有するためのものです。デフォルトのディレクトリ構成やどんな機能があるかを説明しています。

一応LTSのLaravel5.5準拠で書いてますが、自分の理解をベースに書いてるので何か間違ってるかもしれないです。見逃すかやんわり教えてください。

Lumenも同じようなものですが、違いについても少し言及できればいいなーとは思ってます。

デフォルトのディレクトリ構成

デフォルトで大体こんな感じです。

├── app
│   ├── Console
│   ├── Exceptions
│   ├── Http
│   │   ├── Controllers
│   │   └── Middleware
│   ├── Models
│   └── Providers
├── bootstrap
│   └── cache
├── config
├── database
│   ├── factories
│   ├── migrations
│   └── seeds
├── public
│   ├── css
│   └── js
├── resources
│   ├── assets
│   │   ├── js
│   │   └── sass
│   ├── lang
│   │   └── en
│   └── views
├── routes
├── storage
│   ├── app
│   │   ├── protos
│   │   └── public
│   ├── framework
│   │   ├── cache
│   │   ├── sessions
│   │   ├── testing
│   │   └── views
│   └── logs
├── tests
│   ├── Feature
│   └── Unit
├── vendor
└─  .env

大事なところから解説します。名前から自明なところは省きます。

.env

ディレクトリ構成と言いつつ早速ファイルです。

Laravel5のconfigurationは環境変数を利用してます。この .envファイルでは環境変数を定義します。以下のような感じです。

APP_NAME=Laravel
APP_ENV=local

この値が環境変数としてセットされ、フレームワークから読まれます。フレームワークからは.envファイルのみ読み取るので

などのように環境ごとに用意して、デプロイのタイミングで .envファイルを置き換えるのが一般的じゃないかなと思います。

database

factories

ほぼテストのためのものだと思っています。テストで使いたいDBのレコードを生成するとき何かに使えます。

migrations

DB Schemaを管理します。変更内容を1つずつ残してどこまで実施したかなど管理してます。設計としてはFlywayに近いんじゃないかと思います。以下のような感じです。

class CreateFlightsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->increments('id'); // auto_incrementがつくので自動的にPKとして設定される
            $table->string('name'); // varchar. デフォルトのlengthは255
            $table->string('airline');
            $table->timestamps(); // created_at, updated_atというdatetimeカラムを作成 timestampだったっけな…?
        });
    }

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

upはmigration時、downはmigrationのrollback時に呼ばれます。ここでのrollbackっていうのは、migrationの状態を1つ前の状態に戻すことを言います。クエリのTransactionとは関係ありません。

seeds

ある決まったデータセットを生成するための機能です。例えば、DBのマスタデータ的なデータを一括で生成したりとか、そういうのに使えます。

routes

以下のような感じでWebなどのルーティング定義を書きます。

middlewareについては後述します。

Route::middleware(['AuthenticationMiddleware'])->group(function () {
    Route::get('/', function () {
        // do something
    });

    Route::get('user/profile', 'UserController@profile');
});

get, post, put, delete (patchもあった気がする) でHTTPメソッドを定義し、記述したURLパターンに沿ってクロージャで処理を書いたり、処理を行うController@メソッド を記述します。

Route::resources という指定方法もあり、RESTに対応した一覧取得、単体取得、作成画面、作成処理、更新、削除 のRoutesを定義したのと同様の動きをします。その時にハンドリングされるメソッド名がそれぞれ index, show, create, store, update, destroy なので、僕が手動でControllerを作るときにもこのメソッド名を使うようにしています。

app

アプリケーションのコードを書くエリアです

Console

バッチ処理とかCUI処理を書くところです。

Http

Web系の処理を置くところです。CUI側と処理を共通化させるため、ビジネスロジックなどはここに置かない方が良いでしょう。

Http/Controllers

いわゆるControllerです。

Http/Middleware

Controllerの前に行いたい処理を書きます。例えばAuthenticationMiddlewareというのを作ったとします。

class AuthenticationMiddleware extends 
{
    public function handle($request, Closure $next, $guard = null)
    {
        // check authentication
        if (authenticated()) {
            return $next($request);
        }
        return redirect('/login');
    }
}

のようなイメージです。 ログイン済みかどうかを判定して、ログイン済みであれば次のミドルウェアに処理を流します。 次のミドルウェアがなければControllerに処理が移ります。 ログイン済みでなければログイン画面にリダイレクトしてるイメージです。

ミドルウェアとして処理を抽出できるので、例えばログインしているかどうかなどの判定をControllerの責務から外すことができ、共通化できます。

次のミドルウェアと書いたように、ミドルウェアは任意の順番で複数置くことができます。例えばAPIアクセス(i.e. Content-Type: application/json)かどうか、など。

Http/Requests

Httpのフォームリクエスト処理のハンドリングをするレイヤーです。デフォルトでは存在しませんが、コマンドから自動生成することができます。以下のようなものが生成されます。

class TestRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

通常、Controllerのroutingに対応したメソッドには\Illuminate\Http\Requestを渡して(F/Wがメソッドインジェクションしてくれる)httpのリクエスト値を取得したりします。 フォームリクエストの場合、フォームに対応したRequestをクラスを作り、そのクラスをメソッドの引数に指定すると、自動的に記述したルールに沿ってバリデーションして通ったものだけControllerに処理が渡ります。つまり、Controllerからフォームのバリデーション責務を外すことができます。

Lumenにはこの機能は無い(はず)ので、通常Controllerでvalidationを行います。 こういう便利機能はパフォーマンスとトレードオフになる部分もあるので、Lumenにはこういう便利機能が存在しません。

Models

ActiveRecordのモデル層のようなものです。こんな感じでusersテーブルにアクセスできます。

$user = User::find(1);
$users = User::limit(10)->get();

$usersはCollectionとして返ってきます。Collectionは様々なarray関数のラッパーのようなものです。

PHPはよくarray_filterとかarray_mapの引数の順番が違って気持ち悪いとか言われますが、C言語に合わせているからそうなっている、そうです。いい感じに書きたいならフレームワークでラップしてくれ、という思想なので、このCollectionなんかはそれを体現しています。こんな感じになります。filter, mapなどもCollecitonを返すのでメソッドチェーンできます。

// collectionsのfilter, map
$filtered = $users->filter(function (User $user) {
    return !$user->isSomething();
});

$userNamesWithSuffix = $users->map(function (User $user) {
    return $user->name . ' さん';
});

LaravelではデフォルトではEloquentというORMを使っています。EloquentはQueryBuilderという層をラップしています。 Eloquentを使うと遅くなったり、論理削除とか余計なものもついてくるので使わないで、QueryBuilderを直接使う派もあります。

relationが便利なので僕は使うことが多いです。

Lumenだと明示的に有効にしないと使えなかったはずですが、有効にするとAPIが10ms遅くなります。

Providers

LaravelがLaravelとして動くために最も重要ですが最も分かりづらい層です。ただフレームワークを使いたいだけならばそんなに意識しなくても良いです。

例えばLaravelだとこんな風に書くと


class FooController extends Controller
{
    public function index($request)
    {
        $value = Cache::get('key');
    }
}

Cacheストレージからkeyに対応した値を取得できます。 CacheストレージがRedisだろうとMemcacheだろうとDBだろうとこれで取ってこれます。設定ファイルでどのキャッシュエンジンにするか指定しておくだけでいい感じにしてくれます。

ものによって違いはありますが、このCache::で呼び出される「設定ファイルなどの何かしらで指定された内容をもとにクラスインスタンスを作る」ような層が、このProviderです。フレームワーク上ではService Providerと呼ばれています。

なお、Cache::get のように一見staticメソッドで呼ばれてるけど裏側で作られているクラスインスタンスのメソッドをコールするような処理をFacadeと呼びます。GoFのデザパタのFacadeとは感覚が違うので注意が必要です。

このFacadeはstaticメソッドのような感覚で呼べるので、Cache、Session、その他いろいろ好きな場所で好きなように呼べてしまうので綺麗に設計しようとすると邪魔になります。うかつにFacadeを使うと怒る人(Facade警察)もいます。

Facadeはユニットテスト時にモックに差し替えることもできるため、staticメソッドのような見た目なのにモックを使ったテストができます。 ちなみにFacadeもLumenではオプションです。有効にすると10ms以上遅くなります。

設計としてはFlyweightとProxyを合わせたようなものです。

生成したインスタンスはApplicationというクラスで管理されますが、ApplicationはContainerを継承しているのでLaravelは基本的にContainerだと言えそうです?

bootstrap

フレームワークを開始するためのスクリプトが入ります。Laravelでは触る必要はそんなに無いはずです。

Lumenではbootstrap/appで使う機能とか使わない機能とかいろいろ設定します。Lumenではパフォーマンスのためにファイルを分散させないようにしてあるので、ファイルを分割するのは良くないでしょう。


とまぁ、デフォルトでこういうものが用意されていますが、使うのも使わないのも自由です。

フレームワークのブレーキングチェンジへの対応が難しくなるのでapp以下を使わない人もいます。

以上!何かあれば追加します。