PHP

Laravel 5.5正式版がリリース、新機能をまとめてみた

2017/09/05

Christopher Vundi

66

Articles in this issue reproduced from SitePoint
Copyright © 2017, All rights reserved. SitePoint Pty Ltd. www.sitepoint.com. Translation copyright © 2017, KADOKAWA CorporationJapanese syndication rights arranged with SitePoint Pty Ltd, Collingwood, Victoria,Australia through Tuttle-Mori Agency, Inc., Tokyo

人気PHPフレームワーク「Laravel」の新バージョン「Laravel 5.5」が8月30日にリリースされました。新機能と従来との違いを紹介します。

Laravel 5.5を使用するにはPHP 7.0以上が必要です。PHPの新機能は、Learn PHP 7, Find out What’s New, and Moreにまとめています。

Laravel Logo with 5.5 juxtaposed

Laravel 5.5は次期LTS(Long Term Support)リリースに該当し、2年間のバグ修正と3年間のセキュリティ更新が保証されています。Laravel 5.1もLTSでしたが、2年のバグ修正期間が今年で終了します。

Laravel 5.5の機能を紹介します。

Laravel 5.5で新しいプロジェクトを作成

本記事執筆時点では正式版はリリースされていないため、開発者バージョンを次のコマンドでインストールします(日本版編注:8月30日にリリースされた)。

laravel new laravel55 --dev
cd laravel55
php artisan key:generate

Laravelインストーラーの代わりにComposerも可能です。

composer create-project --prefer-dist --stability=dev laravel/laravel:dev-master
cd laravel
php artisan key:generate

新しく作成したアプリのホームページに移動すると、既存のLaravelに類似したWelcomeページが表示されます。

Mailableをブラウザーにレンダリングする

LaravelでEmailテンプレートをテストするには、実際にメールを送信するかMailtrapをはじめEmailクライアントが必要でした。しかしLavel5.5では、Emailのレイアウトをブラウザーに直接レンダリングできます。

この機能を試すために、現在のプロジェクトにMailableとEmailテンプレートを作成します。

php artisan make:mail Welcome --markdown=emails.welcome

コンテンツが用意されているテンプレートからMarkdownを使います。web.phpを開いてRouteを作成し、Emailのレイアウトを表示します。

routes/web.php
Route::get('/email', function () {
    return new App\Mail\Welcome();
});

Route/emailに移動すると、Emailテンプレートのプレビューが表示されます。

Email Preview

Laravel 5.5のMailableクラスは、render()メソッドを持つRenderableコントラクトを継承しています。lluminate/Mail/Mailable.phprender()メソッドは次の通りです。

lluminate/Mail/Mailable.php

public function render()
{
    Container::getInstance()->call([$this, 'build']);

    return Container::getInstance()->make('mailer')->render(
        $this->buildView(), $this->buildViewData()
    );
}

このメソッドを使ってプレビューを表示します。もしRenderableコントラクトを継承しないクラスのインスタンスを先ほどのRouteで返すと、UnexpectedValueExceptionが発生します。

Emailテーマのカスタマイズ

LaravelでEmailにMarkdownを使うとデフォルトのテーマが使用されますが、自社のブランドイメージに合ったEmailテンプレートも作成できます。

特定のMailableテーマをカスタマイズするは、スタイルを.cssファイルに記述します。

touch resources/views/vendor/mail/html/themes/custom.css

ファイル名をMailableクラスのプロパティに指定します。

app/Mail/Welcome.php
class Welcome extends Mailable
{
    protected $theme = 'custom';
    [...]
}

custom.cssに記述したスタイルのEmailをレイアウトします。この手法は、Mailableごとテーマを変えられるメリットがあります。

例外のヘルパー関数

Laravel 5.5には例外のヘルパー関数throw_ifthrow_unlessメソッドがあり、簡潔にコードを記述できます。どちらも引数は3つで、最後の引数は省略可能です。

実際の使用例を紹介します。

$number = 2;
throw_if($number !== 3, new NotThreeException('Number is not three'));
// or
throw_if($number !== 3, NotThreeException::class, 'Number is not three');

throw_ifを使うと、最初の引数がTrueのときに例外が発生します。

throw_unlessも同様に使えますが、throw_ifとは異なり、最初の引数がFalseのときに例外が発生します。

