RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

PHPerのための「Laravel10の新機能」を語り合う【PHP TechCafe イベントレポート】

弊社で毎月開催し、PHPエンジニアの間で好評いただいているPHP TechCafe。

2023年2月のイベントでは「Laravel10の新機能」について語り合いました。

弊社のメンバーが事前にまとめてきたLaravel10の新機能の情報にしたがって、他の参加者に意見を頂いて語り合いながら学びました。

今回はその内容についてレポートします。

rakus.connpass.com

特集:Laravel10の新機能

PHP TechCafeでは過去に何度かLaravelを取り上げています。

下記が過去のLaravel回のまとめになります。資料には過去のバージョンのものも記載していますので、参考にしていただければと思います。

Laravelのリリースサイクルについて

  • LaravelはLaravel8以降1年に1回のメジャーバージョンアップになっています。

PHP8.0系の非対応

  • Laravel10はPHP8.1以上が必要です。

PHP8.0系は2023年の11月末くらいでセキュリティサポートが切れるので妥当かなという意見がありました。

Laravel Pennant

下記のような特徴を持つLaravel Pennantというパッケージが追加されました。

  • 新しいファーストパーティパッケージ
  • フィーチャーフラグを追加できる
  • Composer 経由でインストールが可能

フィーチャーフラグとは??

フィーチャーフラグとはコードを書き換えることなく、システムの振る舞いを変更できるようにする開発手法です。 フラグによって機能のON/OFFが可能となります。 新しい機能を段階的にロールアウトしたり、 A/B テストを行ったりできます。

laravel.com

「最近、結構(フィーチャーフラグの話を)聞くようになった」

PDCAサイクルを早める手法として紹介されたり、流行っている印象です。」

などの意見が上がりました。

導入手順

下記の手順でComposer で簡単で導入することができます。

$ composer require laravel/pennant
$ php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
$ php artisan migrate

機能利用のON/OFFの定義

下記が機能利用のON/OFFの定義方法になります。

サービスプロバイダで定義する方法

<?php
 
namespace App\Providers;
 
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;  // ★

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        // 新しいAPIについて
        Feature::define('new-api', fn (User $user) => match (true) {
            // 内部メンバーは利用可能
            $user->isInternalTeamMember() => true,
            // トラフィックの多いユーザは利用不可
            $user->isHighTrafficCustomer() => false,
            // それ以外の場合、1/100の確率で利用可能
            default => Lottery::odds(1 / 100),
        });
    }
}

クラスでの定義

クラスでの定義には下記2つの特徴があります。

  • 機能フラグをクラスで定義することも可能
  • artisanコマンドから雛形を作成できる
$ php artisan pennant:feature NewApi
<?php
 
namespace App\Features;
 
use Illuminate\Support\Lottery;
 
class NewApi
{
    /**
     * Resolve the feature's initial value.
     */
    public function resolve(User $user): mixed
    {
        // 新しいAPIについて
        return match (true) {
            // 内部メンバーは利用可能
            $user->isInternalTeamMember() => true,
            // トラフィックの多いユーザは利用不可
            $user->isHighTrafficCustomer() => false,
            // それ以外の場合、1/100の確率で利用可能
            default => Lottery::odds(1 / 100),
        };
    }
}

機能フラグの利用方法

Feature::active('KEY名') で ON/OFF を取得できます。

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
 
class PodcastController
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): Response
    {
        return Feature::active('new-api') // 定義した条件通りにtrue/falseが返される
                ? $this->resolveNewApiResponse($request)
                : $this->resolveLegacyApiResponse($request);
    }
 
    // ...
}

クラスに定義している場合

フィーチャーフラグを管理しているクラス名を指定する必要があります。

<?php
 
namespace App\Http\Controllers;
 
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
 
class PodcastController
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): Response
    {
        return Feature::active(NewApi::class)  // ★
                ? $this->resolveNewApiResponse($request)
                : $this->resolveLegacyApiResponse($request);
    }
 
    // ...
}

Blade内での利用方法

@feature ディレクティブを使ってBladeで使用することも可能です。

@feature('new-api')
    <!-- 'site-redesign' is active -->
