tanaka's Programming Memo

プログラミングについてのメモ。

Laravel5.4の新機能

Laravel5.4が出ていたので、公式ページの新しい機能の紹介部分を読みながらのざっくりメモです。

Release Notes - Laravel - The PHP Framework For Web Artisans

マークダウンでメールとNotificationsの文面を書けるようになった

マークダウンで書いたメールや通知の文面は、 Laravel でプリビルドされて、レスポンシブな HTML テンプレートと plain textの双方が生成されるとのことで、これはとてもありがたいです。

タグに該当するものは @component()〜@endcomponent で囲みます。

  • mail::message メール文
  • mail::button 第2引数に URL を渡して、ボタンを生成
  • {{}} で PHP が実行できるので、 {{ config(‘app.name’) }} などのようにすれば設定を読み取れる

マークダウンで生成されるスタイルをカスタマイズしたい場合は、artisan コマンドの vendor:publish を実行します。この設定だけ書き出したい場合は、 laravel-mail タグを追加します。

Laravel Dusk

Webブラウザー上での動きや、 API をテストするための新しい機能です。デフォルトで使う場合は、 JDKSelenium のインストールはせず、スタンドアロンの ChromeDriver を利用します。

Webブラウザー上でテストするので、 JavaScript を利用する機能のテストも可能です。

Selenium を利用することももちろんできます。

$this->browse(関数)でテスト開始します。

Browser Tests (Laravel Dusk) - Laravel - The PHP Framework For Web Artisans

Laravel Mix

これまでは、 Gulp ベースの Laravel Elixir がビルドツールとして提供されていましたが、その Webpack 版です。

Laravel Mix は、 Laravel アプリで使われる CSSJavaScript を処理するプリプロセッサーを Webpack の APIとして提供します。

Blade コンポーネントとスロット

Vue.js にある コンポーネントとスロットと同様の機能を Blade で使えるようになりました。

コンポーネントの定義内で、スロットの位置に {{ $slot }} を書きます。

Blade内で、上記で定義したコンポーネントを @component(コンポーネント名)〜@endcomponent で利用して、その間にスロットに差し込みます。

以下、公式サイトの例です。

以下、alert コンポーネントの定義例。

<!-- /resources/views/alert.blade.php -->

<div class="alert alert-danger">
    {{ $slot }}
</div>

以下で、alert コンポーネントのスロットに「Whoops! Something went wrong!」を渡します。

@component('alert')
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

名前付きスロットを利用する場合は、 @slot(‘スロット名’)と@endslot の間に、スロットの中身を書きます。

Broadcast Model Binding

HTTPルートのように、 Broadcast の channel で、 Route Model Binding(RouteのURLセグメントに含まれるモデルのIDに該当するデータを、自動的にデータベースから取得して、ルート関数に渡す機能) と同様のルート機能を利用できるようになりました。

以下、公式サイトの例です。 Authorizing Channels の例を書き換えています。

use App\Order;

Broadcast::channel('order.{order}', function ($user, Order $order) {
    return $user->id === $order->user_id;
});

上記の元のコードは以下です。関数内で、データベースにアクセスしている部分が省略できています。

use App\Order;

Broadcast::channel('order.{orderId}', function ($user, $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

Collection Higher Order Messages

Collections (データベースから取り出したデータのオブジェクト) が、コレクション上でよく利用する共通の操作を行うショートカットを実装しました。対応したのは以下のメソッドです。

contains, each, every, filter, first, map, partition, reject, sortBy, sortByDesc, and sum.

以下、公式サイトの例です。 User モデルから votes の値が 500 より大きいデータを $users に取り出して、取得したデータごとにmarkAsVip()関数を実行する例です。

$users = User::where('votes', '>', 500)->get();

$users->each->markAsVip();

以下は、 sum の例で、 Userモデルから group が Development のデータを $users に取り出して、 取り出したデータの votes 要素の合計を求めて、 return しています。

$users = User::where('group', 'Development')->get();

return $users->sum->votes;

Object ベースの Eloquent イベント

Eloquent のイベントハンドラーがイベントオブジェクトに対応づけられました。より直感的に、 Eloquent イベントを制御したり、テストできるようになります。Eloquent モデルに $events プロパティーを定義することで、様々なライフサイクルでイベントを呼び出すことができます。

対応するイベントは Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans

Job Level Retry & Timeout

これまでの Job の リトライやタイムアウトは、コマンドラインからすべてのジョブに対して共通設定となっていました。これを、 job クラス上で、ジョブごとに設定できるようになりました。

$tries プロパティーでリトライ数、$timeout プロパティーでタイムアウトまでの秒数を指定できます。

リクエスト文字列を整理するミドルウェア

デフォルトミドルウェアに、 TrimStrings と ConvertEmptyStringToNull ミドルウェアが追加されました。これにより、入力文字列の前後の空白は削除され、未入力の場合は null になります。

Realtime ファサード

これまでは Laravel のビルトインのサービスのみがファサードとして利用できたが、ユーザーのクラスを簡単に Facade として利用できるようになった。 use Facades\ {} のブラケット内にクラス名を登録すれば、ファサードとして機能します。これを Realtime Facades と言います。

このように定義したユーザーファサードも、 shouldReceive()などのモック化機能が使えるので、テストも容易になります。

カスタム Pivot Table モデル

Laravel5.3 では、 belongsToMany() のリレーションのための pivot テーブルはすべて共通のビルトインの Pivot モデルインスタンスを利用していました。Laravel5.4では、カスタムの Pivot テーブルを定義できるようになりました。

テーブル間を結ぶ中間テーブルに独自のテーブルを指定したい場合は、 using メソッドで指定します。

Redis Cluster サポートの改良

これまでは、同じアプリケーション内で、単体のホストとクラスターへの Redis の接続を定義できませんでしたが、 5.4 では複数のホストとクラスターを一つのアプリケーション内で接続できるようになりました。

Migration Default String Length

Laravel5.4 では、絵文字に対応できるように、デフォルトの文字セットとして utf8mb4 を使うようになりました。Laravel5.3からアップグレードする際には、キャラクターセットの変更は不要です。

手動でこの文字セットに変更したい場合や、 MySQL の Ver5.7.7 より古いもので動かしている場合は、 migration によってデフォルトの文字長を設定し直す必要があります。 AppServiceProvider クラスの boot メソッド内で Schema::defaultStringLength メソッドを呼び出して設定します。

以下、公式サイトの例です。

use Illuminate\Support\Facades\Schema;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Schema::defaultStringLength(191);
}

まとめ

メールや通知のマークダウン対応はすぐにでも取り入れたいです。Laravel Dusk は、 Codeception との位置付けがどうなっているか、まだ読んでいないのでわかりませんが、SeleniumJDKのインストールをせずに簡単なテストが可能になったのはありがたいです。PHPUnit のバージョンが変わって、メソッド名を変更しないと動かないことがあるようなので、その辺はアップグレードガイドの方で確認する必要があるようです。データベースの文字セットの変更もチェックが必要そうです。

すでに Elixir で環境を構築していたら不要とは思いますが、Webpack を採用する場合、 Laravel Mix へ移行すると良さそうです。

Bladeのコンポーネントとスロット機能や、データベースを操作するショートカット、入力値の自動トリミングや 空文字列の null 値への変更、カスタムファサード機能など、良い改良が施された印象でした。


以下、新しい機能ではありませんが、 Broadcast Model Binding を読む際にまとめてしまったので。

おまけ

Route Model Binding

モデルのIDをルートやコントローラーのアクションに渡す時に、渡された ID を持つモデルを検索する場合がしばしばあります。Laravel の Route Model Binding は、作成するルートに自動的に指定のモデルのインスタンスを渡す簡単な機能を提供します。例えば、ユーザーの ID をそのまま渡すのではなく、その ID が指定する ユーザーモデルのインスタンスを渡す方法です。

暗黙的なバインディング

Laravel は、ルートやコントロールアクション内のタイプヒントの文字列から、 Eloquentモデルのフィールド名を推測して、該当するモデルデータを自動的に取得します。

Route::get('api/users/{user}', function (App\User $user) {
    return $user->email;
});

(公式ページから転載)

例えば上記の例では、引数である $user には、App\User の Eloquent モデルが渡されるように型指定(type hinted)されていて、変数名と URI セグメントの指定が一致する {user} の場所の ID がモデルの特定に利用されます。 Laravelは自動的にリクエスト URI で渡される値からユーザーのIDを検索して、そのモデルのインスタンスを自動的に引数として渡します。もしデータベースで指定のIDが見つからなかった場合は、 404 HTTP レスポンスが自動的に生成されます。

## キー名のカスタマイズ

id 以外のフィールドでマッチさせたい場合は、Eloquentモデルの getRouteKeyName メソッドをオーバーライドします。

/**
 * モデルを検索するルートのキーとして slug を利用したい場合
 */
public function getRouteKeyName() {
    return 'slug';
}
明示的なバインディング

Router の model メソッドを使って、明示的に指定のクラスを登録することができます。明示的なモデルのバインディングの登録は、RouteServiceProviderクラスの boot メソッドに書きます。

以下、公式ページの例です。

public function boot()
{
    parent::boot();

    Route::model('user', App\User::class);
}

次に、{user}パラメーターを含んだルートを定義します。

Route::get('profile/{user}', function (App\User $user) {
    //
});

全ての {user} パラメーターは App\User モデルにバインドされて、User インスタンスがルートで渡されます。例えば、 profile/1 でリクエストされたら、 ID が 1 のユーザーモデルのインスタンスがコールバックの引数の $user に渡されます。

ID が見つからなかった場合は、 404 HTTP レスポンスが自動的に生成されます。

## 解決方法のカスタマイズ

独自に渡されたパラメーターからモデルを取得するロジックを組みたい場合は、 Route::bind メソッドを使うと良いでしょう。bind メソッドに渡した Closure で URI セグメントの値を受け取って、ルートに渡すクラスのインスタンスを返すようにします。

以下、公式サイトの例です。

public function boot()
{
    parent::boot();

    Route::bind('user', function ($value) {
        return App\User::where('name', $value)->first();
    });
}