$number = 2;
throw_unless($number === 3, new NotThreeException('Number is not three'));
// or
throw_unless($number === 3, NotThreeException::class, 'Number is not three');

あまり良い例ではありませんが、使い方が分かると思います。

migrate:freshコマンドを使う

データベースの再構築は、前バージョンでは、php artisan migrate:refreshコマンドを使いました。migrate:refreshコマンドは、migrationファイルごとdownメソッドに指定した内容に基づき、すべてのmigrationをロールバックして、再度migrationを実行します。

Migrate Refresh

しかし外部キー制約があったりdown()メソッドに不備があったりすると、問題が発生し、手作業でテーブルを削除します(CLIか適当なGUIを使って)。新しいmigrate:freshコマンドなら、すべてのテーブルを削除して既存のmigrationをすべて再実行します。

Migrate Fresh

JSONエラーのスタックトレース

前バージョンではAPI構築中にエラーが発生するたび、PostmanをはじめAPIクライアントからHTML形式でエラーが表示されました。Laravel 5.5ではHTMLではなくJSONスタックトレースで表示されるので、読みやすくなりました。

JSON stack trace

パッケージディスカバリーの自動化

Laravelプロジェクトでサードパーティのパッケージを使用する手順です。

  • パッケージをインストール
  • パッケージのサービスプロバイダーを登録
  • Facadeを登録(該当すれば)

Laravel 5.5では、パッケージディスカバリーの自動化を使い、パッケージをrequireするだけで使用できます。ただしパッケージのプロバイダーが自動化に対応している必要があります。

自動化に対応済みのDebugbarパッケージは、composer.jsonextraセクションが追加されています。

"extra": {
    "laravel": {
        "providers": [
            "Foo\\Bar\\ServiceProvider"
        ],
        "aliases": {
            "Bar": "Foo\\Bar\\Facade"
        }
    }
}

パッケージのプロバイダーは、composer.jsonextraセクションを追加して、providersaliasesを指定します。

パッケージディスカバリーの自動化には、依存オブジェクトを削除しても壊れないメリットもあります。通常はパッケージをアンインストールしてもパッケージのサービスプロバイダーとFacadeはconfig/app.phpに残り、問題を引き起こすことがあります。

自動化なら、パッケージをComposerで削除したときに、パッケージに関連するすべてを削除します。

vendor:publishコマンドの変更点

前バージョンでvendor:publishコマンドを実行すると、MigrationやView、Configを含むパッケージのリソースすべてとフレームワークの一部のリソースが公開されました。

Laravel 5.5では、公開対象を指定してこのコマンドを実行します。フラグを渡さずにphp artisan vendor:publishを実行すると、プロバイダーかタグの選択を求められるので、公開する対象を指定します。詳しくは次のスクリーンショットを見てください。

vendor:publish prompt

publishコマンドに-all--providerフラグを付けて実行すると、先ほどのプロンプトは表示されません。

php artisan vendor:publish --all

フロントエンドプリセットの品ぞろえ

Laravel 5.3と5.4では、フロントエンド向けにデフォルトでVueBootstrapのスキャフォールドがありました。5.5ではデフォルトではありませんが、Reactが加わりました。

フロントエンドのプリセットを管理するartisanコマンドも追加され、使いたいスキャフォールドだけプリセットできます。Vue、Bootstrap、Reactなどのデフォルトのプリセットではなく、ほかのフロントエンドフレームワークを使いたい場合は、次のコマンドを使用します。

php artisan preset none

このコマンドを実行すると、既存のフロントエンドスキャッフォールドがすべて削除されます。たとえばReactでフロントエンドを開発したければ、次のコマンドでReactのスキャッフォールドを入手します。

php artisan preset react

このコマンドを実行したときのスクリーンショットです。

Frontend Presets

Whoopsが戻ってきた!

Laravel 5.5になってWhoops!が戻ってきて、エラー表示の方法が変わりました。Whoops!は開発途中でエラーが起こると、エラーになったコードの行をスクリーンショットとエラーメッセージで表示します。

新しいハンドラ―が追加されて、読みやすいエラーメッセージになりました。エラーになったコードの行をスクリーンショットで確認できるので、デバッグしやすくなりました。

whoops error

Whoopsが表示するエラーの例

Whoopsには、関連するファイルをIDEかエディターで直接開く機能があります。エディターがインストールされているマシンからPHPのソースファイルに直接アクセスできる必要があります。app/Exceptions/Handler.phpを開いて、次のコードを追加します。

