新機能とLaravel 8からLaravel 10へのアップグレード方法

新機能とLaravel 8からLaravel 10へのアップグレード方法

Laravel 8のプロジェクトをなぜアップグレードするべきか

Laravelの新バージョンは毎年リリースされます。これらのリリースには多少の変更が含まれ、新しい機能が追加されたり、不要な機能が削除されたりします。そのため、早めにアップグレードを行わないと、アップグレードが困難で時間がかかる可能性があります。アップグレードが困難になる主な理由は、プロジェクトが成長し、関数やメソッドが相互に依存するようになることです。つまり、1つの機能を変更すると他の機能にも大きな影響を与える可能性があります。また、Laravel 10がリリースされた後は、Laravel 8のメンテナンスやサポートが終了するため、できるだけ早くアップグレードすることが重要です。

この記事の主な内容は、Laravel 10へのアップグレード方法を案内します。しかし、本記事では手動でのアップグレード方法を紹介しており、Laravel Shiftのような自動化ツールを使用する方法ではありません(このツールは有料です)。ご了承ください。

Laravel 10の新機能の紹介

1. Laravel Pennant:

これはLaravel 10の新しいパッケージです。このパッケージはアプリケーションにfeature flagの機能を提供します。feature flagを使用することで、機能の使用可否を制御できます。具体的な例として、アプリケーションに「会議の予約」という機能があり、この機能は上司のみが利用可能です。つまり、この機能は「可用性」というプロパティを持っています。この機能の可用性を確認するために、以下のようにFeatureを宣言し、確認することができます。

このパッケージを使えるために、Laravel Pennant 資料をご参照ください。

2. Process Interaction:

Laravel 10は、アプリケーションの実行エンジンと対話するためのabstractionクラスを提供します。これは「process」と呼ばれています。具体的には、この機能により、コマンドを呼び出したり、テストを実行する際にfakeで呼び出したりすることができます。

3. Test Profiling:

artisanで実行するテストコマンドには、新しいオプションパラメータが追加されました。それが –profile です。このオプションを指定すると、各テストファイルの実行時間がどれくらい遅いかまたは速いかを確認することができます。

php artisan test --profile

–profile を渡した時のテスト実行結果:

 

4. Pest Scaffolding:

Pestは、PHPUnitによって提供されるPHPテストフレームワークです。LaravelはPestをサポートするようになりました。新しいプロジェクトを作成する際に、–pestオプションを指定することで、デフォルトでPest test scaffoldingを選択することができます。

laravel new example-application --pest

Laravel 9の新機能の紹介

1. Accessors and Mutatorsは前より簡単になる

「Accessors and Mutators」は「getterとsetter」とも呼ばれます。伝統的には、getterやsetterを宣言する場合には、プロパティ名の前に「get」や「set」という接頭辞を付けます。この方法は廃止されず、新しいバージョンでも使用することができます。しかし、よりコンパクトでシンプルな方法が存在するため、以下の新しい方法を推奨します。

2. Enum Attribute Casting

PHP 8はenum宣言をサポートするようになりました。そのため、Laravel 9ではこの新機能も更新されます。さらに、enumを使用してEloquentモデルのプロパティをキャストすることもできるようになりました。 例えば、次のようにenumを宣言して使用できます。

3. Route Bindings と Enums

この機能は、リクエストパラメータの値をenumにバインドします。つまり、事前にenumを宣言し、以下のようにバインドします。リクエスト時に、enumで宣言されていない値を渡すと、以前のようにコントローラーをチェックする代わりに、HTTP 404エラーが返されます。

4. Forced Scoping Of Route Bindings

Laravelの以前のバージョンでは、以下のようにrouteを宣言することで、1つ目のモデルを参照するハンドラ関数に2つ目のEloquentモデルのスコープを渡すことができました。例えば、以下のroute宣言を参考にして、このポストは1つのユーザーに所属し、slugを使用してクエリを実行することができます。

上記のように宣言すると、Laravelは既存ルール (convention)に基づいて外部キー フィールド(foreign key)を自動的に特定します。 しかし、事前に :slugにcustom key bindingを宣言する必要があります。そうしないと、渡された2番目のモデルをバインドする必要があることが分かりません。

