tanaka's Programming Memo

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

Laravel5.3から5.4へのアップグレードガイドのメモ

Laravel 公式ページの Laravel5.3 から Laravel5.4 へのアップグレードガイドのメモです。

Upgrade Guide - Laravel - The PHP Framework For Web Artisans

想定時間は、1〜2時間程度とあります。GitHubの手順で利用できるようにマークダウン書式にしてみました。

# 依存関係の更新

- [ ] composer.json の変更
  - [ ] laravel/framework のバージョンを 5.4.* に書き換える
  - [ ] phpunit/phpunit のバージョンを ~5.7 に書き換える
  - [ ] 以上終えたら、 composer update で更新する
- [ ] bootstrap/cache/compiled.php があれば、フレームワークで使うことはなくなったので削除
- [ ] 以下を実行して、 Illuminate\View\Factory::getFirstLoop()を削除したことに関連するエラーを解決する。ついでに、ルートのキャッシュをクリアしておく
 ```
php artisan view:clear
php artisan route:clear
 ```
- [ ] tinker Artisan コマンドを引き続き利用したい場合は、以下で laravel/tinker パッケージのインストールが必要
composer require laravel/tinker
  - [ ] インストールしたら、config/app.php の providers 配列に、 Laravel\Tinker\TinkerServiceProvider::class を追加
- [ ] Guzzle 6.0以上が必要

# Authorization

- [ ] getPolicyFor($class) メソッドを利用していた場合、以前は引数のクラスのためのポリシーが見つからなかった場合、例外が発生していたが、 null が返されるようになったので、以下のように null のチェックを追加すること
 ```php
$policy = Gate::getPolicyFor($class);

if ($policy) {
    // code that was previously in the try block
} else {
    // code that was previously in the catch block
}
 ```

# Blade

- [ ] @sectionに渡されたインラインコンテントが、自動的にHTMLエスケープされるようになった。エスケープされていない文字列を section で描画したい場合は、いわゆる "long form" スタイルを利用する
 ```php
@section('title')
  {!! $content !!}
@stop
 ```

# Bootstrappers

- [ ] HTTPかConsoleカーネルで、 $bootstrappers 配列を手動でオーバーライドしていた場合、 DetectEnvironment を LoadEnvironmentVariables に名前変更する

# Broadcasting

- [ ] Laravel5.3で、チャンネルネームプレースホルダーとして * を使っていた場合、 5.4では、 {foo} スタイルのプレースホルダーに書き換える
 ```php
Broadcast::channel('App.User.{userId}', function ($user, $userId) {
    return (int) $user->id === (int) $userId;
});
 ```

# Collections

- [ ] every メソッドが nth メソッドに変更(Loadshのメソッド名に合わせた)
- [ ] $collection->random(1) は、新しい collection インスタンスを返すようになった。これまでは、1つのオブジェクトを返していた。引数なしで呼び出された場合のみ 単体のオブジェクトが返される

# Container

- [ ] bind や instance メソッドによって、コンテナの Alias を登録する機能が削除された。 Alias の登録は alias メソッドに変更する
 ``` php
$container->alias(FooContract::class, 'foo');
 ```
- [ ] container の make メソッドの第2パラメーターに配列パラメーターが渡せなくなったので、他の、より直感的な生成方法に変更する
- [ ] container の resolving メソッドと afterResoliving メソッドは、第1引数に クラス名か binding key を指定することが必須になった
- [ ] share メソッドが削除されたので、 singleton メソッドに変更する
 ```php
$container->singleton('foo', function () {
    return 'foo';
});
 ```

# Console

- [ ] Illuminate\Console\AppNamespaceDetectorTrait への参照があったら Illuminate\Console\DetectsApplicationNamespace に変更する

# Database

- [ ] サービスコンテナに、カスタムのデータベース接続のインスタンスを解決するキーとして、 db.connection.{ドライバーネーム} をバインドしていた場合、 AppServiceProvider の register メソッド内で、 Illuminate\Database\Connection::resolverFor メソッドで登録するように変更する
 ```php
use Illuminate\Database\Connection;

Connection::resolverFor('driver-name', function ($connection, $database, $prefix, $config) {
    //
});
 ```
- [ ] configuration ファイルで PDO の "fetch mode" が使えなくなり、 PDO::FETCH_OBJ が常に使われるようになる。今後も、 fetch mode を変更したい場合は、新設された Illuminate\Database\Events\StatementPrepared イベントを監視して、そこで設定する
 ```php
Event::listen(StatementPrepared::class, function ($event) {
    $event->statement->setFetchMode(...);
});
 ```

# Eloquent

- [ ] date キャストをすると Carbon(DateTimeを拡張するPHPのライブラリ)のオブジェクトに変換され、startOfDay メソッドが呼ばれるようになった。時間まで記録したい場合は、datetime キャストを使うこと
- [ ] リレーションを定義するときに、明示的に外部キーを定義していない場合、テーブル名と主キーの名前をモデルを関連づけるための外部キー名として使うようになった。これを変更したい場合は、該当するモデルの getKeyName メソッドをオーバーライドして、以下のようにすると、 テーブル名_key が外部キーのフィールド名として利用される
 ```php
public function getKeyName()
{
    return 'key';
}
 ```
- [ ] hasOne や hasMany のリレーションの createMany メソッドを呼び出すと、配列ではなく、 collection のオブジェクトが返されるようになったので、必要なら対応すること
- [ ] リレーションが設定されたモデルは、デフォルトのデータベース接続ではなく、親モデルと同じ接続を利用するようになった。デフォルトの接続を利用したい場合は、モデルの接続をデフォルトのものにするように明示的に設定する
- [ ] Model::create と Model::forceCreate メソッドが、マルチコネクション上でモデルを作成しやすくするために Illuminate\Database\Eloquent\Builder に移動された。独自のモデルでこれらのメソッドを拡張していた場合は、 builder 上で create メソッドを呼び出すように実装を変更すること
 ```php
public static function create(array $attributes = [])
{
    $model = static::query()->create($attributes);

    // ...

    return $model;
}
 ```
- [ ] カスタムの接続名を hydrate メソッドに渡していたら、 on メソッドを使うようにする
 ```php
User::on('connection')->hydrate($records);
 ```
- [ ] Model::hydrateRaw メソッドの名前が fromQuery に変更された。また、カスタムの接続名をこのメソッドに渡していたら、 on メソッドを使うようにする
 ```php
User::on('connection')->fromQuery('...');
 ```
- [ ] factory(User::class, 1)->make() か factory(User::class, 1)->create() を呼び出すと、アイテムが一つの collection インスタンスを返すようになったので、必要であれば対応すること。これまではモデルを1つ返していた。モデルが生成されない時のみ、単体のモデルを返す

# Events

- [ ] アプリやパッケージで、手動で Illuminate\Contracts\Events\Dispatcher を実装していたら、 fire メソッドの名前を dispatch に変更すること
- [ ] "priorities" イベントハンドラーが非対応になった。一連の同期メソッドを呼び出す方法に変更すること。他のイベントハンドラー内から、新しいイベントを dispatch することで、無関係なハンドラーの処理の後に、与えられたイベントが処理されるようにする
- [ ] Event::firing メソッドが削除された。ワイルドカードイベントハンドラーは、第1引数にイベント名を、第2引数にイベントデータを配列で受け取るようになった
 ```php
Event::listen('*', function ($eventName, array $data) {
    //
});
 ```
- [ ] kernel.handled イベントは Illuminate\Foundation\Http\Events\RequestHandled クラスを使ったオブジェクトベースのイベントになったので、必要なら対応する
- [ ] locale.changed イベントは Illuminate\Foundation\Events\LocaleUpdated クラスを使ったオブジェクトベースのイベントになったので、必要なら対応する
- [ ] illuminate.log イベントは Illuminate\Log\Events\MessageLogged クラスを使ったオブジェクトベースのイベントになったので、必要なら対応する

# Exceptions

- [ ] Illuminate\Http\Exception\HttpResponseException は、 Illuminate\Http\Exceptions\HttpResponseException に名前が変更になった
- [ ] Exception が複数形の Exceptions になったので、 Illuminate\Http\Exception\PostTooLargeException も Illuminate\Http\Exceptions\PostTooLargeException に変更になった

# Mail

- [ ] Mail::send('view.name', $data, 'Class@send') のように、 'Class@method' という文法を使っていたら、非対応になるので mailables に変更すること
- [ ] Markdownメールに対応させるため、以下の設定を mail の config ファイルに追加する
 ```php
'markdown' => [
    'theme' => 'default',

    'paths' => [
        resource_path('views/vendor/mail'),
    ],
],
 ```
- [ ] Mail::queue や Mail::later メソッドにメールメッセージとして、Closure を設定に渡す方法が非対応になるので、 mailables に変更する

# Redis

- [ ] Redis Cluster 機能を利用する場合、 config/database.php ファイルに以下の設定を追加する
 ```php
'redis' => [

    'client' => 'predis',

    'options' => [
        'cluster' => 'redis',
    ],

    'clusters' => [
        'default' => [
            [
                'host' => env('REDIS_HOST', '127.0.0.1'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 0,
            ],
        ],
    ],

],
 ```

# Routing

- [ ] Illuminate\Foundation\Http\Middleware\VerifyPostSize が Illuminate\Foundation\Http\Middleware\ValidatePostSize に変更になった
- [ ] Illuminate\Routing\Router クラスの middleware メソッドが aliasMiddleware() に変更された。HTTPカーネル内で使われているもので、影響はほぼないはずだが、もし使っているようなら対応する
- [ ] Illuminate\Routing\Route クラスの getUri メソッドが削除されたので、 uri メソッドに変更する
- [ ] Illuminate\Routing\Route クラスの getMethods メソッドが削除されたので、 methods メソッドに変更する
- [ ] Illuminate\Routing\Route クラスの getParameter メソッドが削除されたので、 parameter メソッドに変更する