app\Exceptions\Handler.php
[...]
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Whoops\Handler\PrettyPageHandler;
[...]
class Handler extends ExceptionHandler
{
[...]
    protected function whoopsHandler()
    {
        return tap(new PrettyPageHandler, function ($handler) {
            $files = new Filesystem;
            $handler->setEditor('sublime');
            $handler->handleUnconditionally(true);
            $handler->setApplicationPaths(
                array_flip(Arr::except(
                    array_flip($files->directories(base_path())), [base_path('vendor')]
                ))
            );
        });
    }
}

$handler->setEditor('sublime')の行は、ベースクラスのwhoopsHandler()メソッドをオーバーライドして、リンクをSublime Textで開きます。ほかのエディターを使っているなら、ここでサポートされているエディターの一覧と追加方法を確認できます。また、iOSで使うにはSublime URLプロトコルのダウンロードが必要です。

例外レポートメソッドのカスタマイズ

以前のバージョンでは、特定の方法で発生させたLaravelのカスタムな例外を処理するには、ロジックをHandler.phpreportメソッドに記述しました。下記は一例です。

app/Exceptions/Handler.php
[...]
public function report(Exception $exception)
{
    if ($exception instanceof CustomException) {
        // Do something
    }

    if ($exception instanceof MyOtherException) {
        // Do something
    }

    if ($exception instanceof MyOtherCustomException) {
        // Do something
    }

    return parent::report($exception);
}
[...]

仮にカスタム例外が50なら、乱雑なコードになるでしょう。

Laravel 5.5は、カスタム例外を処理するreport()メソッドを例外の中に記述できます。

app/Exceptions/CustomException.php
[...]
class CustomException extends \Exception
{
    public function report()
    {
        // send email
    }
}

[...]

ファクトリージェネレーターのモデル

Laravel 5.5で、モデルファクトリーを作成するコマンドが追加されました。モデルファクトリーは、テストにおけるフェイクデータの生成や、あるオブジェクトのインスタンス作成に便利です。

特定のクラスのファクトリーを作成するには、次のコマンドを実行します。

php artisan make:factory Post

database/factoriesに移動すると、PostFactoryクラスが表示されます。

database/factories/PostFactory.php
[...]
$factory->define(App\Post::class, function (Faker $faker) {
    return [
        //
    ];
});

関心の分離に従ったこのアプローチは、旧来より優れています。前バージョンでは、ファクトリーはすべてapp/factories/ModelFactory.phpに記述していました。

バリデートデータを返す

Laravel 5.5は、バリデートメソッドから受け取ったデータを、createメソッドに渡せます。これまでのLaravelでは、新しいオブジェクトを作成していました。

public function store()
{
    $this->validate(request(), [
        'title' => 'required',
        'body' => 'required'
    ]);

    // return Post::create(request()->only(['title', 'body'])); or
    return Post::create(request()->all());
}

Laravel 5.5では、バリデートされたデータから直接オブジェクトを作成できます。

public function store()
{
    $post = $this->validate(request(), [
        'title' => 'required',
        'body' => 'required'
    ]);

    return Post::create($post);
}

Laravel 5.5では、requestインスタンスからvalidateメソッドを直接実行できます。

public function store()
{
    $post = request()->validate([
        'title' => 'required',
        'body' => 'required'
    ]);

    return Post::create($post);
}

この方法でオブジェクトを生成すると、バリデートメソッドで初期化していない属性は値を持ちません。作成するオブジェクトのすべての属性を初期化するために、バリデートが不要でも、バリデートメソッドにすべての属性に対する値を渡します。

$post = request()->validate([
        'title' => 'required',
        'body' => 'required',
        'notRequiredField' => '',
    ]);

return Post::create($post);

バリデーションルールに縛られることなく、許可されたrequestデータにフィールドを自動で追加します。

バリデーションルールのカスタマイズ

ルールとメッセージをAppServiceProviderファイルとresources/lang/en/validation.phpファイルに分けて記述して、Validator::extendメソッドを使えば、前バージョンでも可能でした。LaravelドキュメントにLaravel 5.4で実装する方法が記載されています。

Laravel 5.5では、カスタムバリデーションを定義するartisanコマンドが追加されました。Ruleコントラクトを実装する新しいクラスを作成します。新しいルールを追加して、作成されるファイルの中身を確認します。

php artisan make:rule CustomRule

app/Rules/CustomRule.phpには、passesメソッドとmessageメソッドがあります。passesメソッドの引数はattributevalueで、booleanを返します。$attributeはバリデートするフィールドで、$valueはその属性に渡す実際の値です。

例として、アプリが特定の名前を受け付けないようにします。Ruleは以下の通りです。

app/Rules/CustomRule.php
class CustomRule implements Rule
{
    [...]
    public function passes($attribute, $value)
    {
        return $value !== 'unwantedname';
    }

    public function message()
    {
        return 'You cannot use that as your username';
    }
    [...]
}

新しいRuleでusername属性をバリデートします。

app/Rules/CustomRule.php
use App\Rules\CustomRule;

request()->validate([
    'username' => [
        'required',
        new CustomRule()
    ],
    'anotherfield' => 'required|min:5'
]);

Taylor Otwellの記事で、Laravel 5.5でカスタムバリデーションを定義する方法が詳しく紹介されています。

Collectionに追加されたDDとDump

Collectionにdump()メソッドとdd()メソッドが追加されました。これまでのLaravelはCollectionのデバッグで、変数を格納したCollectionが変更されるたびに、変数をダンプしました。Laravel 5.5では、直接dd()dump()をCollectionから呼び出せるので、デバッグしやすくなりました。

PostのCollectionが変更されるたびに中身を確認する方法は以下の通りです。

 $posts = Post::all();

 $posts
    ->dump()
    ->sorBy('title')
    ->dump()
    ->pluck('title')
    ->dump();

以下のように出力されます。