しかし、新しいバージョンにscopeBinding()関数へ呼び出すのみでいいです。

5. Controller Route Groups

次のように、routeをcontrollerにグループ化することができるようになりました。

6. Full Text Indexes / Where Clauses

フルテキストクエリは、whereFullText()やorWhereFullText()などの新しい関数でサポートするようになりました。

7. Laravel Scout Database Engine

Scoutの主な目標は、全文検索エンジンを利用してアプリケーションの作業プロセスを簡素化することです。Scoutは、driver basedのアプローチで構築されています。つまり、ScoutのAPIを通じてのみ、Elasticsearch、Algolia、MeiliSearch、MySQL、PostgreSQLなど、さまざまなエンジンを使用することができます。

主なアイデアは、Eloquentモデルがsearch()関数を呼び出して全文検索を実行できることです。同時に、データベース内のデータを追加、削除、編集すると、当該のフルテキストエンジンにも自動的に同期されます。ただし、このsearch()関数は単純なクエリのみをサポートしているため、大規模なプロジェクトには適していませんが、小中規模のプロジェクトには適しています。

以下のsearch関数の呼び出しを参照します。

8. Rendering Inline Blade Templates

Blade.phpファイルを作成する代わりに、Blade::render()関数の呼び出しを使用してBladeコードをコンパイルできるようになりました。これは、SPAアプリケーションのインターフェースを更新する際に便利なメソッドです。AJAX呼び出しを使用して部分ビューを取得する必要がある場合に使用します。

9. Slot Name Shortcut

前のバージョンでは、slot nameはx-slotタグのname属性を介して渡されていました。しかし、Laravel 9.xでは、以下のようにより簡単に渡すことができます。

10. ValidationにNested Arrayの値を取得

nested array型の入力に対して、どのバリデーションルールを適用するかを決定する前に、入力値を確認する必要があります。そのために、Rule::forEach()関数を使用することができます。

11. Laravel Breeze API & Next.js

Laravel Breezeスターター キットは、Next.jsをフロントエンドとして使用するアプリケーションに適しているため、「API」という新しいスキャフォールディングモードを追加しました。このスターターキットは、Laravelでバックエンドを構築し、フロントエンドのJavaScriptアプリケーションを迅速にセットアップするだけでなく、Sanctum APIを使用してユーザー認証を提供することができます。

12. Bunlding with Vite

javascriptまたはcssファイルをMix webpackでバンドルする代わりに、LaravelはViteの使用に移行しています。Viteの使用方法の詳細については、こちらをご覧ください。

13. Exceptionページのインターフェース改善

新しいインターフェースと機能がExceptionページに追加され、それにより使いやすさが向上し、デバッグ作業がより効率的になりました。