@else
    <!-- 'site-redesign' is inactive -->
@endfeature

下記のような話が上がり、今回のバージョンアップの注目の機能として紹介されました。

  • フィーチャーフラグは実はよく使われる機能!?
    • フィーチャーフラグ、フィーチャートグルという言い方だとあまり使ったことないという感じます
    • よくある課金ユーザだけ機能をONにするような機能で使えると思います
    • フィーチャーフラグを定義しておけば可読性が上がるのではないかと考えられます
  • オレオレ実装した時にハマりそうな罠を一通り回避してくれる
    • フィーチャートグルは名前で損していると感じます
    • 仕様を見ると下記のようなことが可能です
      • フィーチャートグルの管理下の機能を一括で有効にしたりできます
      • 処理の中で、あるユーザに対して「ルートAを通ったら有効、ルートBを通ったら無効」になる場合にキャッシュを取って整合性を取ってくれます
    • オレオレ実装でハマりそうなこと
      • 同じ条件のはずのif文同士なのに条件が微妙に違うといったことがあります。
    • 今後、ブログ等で紹介されることを期待しています
  • 複数チームでの開発でも使えます
    • 並行開発するときにも使えます
      • OFFになっていたらマージが簡単になります
      • 複数人で開発していると、ブランチを切ってからブランチがどんどんかけ離れていってしまうことがあります
      • 実装した機能がif文で囲われていて、そこがfalseになっているからマージしてもOKという話だと思います
      • コンフリクトに時間をかけることも少なくできることが期待できます
      • デメリット
        • どこかでifを削除する必要があります
          • カンファレンス等でそのifを自動削除するツールを作った発表を見ましたが、自動テストが充実していないと事故になりかねないと思われます
  • ソーシャルゲームの場合はよく使われている機能ではないか
    • ソーシャルゲームで新しい機能をリリースして何か不具合があった場合、フィーチャーフラグを利用しているとすぐに機能を閉じることができます
    • ソーシャルゲームはアプリという形で配信してしまっているので切り戻しが効かない世界です
    • こういったノウハウを色々と持たれているかなと感じました

Native type declarations in Laravel 10 skeleton

  • 以前のバージョンでは新しくひな型を作ると、引数と戻り値に型宣言が追加されていました
  • PHPDocで補足していた型ヒントが消えて、型宣言に置き換えられました
  • 下位互換性があるので、型宣言が追加されたからといって既存プロジェクトが動かなくなることはないと記述されています。

「型のない言語はもう許されない(流行らない)のか……?」という意見もありました。

Native type declarations in Laravel 10 skeleton

Invokable Validation rules are the default

Laravel9で導入されたInvokable 検証ルールをデフォルトにする変更です。

この変更によって得られるメリットは2点です。

  • コードが簡潔になる
  • コストがシンプルになった分、学習コストが減る

Laravelはphp artisan make:rule [ルールのファイル名]で独自のバリデーションルールを設定できます。 makeコマンドに引数が無い場合はpassesメソッドとmessageメソッドが実装されたコードが生成されます。 下記コードはGithubのものです。

<?php
class Quantity implements Rule
{
    protected $messages = [];

    public function passes($attribute, $value)
    {
        if (! is_array($value)) {
            $this->messages[] = trans('validation.quantity.must_be_an_object');

            return false;
        }

        if (! array_key_exists('magnitude', $value)) {
            $this->messages[] = trans('validation.quantity.missing_magnitude');
        }

        if (! array_key_exists('units', $value)) {
            $this->messages[] = trans('validation.quantity.missing_units');
        }

        return $this->messages === [];
    }

    public function message()
    {
        return $this->messages;
    }
}

passesでバリデーションを実施してfalseであればmessageをreturnします。 上記のコードでいうと、配列のkeyにmagnitudeが存在しない時、validation.quantity.missing_magnitudeメッセージが返されます。

makeコマンドの引数にinvokableを指定するとinvokeメソッドのみが実装されたシンプルなコードが生成されます。(invokable rule) php artisan make:rule [ルールのファイル名] --invokable 下記コードはGithubのものです。

<?php
class InvokableQuantity implements InvokableRule
{
    public function __invoke($attribute, $value, $fail)
    {
        if (! is_array($value)) {
            return $fail('validation.quantity.must_be_an_object')->translate();
        }

        if (! array_key_exists('magnitude', $value)) {
            $fail('validation.quantity.missing_magnitude')->translate();
        }

        if (! array_key_exists('units', $value)) {
            $fail('validation.quantity.missing_units')->translate();
        }
    }
}

invokeメソッドの引数$failは失敗時に実行されるコールバック関数です。 Laravel10からmake:ruleコマンドに引数invokableを渡さなくてもinvokable ruleが適用されるようになりました。 なお呼び出し方はどちらも同じなので後方互換性が無くなることはありません。 移行する場合もシンプルで、手順は以下になります。

  1. Quantityクラスpasses内の下記のコードをInvokableQuantityクラスの__invoke関数内にコピペします。
<?php
public function passes($attribute, $value) {
  中略...
  if (! array_key_exists('units', $value)) {
    $this->messages[] = trans('validation.quantity.missing_units');
  }
}

<?php
public function __invoke($attribute, $value, $fail) {
  if (! array_key_exists('units', $value)) {
    $this->messages[] = trans('validation.quantity.missing_units');
  }
}
  1. messagesに値を入れている部分を$failを使用するように修正します。
<?php
public function __invoke($attribute, $value, $fail) {
  if (! array_key_exists('units', $value)) {
    $fail('validation.quantity.missing_units')->translate();
  }
}

Invokable Validation rules are the default

下記のような話があがりました。

  • invokeメソッドとは
    • __invoke()を入れておいたらインスタンス化されたものを関数みたいに呼び方すると呼ばれるものです
    • 呼び出しは簡単になるが、処理が追いづらくなるという意見もありました。
  • 見た目がスッキリしました
  • 元々はオプションだった機能をデフォルトにするので、気をつけたほうがいい
  • 旧方式のバリデートルールを使っている人が新方式に置き換える場合の修正内容
    • passesの中身をInvokeの中身に移植
    • messageのところを$failに渡す
  • 実装時の手間が省くことができそうです。
    • 実案件のコードだとrulesの配列に100行、messageの配列に100行書かないといけなかったです
    • それが一気にスッキリかけるようになりました
  • Laravel10の雛形ファイルがInvokeからvalidateに変更されています

Processes 関連機能

  • 別のプロセス呼び出しがファサード経由で実行可能になりました。
  • 並行プロセスの実行と管理が容易になります。

プロセスの実行方法

  • Processファサードrunメソッドで実行可能です。
  • プロセスは同期、非同期を選択できます。

同期実行(処理が終わるまで待つ)

<?php
use Illuminate\Support\Facades\Process;
 
$result = Process::run('ls -la');
 
return $result->output();

非同期実行

<?php
use Illuminate\Support\Facades\Process;

// 非同期実行(タイムアウトも設定)
$process = Process::timeout(120)->start('bash import.sh');

// 実行中かどうかを判定することもできる
while ($process->running()) {
    // ...
}
 
$result = $process->wait(); // 処理が終わるまで待つことも可能

便利なメソッド群

runで実行したプロセスの結果を検査するためのメソッドです。

<?php
$result = Process::run('ls -la');
 
$result->successful();
$result->failed();
$result->exitCode();
$result->output();
$result->errorOutput();

並行プロセスの管理

複数のプロセスをプールさせることも簡単になります。

<?php
use Illuminate\Process\Pool;
use Illuminate\Support\Facades\Process;
 
$pool = Process::pool(function (Pool $pool) {
    $pool->path(__DIR__)->command('bash import-1.sh');
    $pool->path(__DIR__)->command('bash import-2.sh');
    $pool->path(__DIR__)->command('bash import-3.sh');
})->start(function (string $type, string $output, int $key) {
    // ...
});
 
while ($pool->running()->isNotEmpty()) {
    // ...
}
 
$results = $pool->wait();

Processes 関連機能

テストのプロファイル オプション

  • artisantest コマンドに --profile オプションが追加されました。
  • 実行が遅いテストを一覧表示できる。(最大10個)

テストのプロファイル オプション

Pest Scaffolding

  •  Pest
  • Laravelのアプリケーションを新規作成する時に Pest を利用できるようになりました。
$ laravel new example-application --pest

Pest Scaffolding

パスワード生成ヘルパー Str::password()

<?php
Str::password()
  • 特定の長さのランダムパスワードを生成できます。
  • パスワードは、文字、数字、記号、スペースの組み合わせで構成されます。
  • デフォルトでは、パスワードの長さは 32 文字です。

パスワード生成ヘルパー Str::password()

設定ファイルパスのカスタマイズ

<?php
$app->configPath(__DIR__ . '/../some/path');

設定ファイルへのパスを設定することができるようになりました。

設定ファイルパスのカスタマイズ

下記のような意見があげられました。 Laravelプロジェクト自体の話も飛び出しました。

  • Gitのプルリクで「なんでこれが必要なんだ」という議論がなされていました
  • 下記の理由が記述されていました
    • 「カスタマイズされたLaravel構造のプロジェクトに取り組んでいて、現在の構成パスはベースパスに残っているが、別ディレクトリに移動したいと考えています」
  • 「息の長いプロジェクトだとこういうことも考えないといけないんですね」と感想がありました
  • Laravelのプルリクでは「なんで?」が多いです
  • (Laravelのプロジェクトに対する)プルリクが4万件
  • 「ユーザのメリットを教えてくれるかい?」など少し怖さを感じることもあります
    • すごい数のプルリクが来ているから本当に必要なことを判別するため?
    • 作者本人から即レスされたり、書いたプルリクはちゃんと読まれている印象
  • Symfonyに支えられているのがLaravel

doctrine/dbal is not needed anymore to modify columns in migrations

  • Laravel9では、マイグレーションにてテーブル列名を変更する場合は doctrine/dbal をインストールする必要がありました。
  • Laravel 10 からは doctrine/dbal がインストールは不要になります。
<?php
return new class extends Migration
{
    public function up()
    {
        Schema::table('foo', function (Blueprint $table) {
            $table->unsignedBigInteger('bar')->change();
        });
    }}

既に doctrine/dbal がインストールされている場合は、サービスプロバイダに以下の記述が必要です。

<?php
use Illuminate\Support\Facades\Schema;
 
…
 
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Schema::useNativeSchemaOperationsIfPossible();
    }
}

Laravel 10 requires at least Composer 2.2

  • Laravel10.xからは Composer 2.2 以上が必要となります。

非推奨となる変更点

以下が非推奨となる変更点です。

  • Remove various deprecations Pull Request #41136
  • Remove deprecated dates property in Pull Request #42587
    • Eloquent モデルの非推奨の$datesプロパティが削除されました。
    • $castsプロパティを使用する必要があります。
  • Remove handleDeprecation method in Pull Request #42590
  • assertTimesSentメソッドが削除された。 #42592
    • 通知が送信された回数の合計をassertするテスト用のメソッドです。
    • assertSentTimesメソッドを代わりに使用します。
  • ScheduleListCommand.php$defaultNameプロパティが削除された。コミットコメント
    • スケジュールされているタスクのリストを表示するコマンドです。
    • $defaultNameプロパティは遅延ロード中にコマンドを識別するために使用されていた模様です。(修正コミット)
    • 使用する側には影響なしと思われます。
  • Route::homeメソッドが削除された。#42614
    • homeとして登録されているルートに遷移するメソッドのようです。
  • dispatchNow()が削除された。#42591
    • ジョブをすぐに実行するメソッドです。
    • ジョブをすぐに実行したい場合は代わりにdispatchSync()を使用する必要があります。

参考資料

まとめ

今回はLaravel10の新機能について、イベント参加者の生の声を交えてまとめてみましたがいかがでしたでしょうか? イベントでは追加される新機能の内容だけでなく、実用的な知見やノウハウなども紹介されていて、有意義なTech Cafeであったと思います。

PHP TechCafe」では今後もPHPに関する様々なテーマのイベントを企画していきます。 皆さまのご参加をお待ちしております。

Copyright © RAKUS Co., Ltd. All rights reserved.