Collection {#284 ▼
    #items: array:3 [▼
        0 => Post {#285 }
        1 => Post {#286 }
        2 => Post {#287 }
    ]
}

Collection {#272 ▼
    #items: array:3 [▼
        0 => Post {#285 }
        2 => Post {#287 }
        1 => Post {#286 }
    ]
}

Collection {#268 ▼
    #items: array:3 [▼
        0 => "Aida Bosco"
        1 => "Madge Leuschke"
        2 => "Miss Bulah Armstrong Jr."
    ]
}

Collectionの中身を簡単に確認できます。ただし、dump()dd()の違いは、dump()は最新の結果を出力して処理を継続しますが、dd()は処理を停止して結果をダンプします(ddはdump and dieの略です)。Collectionからdd()を繰り返し呼び出しても、1回目のdd()だけが結果を出力します。次のコードを見てください。

 $posts = Post::all();

 $posts
    ->dump()
    ->sorBy('title')
    ->dd()
    ->pluck('title')
    ->dump();

以下のように出力されます。

Collection {#284 ▼
    #items: array:3 [▼
        0 => Post {#285 }
        1 => Post {#286 }
        2 => Post {#287 }
    ]
}

array:3 [▼
    0 => Post {#285 }
    2 => Post {#287 }
    1 => Post {#286 }
]

多対多をキャストするPivotテーブル

通常は属性の保存と読み込み方法を定義するModelにcastsプロパティを宣言します。PostModelと数個のフィールドを考えます。フィールドの1つは読み込みと書き込み時にJSONをシリアライズします。以下のコードで実装します。

class Post extends Model
{
    [...]
    protected $casts = [
        'somefield' => 'array',
    ];
    [...]
}

Laravel 5.4から読み込みの場合のみ多対多の関係にあるカスタムPivotのキャストも可能です。書き込みは、属性の値を手動でキャストしてから保存します。Laravel 5.5では、Eloquent\Relations\Pivotクラスの$castsプロパティにEloquent\Modelクラスと同様にattach、sync、saveメソッドが追加されました。

Blade::if()句のカスタマイズ

Bladeテンプレートに長いチェックを繰り返し書くと、テンプレートが乱雑になります。Laravel 5.5では、繰り返し実行するチェックを抽象化してテンプレートから分離できるため、テンプレートが読みやすくなります。

@if (auth()->check() && auth()->user()->isSubscribed())
    <p>Subscribed</p>
@else
    <p>Not Subscribed</p>
@endif

このコードは次のように書き換えられます。

@subscribed
    <p>Subscribed</p>
@else
    <p>Not Subscribed</p>
@endsubscribed

カスタムBlade句を作成するロジックは、AppServiceProviderクラスのbootメソッドに定義します。

app/Providers/AppServiceProvider.php
[...]
use Illuminate\Support\Facades\Blade;

class AppServiceProvider extends ServiceProvider
{
    [...]
    public function boot()
    {
        Blade::if('subscribed', function () {
            return auth()->check() && auth()->user()->isSubscribed();
        });
    }
    [...]
}

チェックに引数が必要なら、カスタムBlade句を定義するときに、引数をクロージャーに渡します。

@if (auth()->check() && auth()->user()->isFollowing($user->id))

上記の例では、$user->idisFollowing()メソッドに渡します。$user->idを引数に取るカスタムBlade句は、次のコードで定義します。

Blade::if('following', function (User $user) {
    return auth()->check() && auth()->user()->isFollowing($user->id)
});

Bladeをテンプレートで使用します。

@following($user)
    <p>Following</p>
@else
    <p>Not Following</p>
@endfollowing

新しいArtisanコマンドをカーネルに自動登録する

新しいartisanコマンドを作成するには通常php artisam make:command command-nameを使います。コマンドを実行後に、コマンドのクラスにシグネチャーを宣言し、カーネルに移動して手動でコマンドを登録します。

Laravel 5.5では、コマンドディレクトリを検索してファイルパスをすべてnamespace付きのパスに変換するメソッドがapp/Console/kernel.phpに加わったため、新しいコマンドのカーネル登録が必須ではなくなりました。

[...]
protected function commands()
{
    $this->load(__DIR__.'Commands');

    require base_path('routes/console.php');
}
[...]

カーネルに登録されていないコマンドを参照しても、commands()メソッドが自動でコマンドを解決します。

新しいRouteメソッド

目玉機能ではありませんが、新しいRouteメソッドが2つ加わりました。

Route::view('/welcome', 'welcome');
Route::redirect('home', 'dashboard');

1つ目のメソッドはwelcome view/welcome pathにマップし、2つ目のメソッドは/homeへのリクエストを/dashboardにリダイレクトします。

Laravel Horizonの導入

新しいLaravelのパッケージで、Laravel Redis Queueのダッシュボードとコードで使う設定システムです。

Horizon Dashboard

Horizonを使うと、リアルタイムでQueueのワークロード、最近のジョブ、失敗したジョブ、ジョブの再実行、プットやランタイムのメトリックス、プロセス数などの指標で監視できます。

Horizonの機能です。

  • ハイレベルなジョブの分析。たとえば、分あたりのジョブ数や過去1時間に実行されたジョブ数
  • ジョブとQueueに特化した分析
  • タグとモニタリング。ジョブにタグを付けてモニタリングする
  • 最近のジョブ。直近に実行したジョブの情報を取得する
  • Queueバランス戦略。Queueのワークロードに合わせて自動的にワーカープロセスの負荷を調整する

Horizonの設定や機能はTaylor Otwellの記事に書かれています。私たちもHorizonの記事を書く予定です。

新しいデータベースマイグレーションのトレイト

RefreshDatabaseのトレイトが必要なのか疑問に思う人もいるかもしれませんが、導入された理由には説得力があります。もともと、DatabaseMigrationsDatabaseTransactionsのトレイトがありました。

テストでDatabaseMigrationsトレイを使うと、テスト前にマイグレーションが実行されたことを保証できます。DatabaseTransactionsトレイトを使うと、テスト後にデータベースが初期状態に復元されたと保証できます。

新しいRefreshDatabaseトレイトは両方の機能を兼ね備えます。テスト開始時にデータベースをマイグレートし、トランザクション付きでテストを実行します。テストのたびにデータベースをマイグレートする必要がなくなり、テストを高速化できます。

最後に

紹介したとおり、魅力的な機能がたくさん追加されました。まだ正式版はリリースされてないので、アップグレードガイドが公開されるまで待ちましょう(日本版編注:8月30日に正式版がリリースされた)。

(原文:What Are the New Features in Laravel 5.5?

[翻訳:内藤 夏樹/編集:Livit

Copyright © 2017, Christopher Vundi All Rights Reserved.

Christopher Vundi

Christopher Vundi

Andelaに勤務するソフトウェア開発者です。RailsとLaravelの使用経験があり、役立つ情報をブログで発信しています。旅行好き。

Loading...