(src: https://laravel.com/docs/9.x/releases#exception-page)


新バージョンにアップデートした脆弱性

.phar形式のファイルをダウンロード禁止

「PHP executable」形式のファイルをアップロードする際、セキュリティ上の理由から、Laravelはこれらのファイルをブロックして除外します。これには、’.php’、’.php3’、’.php4’、’.php5’、’.php7’、’.php8’、および’.phtml’形式のファイルが含まれます。ただし、以前は’.phar’形式のファイルは含まれていませんでした。新しいバージョンでは、Laravelはこれを更新し、’.phar’形式のファイルも除外対象となりました。

SQL ServerにSQLインジェクション

Laravelは、SQLインジェクション攻撃に関連する脆弱性を修正しました。この脆弱性は、SQL Serverのlimit()関数とoffset()関数に直接ユーザー入力を渡す場合に発生します(MySQLやPostgreSQLなどの他のデータベースは、この脆弱性の影響を受けません)。

SQLインジェクション – 配列とクエリパラメータBinding

以下のソースコードは、SQLインジェクション攻撃の可能性を引き起こす可能性があります。その理由は、is_adminに0の代わりに1の値が割り当てられるためです。この問題は、where()関数に$value = [1, 1]の配列を渡す場合に発生します。

User::where('id', [1,1])->where('is_admin', 0)->first();
// sql: select * from `users` where `id` = 1 and `is_admin` = 1

しかし、この脆弱性が修正されました。 そして、クエリは次のように生成されます。

// sql: select * from `users` where `id` = 1 and `is_admin` = 0

上記では、Laravel 9と10の新機能について説明しました。また、Laravelが修正した脆弱性についても説明しました。次に、プロジェクトをLaravel 8からLaravel 10にアップグレードする方法について説明します。


Laravel 8のプロジェクトからLaravel 10へアップグレードの案内

PHP 8.1.0 & Composer 2.2.0へのアップグレード

Laravel 10を利用するためには、PHP 8.1.0以降とComposer 2.2.0以降がインストールされている必要があります。したがって、アップグレードする前にこれらのバージョンに合わせて調整する必要があります。この手順は、Laravel 10にアップグレードする前に必須となります。

composer.jsonでdependenciesの変更:

a. アップグレード必要なパッケージ:

以下は新しいバージョンにアップグレードする必要なパッケージです。 以下の手順を行います。

composer.jsonファイルで、パッケージのバージョン値を次のように変更します。

{
  "require": {
    "php": "^8.1",
    "laravel/framework": "^10.0",
    "laravel/sanctum": "^3.2",
    "doctrine/dbal": "^3.0",
    "laravel/passport": "^11.0"
  },
  "require-dev": {
    "nunomaduro/collision": "^6.1",
    "spatie/laravel-ignition": "^2.0"
  }
}

Broadcasting機能を使用している場合は、以下のようにバージョンを変更します。

{
  "require": {
    "pusher/pusher-php-server": "^5.0"
  }
}

facade Storage経由でS3、FTP、またはSFTPドライバーを使用している場合は、以下のように、当該パッケージのバージョンを変更します。

{
  "require": {
    "league/flysystem-aws-s3-v3": "^3.0",
    "league/flysystem-ftp": "^3.0",
    "league/flysystem-sftp-v3": "^3.0"
  }
}

PHPUnit 10を使用したい場合は、phpunit.xmlというconfigファイルで<coverage> 部分の processUncoveredFiles=”true” 属性を削除します。 それから、パッケージのバージョンを以下のように変更します。

{
  "require-dev": {
    "nunomaduro/collision": "^7.0",
    "phpunit/phpunit": "^10.0"
  }
}

b. Laravelで削除されたパッケージ:

以下のパッケージはLaravelで使用されなくなったので、削除してソースを軽くすることができます。

Trusted Proxies:

app/Http/Middleware/TrustProxies.phpファイルで以下のように変更します。

use Fideloper\Proxy\TrustProxies as Middleware;

次のように変更します。

use Illuminate\Http\Middleware\TrustProxies as Middleware;

次に、app/Http/Middleware/TrustProxies.phpファイル で、 $headers属性の値を次のように変更します。

// Before...
protected $headers = Request::HEADER_X_FORWARDED_ALL;
// After...
protected $headers =
    Request::HEADER_X_FORWARDED_FOR |
    Request::HEADER_X_FORWARDED_HOST |
    Request::HEADER_X_FORWARDED_PORT |
    Request::HEADER_X_FORWARDED_PROTO |
    Request::HEADER_X_FORWARDED_AWS_ELB;

後に、以下のようにパッケージを削除します。

{
  "require": {
    // Remove this package
    "fideloper/proxy": "^4.4"
  }
}

Fruitcake/laravel-cors:

このパッケージは、所有者によってメンテナンスとアップグレードが行われなくなりました。そのため、Laravel 10ではこのパッケージが削除されました。この変更を反映するには、まず、app\Http\Kernel.phpファイルでミドルウェアのname spaceを以下のように変更します。

\Fruitcake\Cors\HandleCors::class

次のように変更します。

\Illuminate\Http\Middleware\HandleCors::class

後に、composer.jsonでこのパッケージを削除します。

{
  "require": {
    // Remove this package
    "fruitcake/laravel-cors": "^2.0"
  }
}

c. 置換されたパッケージ:

以下は別のパッケージで置換されたパッケージなので、次のように変更します。

{
  "require-dev": {
    // Replace this
    "facade/ignition": "^2.5"
    // With this
    "spatie/laravel-ignition": "^1.0"
  }
}

Laravel SMS Notifications機能に関しては、現在VonageがNexmoを所有するため、このパッケージの名前をそれに応じて変更されます。 composer.jsonファイルで次のように修正します。

{
  "require": {
    // Replace this
    "laravel/nexmo-notification-channel": "^2.0"
    // With this
    "laravel/vonage-notification-channel": "^3.0"
  }
}

d. SwiftMailerの代わりSymfony Mailerへ  :

Laravel 9.xで最も重要な変更点の一つは、SwiftMailerのメンテナンスが終了したため、メール機能がSwiftMailerの代わりにSymfony Mailerを使用するようになったことです。この変更を反映するには、以下の2つのコマンドを実行します。

composer remove wildbit/swiftmailer-postmark
composer require symfony/mailgun-mailer symfony/postmark-mailer symfony/http-client

「composer update」のコマンド実行

composer.jsonファイルを更新した後、composer updateコマンドを実行します。ただし、注意点が1つあります。上記の変更は基本的なLaravelアプリケーションにのみ適用されます。各プロジェクトは異なる追加パッケージを使用する場合があり、それらのパッケージのバージョンの競合が発生する可能性があります。そのため、これらのパッケージのバージョンも更新する必要があります。

updateコマンドの実行が成功した場合、次にソースコードを「リファクタリング・再構築」する必要があります。これは、一部の機能が古くなったり、使い方が変更されたためです。最後に、アプリケーションでLaravelの新機能を使用することを検討することができます。

構成(configuration)の変更:

プロジェクトでPostgresを使用する場合は、config/database.phpファイルで「schema」の名前を「search_path」に変更します。

'connections' => [
  'pgsql' => [
    // replace this
    'schema' => 'public',
    // by this
    'search_path' => 'public',
  ]
]

facade Storage経由してSFTP 機能を使用する場合は、config/filesystems.phpファイルを次のように修正します。

'sftp' => [
  // Replace this         
  'password' => env('SFTP_PASSPHRASE'),
  // By this
  'passphrase' => env('SFTP_PASSPHRASE'),
]

SMTPのstream構成の宣言はサポートされなくなりました。代わりに、直接宣言する必要があります。例えば、TSL証明を無効にする場合は、以下のように宣言します。また、SMTPサーバーへの接続時にauth_modeが自動的に決定されるため、auth_modeは不要になりました。

'smtp' => [
  // 'auth_mode' => null,

  // Laravel 8.x...
  'stream' => [
      'ssl' => [
          'verify_peer' => false,
      ],
  ],
  // Laravel 9.x...
  'verify_peer' => false,
]

ディレクトリ構造の変更:

Laravelの新しいバージョンでは、/resources/langが/langに変更されました。つまり、langディレクトリはプロジェクトのルートディレクトリに配置されます。もしソースコードでlangディレクトリのパスをハードコードしている場合は、app()→langPath()関数に切り替える必要があります。


ソースコードの改修実行

Laravel 10では、一部の機能が削除され、使用方法や動作が変更されました。そのため、プロジェクトのソースコードを適切に更新する必要があります。全ての機能を更新する必要はありませんが、使用している機能のみを更新する必要があります。

削除されたメソッド

以下の関数はLaravel 8でのみサポートされており、新しいバージョンではサポートされなくなりました。したがって、これらの関数の呼び出し箇所を見つけて、別の方法に書き直す必要があります。

EnumeratesValues Trait: reduceWithKeys()メソッドが削除されました。reduceWithKeys()メソッドの代わりに、reduce()メソッド使用します。又、reduceMany()メソッドは、reduceSpreadへ名前を変更しました。

Illuminate\Database\Schema\Builder::registerCustomDoctrineType() メソッドが削除されました。代わりに、DBファサードで、registerDoctrineTypeメソッドを使用するか、config/database.php設定ァイルで、カスタムDoctrineタイプを登録できます。

Testing: assertDeleted() メソッドが削除されました。assertModelMissing()メソッドを使用します。

Queue: Bus::dispatchNow() と dispatch_now() メソッドが削除しました。Bus::dispatchSync() と dispatch_sync()メソッドを使用します。

Redirect::home()メソッド が削除しました。代わりに、return Redirect::route(‘home’)メソッドを使用することでリダイレクトします。

【陳腐】- Blade – レイジーコレクションと$loop変数

@php
    use App\Models\Car;
    $cars = Car::cursor();  // cars lazy collection
    foreach($cars as $c) {
        echo $loop->iteration;
    }
@endphp

上記のソースコードで、 レイジーコレクション内の$loop変数へ抽出しています。 この仕方は古いです。そういう風で呼び出す時に全てコレクションがRAMのメモリにロードされるからです。( レイジーコレクション稼働と逆です。)

【削除】- ストレージ

Storage – FlySystemがcached adaptersをサポートしなくなりました。削除するためには下記のコマンドを実行します。

composer require league/flysystem-cached-adapter

【削除】 DBからStringExpressionの抽出

前のバージョンでは、以下の方法ででExpression の文字列の一部を抽出することができます。

$expression = DB::raw('select * from news');
$expStr = (string)$expression;

この方法がサポートしなくなりました。代わりに、以下の方法でやります。

$expStr = $expression->getValue(DB::connection()->getQueryGrammar());

【削除】Eloquent モデルにおける$dateプロパティ

以前は、自動的にDateTimeプロパティを定義するためには、$date プロパティを使用することができました。しかし、現在のバージョンでは、$date プロパティは削除されています。

protected $dates = [
    'deployed_at'
];

その代わりに、$castsプロパティに指定します。

protected $casts = [
    'deployed_at' => 'datetime',
];

【削除】Testing – Service Mocking

MocksApplicationServices トレイトのexpectsEvents()、expectsJobs()、expectsNotifications()などのテストメソッドが削除されました。これらのメソッドを使用している場合は、代わりにEvent::fake()、Bus::fake()、Notification::fake()などのメソッドを使用する必要があります。fakeメソッドでモックを作成する方法詳細については、fakeしたい該当サービスの資料を参照ください。

【変更】バリデーション – パスワードのルール

パスワードのルールはユーザーが入力したパスワードを確認するためです。現在、passwordルールの名前をcurrent_passwordへ変更しました。

【変更】Blade – Vueスニペットの上書き避止

Laravel 9では、@diabled、@checked、@selected等の 新しいブレード ディレクティブ を提供します。又、Vue も同様なディレクティブを使用しています。そのため、Vueを上書きしないために、@@disabled、@@selected、@@selected のような記号を付ける必要があります。

【変更】コレクション

Collection::when(), unless()メソッドの1番目の引数にクロージャを渡すことができます。これらのクロージャは実行されます。(前のバージョンで実行できません)

$collection->when(function ($collection) {
  // This closure is executed...
  return false;
}, function ($collection) {
  // Not executed since first closure returned "false"...
  $collection->merge([1, 2, 3]);
});

【変更】- Eloquent – Null値でキャストカスタム

Laravel 8では、$value = nullの場合にmutator set() メソッドが実行されませんが、Laravel 9で実行します。 以下のソースコードで、$value = nullの場合より出力値が全く違います。

// In App\Casts\FilenameWithTimestamp.php
public function set($model, $key, $value, $attributes) {
  return time() . '_' . $value;
}

// In App\Models\File.php
protected $casts = [
  'filename' => FilenameWithTimestamp::class
];

// Somewhere in your program
// With Laravel 8, the result of echo statement will be: null
// With Laravel 9, the result of echo statement will be: "20230322_"
$file = File::first();
$file->filename = null;
echo $file->filename;

Laravel 9では、nullを渡してもファイル名にタイムスタンプが付けられます。これは不合理です。そのため、ソースコードを再度確認して、nullを渡してもアプリケーションも処理できることを確保しないといけません。例えば、上記のソースコードは、nullをチェックするケースを以下のように修正できます。

// In App\Casts\FilenameWithTimestamp.php
public function set($model, $key, $value, $attributes) {
  if (empty($value)) {
    return '';
  }
  return time() . '_' . $value;
}

上記の修正したら、ソースコードが正常に実行できます。

【変更】ストレージ – 例外処理

前のバージョンでは、存在しないファイルを削除する等書き込みや読み込み途中でエラーが発生した場合、Laravelが例外をスローします。しかし、新しいバージョンでは、Laravelは例外をスローせずに、true、false、null などの結果のみを返します。

この変更により、try-catchで例外処理を実装している場合、アプリケーションが正常に稼働しない可能性があります。この変更に対して対策は、if-else を使うことでエラーチェックを行います。または、以前のように例外をスローするためにLaravel を以下のように構築します。

'public' => [
  'driver' => 'local',
  // ...
  'throw' => true,
]

【変更】ストレージ – カスタムファイル システムドライバー

スタムファイル システムドライバーの登録に必要な手順が一部変更されました。したがって、もし独自のカスタムファイルシステムドライバを定義していたり、カスタムドライバを定義しているパッケージを使用していた場合は、コードと依存を更新する必要があります。

例えば、Laravel8.xのカスタムファイルシステムの登録は、以下のようにします。

use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;

Storage::extend('dropbox', function ($app, $config) {
    $client = new DropboxClient(
        $config['authorization_token']
    );

    return new Filesystem(new DropboxAdapter($client));
});

しかし、Laravel9.xから、Storage::extend メソッドへ与えられるコールバックは、 直接Illuminate\Filesystem\FilesystemAdapterインスタンスを返さなければなりません。

use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;

Storage::extend('dropbox', function ($app, $config) {
    $adapter = new DropboxAdapter(
        new DropboxClient($config['authorization_token'])
    );

    return new FilesystemAdapter(
        new Filesystem($adapter, $config),
        $adapter,
        $config
    );
});

【変更】Swift MailerをSymfony Mailerに置き換える

Laravel8 では、Swift Mailer ライブラリがメールを送信するように使用されますが、Swiftmailerが廃止され、Symfony/Mailerが採用されています。大きく影響を与えます。ライブラリへ呼び出すコマンドが殆ど変更されて、コマンドを更新する必要があります。

「Swift」のメソッドの名前の変更:さまざまなSwiftMailer関連のメソッドは、Symfony Mailerの対応するメソッドへ名前を変更しました。 IDE のadvanced search機能を使用して、以下のように該当メソッドを使用することでメソッドへの呼び出しのコマンドに上書きを一括で行います。

Message::getSwiftMessage();
Message::getSymfonyMessage();

Mailable::withSwiftMessage($callback);
Mailable::withSymfonyMessage($callback);

MailMessage::withSwiftMessage($callback);
MailMessage::withSymfonyMessage($callback);

Mailer::getSwiftMailer();
Mailer::getSymfonyTransport();

Mailer::setSwiftMailer($swift);
Mailer::setSymfonyTransport(TransportInterface $transport);

MailManager::createTransport($config);
MailManager::createSymfonyTransport($config);

MessageSent イベント の変更:

Illuminate\Mail\Events\MessageSent イベントは、前のSwift_Messageの代わりに、 Symfony\Component\Mime\Emailの変数を提供しています。この変数に送信前のメールに関する情報が含まれています。同時に、新しくsentプロパティを追加しました。この新しいプロパティは、メッセージIDなど送信したメールに関する情報が含まれています。

配信失敗メールの受信者リスト:

メッセージ送信後に、失敗した受信者のリストを取得できなくなりました。代わりに、メッセージの送信に失敗すると、Symfony\Component\Mailer\Exception\TransportExceptionInterface例外が投げられるようになりました。

理解度テスト

この記事の理解度を測るテストを用意しました。
ぜひ挑戦してください!!

https://exam-site.briswell-vn.com/startTest/copKXQI21W

参照元: