M.Y. 雑感ブログ

札幌からWebの技術などを発信しています。

Laravel 5.4 と 5.5 の違い

数日前、 Laravel 5.5 がリリースされた。

5.4 との違いを

を見ながら注目すべき点をまとめる。

LTS

5.5 は 5.1 に続く新しいLTSバージョンである。

2019/08 までのバグフィックスサポート、 2020/08 までのセキュリティサポートが受けられる。

オフィシャルパッケージ Horizon 追加

Laravel の Redis キューの監視・管理が行えるダッシュボードを提供する Laravel Horizon が提供された。

簡単な設定と supervisor プロセスの追加で、すぐに導入出来る。

※PHP7.1でなければ動かないなので注意

PHP5.6 の切り捨て

このバージョンから、 PHP7.0 以上でなければ動作しなくなる。

これは composer.json"require": {"php": ">=7.0"} と書くことで実現している。

$ composer install --ignore-platform-reqs

というオプションでインストールすることでこの制約を無視することはできるが、PHP7記法を用いたクラスでシンタックスエラーになるので実質このオプションをつける意味はない。

phpunit 6 アップデート

2017年9月現在での Current Stable Release である PHPUnit 6 へアップデートされた。

こちらもPHP7.0以上でのみ動く。

PHPUnit 6 のリリースノートにもある通り、

  • 名前空間が利用されるようになり、例えば PHPUnit_Framework_TestCase クラスは PHPUnit\Framework\TestCase に変更
  • アサートがないテストメソッドをデフォルトで警告する
  • グローバル・スーパーグローバル変数はデフォルトでバックアップ・リストアされない
  • --log-junit で生成されるログのフォーマットが最新版に対応

などがあるため、そちらの対応もする必要があるかもしれない。

エラーハンドリング

whoops 再導入

Laravel 4 時代に導入されていたデバッグ用例外ページレンダラライブラリの whoops が再導入された。

  • config('app.debug') が true
  • class_exists(\Whoops\Run::class) が true(= require-dev 付きでインストールされている)

以上の場合に Whoops が利用される(ハンドリングしている部分)。

デフォルトビュー

同時に、 debug モードでない場合に表示されるデフォルトの 404, 419, 500 エラーページのビューが追加された。

元々 503(メンテナンス中) ページについては綺麗なエラーページビューが存在したが、それを 404, 419(Authentication Timeout), 500 にも適用した形だ。(該当PR)

csrfトークン不一致時の TokenMismatchException 時に 500 ではなく 419 が返るようになるので注意。

json 要求時のエラーは常に json で返すように

以前はjsonを要求しているリクエストでも、ハンドルされないエラーが起きた場合は text/html でレスポンスしていた(非デバッグ時)。

今後は非デバッグ時でもjsonが要求されればjsonで返すようになる。ajax等でjsonのパースに失敗しなくなるので嬉しい。

{
    "message": "Server Error",
    "errors": {}
}

HttpException の場合はそれらのメッセージが上記形式でレスポンスされる。 errors には詳細なエラー情報が乗ることがある(バリデーションエラー時など)。

report ヘルパー関数

ロジック処理中にエラーが発生した場合、「エラーをログに残してカスタムのエラーレスポンスを返す(リダイレクトするなど)」という処理をする場合があるが、そういった場合に「エラーをログに残す」つまり ExceptionHandler->report メソッドのみを呼ぶことが出来る report() ヘルパー関数が追加された。

<?php
class FooController
{
    public function postSome()
    {
        try {
            // ロジック
        } catch (\Exception $e) {
            report($e);
            return redirect()->back()->with('alert-error', '処理に失敗しました');
        }
        return redirect()->route('index')->with('alert-success', '処理しました');
    }
}

などのようにかけるというわけ。

個人的には親コントローラクラスにレポートメソッドを追加していたが、責務的に正しく分けられるようになった(再利用も出来ている)。

バリデーションルールクラス

これまで自分のバリデーションルールを追加する場合は Validator::extendクロージャを登録する形式だったが、需要の多さからクラス登録形式に変更された。

artisan コンソールの改善

コマンド登録の自動化

自作コマンドを追加する場合、 app/Console/Kernel.php にコマンドクラスを登録していたが、今後は app/Console/Commands ディレクトリ以下の全てのコマンドを自動で登録してくれるようになるため、不要になった。

Updateする場合は この処理の追加 が必要である。

optimize コマンドのDEPRECATED

最近のPHP op-code(中間コード的なもの)のキャッシュ改善により、毎回ロードするであろうクラスを1つのファイルにまとめて require の時間を短縮しようとする optimize コマンドが DEPRECATED になった。ビルドスクリプトの修正が必要になってくる。

ServiceProvider::compile() メソッドによる optimize 対象ファイルの指定も同様に DEPRECATED になったため、早めの削除が推奨されている。

vendor:publishインタラクティブ操作

publish するパッケージなどをインタラクティブに選択できるようになる。

migrate:fresh コマンド追加

migrate:refresh コマンドは一旦すべて down してから up するが、マイグレーションファイルの修正作業中にこのコマンドを実行した場合、または同一環境で複数ブランチのビルドをする場合などで、 down に失敗する場合がある。

この新しいコマンドでは DROP TABLE を実行してから up するようになり、 down に失敗してビルドエラーになるようなことがなくなった。

mysql -uuser -psecret test_db -N -e 'show tables' | while read table; do mysql -uuser -psecret -e "drop table $table" test_db; done
php artisan migrate