# Sessions

Session について、 Symfony の SessionInterface が非対応になり、 Illuminate\Contracts\Session\Session が定義されたので、以下の対応が必要。

- [ ] 全てのSymfony の ->set() メソッドを ->put() メソッドに変更する(Laravel内ではドキュメントに記載していないこの機能は利用していないが、ユーザー側で利用していたら対応すること)
- [ ] 全てのSymfony の ->getToken() メソッドを ->token() メソッドに変更する
- [ ] 全てのSymfony の $request->setSession() メソッドを setLaravelSession メソッドに変更する

# Testing

Laravel5.4で、これまでよりもシンプルで軽量なテスト機能が実装された。

- [ ] Laravel5.3 以前のテストを引き続き利用する場合は composer require laravel/browser-kit-testing laravel/browser-kit-testing を端末で実行して、パッケージをプロジェクトにインストールする
- [ ] テストのために putenv('APP_ENV=testing') を設定することはしなくなったので、 .env ファイルの APP_ENV をテストの時に設定すること
- [ ] Event の fake である assertFired メソッドは、 assertDispatched に変更になった
- [ ] Event の fake である assertNotFired メソッドは、 assertNotDispatched に変更になった

## Laravel5.3 と 5.4 のテストを併用する

- [ ] composer.json の autoload-dev ブロックに、以下のネームスペースを追加
 ```json
"psr-4": {
    "Tests\\": "tests/"
}
 ```
- [ ] composer require laravel/browser-kit-testing として、5.3用のパッケージをインストール
- [ ] tests/TestCase.php ファイルのコピーを作成して、 tests/BrowserKitTestCase.php にする
- [ ] コピーした BrowserKitTestCase.php をエディターで開いて、 Laravel\BrowserKitTesting\TestCase を継承するように変更する。TestCase.php が 5.4 のテスト用のベースクラスで、 BrowserKitTesting.php が 5.3 のテスト用のベースクラスになる
- [ ] composer.json を開いて、以下を追加
 ```json
"autoload-dev": {
    "classmap": [
        "tests/TestCase.php",
        "tests/BrowserKitTestCase.php"
    ]
},
 ```
- [ ] 以上が完了したら、 5.3 用のテストを全て BrowserKitTestCase を継承するように変更する

# Translation

- [ ] {Inf} プレースホルダーで複数の文字列を表現していた場合、 * 文字に置き換える
 ```
{0} First Message|{1,*} Second Message
 ```

# URL生成

- [ ] Illuminate\Routing\UrlGenerator クラスの forceSchema メソッドを forceScheme に変更する

# Validation

日付フォーマットのチェックがより厳密になり、PHP の date関数内に読み込めるようになった。

- [ ] これまではタイムゾーンのプレースホルダーとして P が、全てのタイムゾーンフォーマットを表していたが、5.4 では、タイムゾーンごとに、PHP のドキュメントにある個別のプレースホルダーを持つようになった。必要であれば対応する
- [ ] addError メソッドを addFailure に変更する(Validator クラスを継承した時に変更が必要になる可能性がある)
- [ ] doReplacements メソッドを makeReplacements に変更する(Validator クラスを継承した時に変更が必要になる可能性がある)

# その他

GitHub リポジトリhttps://github.com/laravel/laravel にある変更点の確認も推奨する。具体的な変更点は、 https://github.com/laravel/laravel/compare/5.3…master で確認できるので、必要に応じてチェックすると良い。


# 5.4 で使えるようになった機能や対応したライブラリ

## 依存関係

- [ ] 5.4から、 JavaScriptのライブラリーである Axios に対応したので、グローバルで axios からライブラリーを利用できる
- [ ] Laravel Scout のバージョンが 3.0.0 になった
- [ ] Laravel Socialite のバージョンが 3.0.0 になった
- [ ] bindメソッドでコンテナにクラスを登録する際に \ で指定する方法が非対応になった。以下のように変更する
 ```php
// 変更前
$container->bind('Class\Name', function () {
    //
});

// 変更後
$container->bind(ClassName::class, function () {
    //
});
 ```

## Eloquent

- [ ] where('key', ...) として主キーの値を検索句に設定していたら、 whereKey($id) を使うことができる

## Laravel Dusk を取り入れる

- [ ] 端末で composer require laravel/dusk を実行して、パッケージをインストールする
- [ ] tests/CreatesApplication.php を作成して、以下のコードを入力
 ```php
<?php

use Illuminate\Contracts\Console\Kernel;

trait CreatesApplication
{
    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Kernel::class)->bootstrap();

        return $app;
    }
}
 ```

以上ができたら、続きは https://laravel.com/docs/5.4/dusk#installation の通りに進めれば良い。

## メールのテスト

assertSentTo メソッドは、より簡単な assertSent メソッドに置き換えることができ、コールバック内で hasTo や hasCc などのヘルパーを利用できる。

Mail::assertSent(MailableName::class, function ($mailable) {
    return $mailable->hasTo('email@example.com');
});

以上です。