私はこうしたワークアラウンドで対応していた(恐らくそれが多かったのだろう)。

Factory クラスの make コマンド追加

テスト用のレコード作成をサポートする Factory を自動生成するコマンド make:factory が追加された。

また、モデル作成時に make:model --factory とすれば、モデルファイルと同時にFactoryも生成されるようになった。

(個人的にはstubの規約修正が面倒であまりmakeコマンド使ったことないんだよなあ)

CommandStarting CommandFinished イベントの追加

コマンド hoge を開始/終了します などのログを吐くことは多いが、全てのコマンド実装クラスに対して書くのが面倒な場合はこのイベントを購読すると良さそうだ。

View 内の @switch ディレクティブ追加

@switch($i)
  @case(1)
    <h1>1だよ</h1>
  @break

  @case(2)
    <h2>2だよ</h2>
  @break

  @default
    <p>デフォルトだよ</p>
@endswitch

という記述が出来るようになった。

(break 書くの忘れてバグりそうだから、自分としてはあまり使わないかも)

ライブラリの ServiceProvider を自動探索する

外部ライブラリをrequireして、そのサービスプロバイダを登録するには config/app.php にクラス名を追加する必要があった。

Package Discovery 機能が追加され、自分でコンフィグをいじらなくても自動で追加してくれるようになった。

(外部ライブラリ側が composer.json をいじる必要がある)

Facade のエイリアス登録も同時に出来るようになる。

Responsable インターフェース

Illuminate\Contracts\Support\Responsable というインターフェースが追加された。

これを実装したオブジェクトを、コントローラで返せば、 toResponse() メソッドで生成されるレスポンスインスタンスを利用してレスポンスすることが出来る。

例えば、複数のオブジェクトからレスポンスjsonを生成するクラスとして、

<?php
class FooController
{
    public function postSome($request)
    {
        $resultSome = $this->someService->__invoke($request->some);
        $resultOther = $this->otherService->__invoke($request->other);
        return new FooPostSomeReducer($resultSome, $resultOther);
    }
}

class FooPostSomeReducer implements \Illuminate\Contracts\Support\Responsable
{
    public function __construct($some, $other)
    {
        $this->some = $some;
        $this->other = $other;
    }

    public function toResponse()
    {
        return response()->json([
            'some' => $this->toJsonSome(),
            'other' => $this->toJsonOther()
        ]);
    }

    private function toJsonSome()
    {
        return [
             'id' => $this->some->id,
             'urls' => $this->some->urls->map(function ($url) { return $url->url; })->values()->toArray()
        ];
    }

    private function toJsonOther()
    {
        return [
            'id' => $this->other->id,
        ];
    }
}

このようなデータ整形クラスに分割することが出来る。

こうすることで「コントローラがレスポンスの生成ロジックを受け持つ」責務が分離されるので、コントローラとしてより正しい使い方が出来るように思う。

Eloquent API Resources

json で Eloquent Model を返却することはよくあるが、Model返却時の形式は統一したい場合が多い。

上記の Responsable だけでは統一出来ず、 Model に toJson メソッドを追加して対応することがあるのではないだろうか。

API用の整形処理は Model クラスで行うべきではないので、 API Resources という機能が実装された。

これは、 Model クラスのインスタンス(またはそのCollection)を json 形式に変換するためのリソースクラスである。

このクラス自体が Responsable インターフェースを実装しているので、 Controller で直接返せば良い。

リレーションやコレクション、ページネイトの場合の処理も記述出来るので、Modelのレコード情報を簡単なマッピング処理で簡単に返したい場合はこちらを利用するとコード記述量が減って良いのではないだろうか。

構造は変わってしまうが、先ほどのコードを API Resource で書き直すとすれば、こうなるだろう。

<?php
class FooController
{
    public function postSome($request)
    {
        $resultSome = $this->someService->__invoke($request->some);
        $resultOther = $this->otherService->__invoke($request->other);
        return response()->json([
            'some' => new SomeResource($resultSome),
            'other' => new OtherResource($resultOther)
        ]);
    }
}

class SomeResource extends \Illuminate\Http\Resources\Json\Resource
{
    public function toArray()
    {
        return [
             'id' => $this->id,
             'urls' => new UrlResourceCollection($this->urls)
        ];
    }
}

class OtherResource extends \Illuminate\Http\Resources\Json\Resource
{
    public function toArray()
    {
        return [
            'id' => $this->id,
        ];
    }
}

class UrlResourceCollection extends \Illuminate\Http\Resources\Json\ResourceCollection
{
    public function toArray()
    {
        return [
            'data' => $this->collection->map(function ($url) { return $url->url; })->values()->toArray()
        ];
    }
}

ただし、

  • 複数の Model インスタンスやコレクションを一つの情報にまとめる Reduce 処理
  • 特定のAPIでしか使わない整形処理
  • Model に関連しない情報が多い

といったAPIレスポンスの場合は、上記のように自分で Responsable のクラスを実装した方が良いだろう。あくまで「特定の Model を json で表す場合はこうである」という定義のためのクラスである。

RESTful API などを構築している場合はかなり有用ではないだろうか。


その他細かいものが諸々。

責務分離に最近重点を置いてソースコードやインターフェースを見ているが、Laravelは定義済みのアプリケーション層が薄いので、こういった薄い層でさらに分離を推し進められる改善は嬉しいなあ。