
はじめに
こんにちは!23年度に新卒入社したWebアプリエンジニアの室永です。最近休日のお昼からお酒を飲むのがやめられなくなりつつあります。
私は前期までPHPとSymfonyのバージョンアップを実施するプロジェクトに所属していました。
その中で新規機能について調査する機会があったため紹介しようと思います(Symfonyは使ってないよ〜という方でも、PHPだけでも眺めてみて頂けると嬉しいです)。
前提
当社では、バージョンアップ前までPHP8.1とSymfony5.4を使っていました。
今回のバージョンアップでは、PHP8.1 → 8.3、Symfony5.4 → 6.4を実施したため、今回はPHP8.2-8.3、Symfony6.0-6.4の新機能の紹介になります。
また、当社のコードベースにおいて使えそうなものを私が独断と偏見でピックアップしているため、他にも便利な機能などあるかもしれません。悪しからず。
PHP8.2-8.3
型やEnumなどに関する新機能が多い印象でした。
readonlyクラス
クラス内の各プロパティをreadonlyにできます。
readonlyを書く手間を省けて良いですね。
before
<?php class Sample{ public function __construct( private readonly string $php, private readonly string $symfony, private readonly string $vue, ) { } private readonly string $mysql; }
after
<?php readonly class Sample{ public function __construct( private string $php, private string $symfony, private string $vue, ) { } private string $mysql; }
const定数でEnum/型宣言
const定数でEnumや型宣言が使えるようになりました。
静的解析が捗りそうです。
<?php enum Sample1: string { case PhpText = 'php'; } class Sample2 { public const string SYMFONY_TEXT = 'symfony'; } echo Sample1::PhpText->value; // 'php' echo Sample2::SYMFONY_TEXT; // 'symfony'
Traitでconst定数が使えるようになった。
読んで字のごとくですが、Traitでもconst定数が使えるようになりました。
<?php trait Sample { private const string PHP_TEXT = 'php'; }
#[Override]
オーバーライドされたメソッドであることを明示できます。
継承先のメソッドを意図せず書き換えたり、パッケージのバージョン変更などで継承が外れてしまった場合などに気付けそうですね。
interfaceでも使えます。
<?php interface Parent { public function sample(): void {} } class Child implements Parent { #[\Override] public function sample(): void {} #[\Override] public function error(): void {} // こちらはエラーが発生する }
#[SensitiveParameter]
対象引数がスタックトレースに表示されなくなり、オブジェクトとして扱われるようになります。
機密性が高い情報が思わず漏れてしまうのを防げそうです。
<?php public function sample( #[\SensitiveParameter] string $secret, string $normal ) { throw new Exception('Error!'); } /** * Fatal error: Uncaught Exception: Error! in php-wasm run script:x * Stack trace: * #0 php-wasm run script(x): sample(Object(SensitiveParameterValue), 'name') * #1 {main} thrown in php-wasm run script on line x */ sample('password', 'name');
Symfony6.0-6.4
アトリビュートの追加や、リクエストのハンドリング関連での新機能追加が多い印象でした。
#[Autowire]
YAMLファイルが肥大化するのを防げる、クラスに直接記述できるため可読性や集約性が高まるなど、メリットの大きい新機能かと思います。
ちなみに、遅延読み込みオプションなどの付与も可能です。
before
# services.yaml MyService: arguments: $service: 'sample_service' $parameter2: '%sample.url' $parameter3: '%env(SAMPLE_API_KEY)%'
<?php class MyService { public function __construct( private $service, private $parameter1, private $parameter2, ) {} }
after
<?php use Symfony\Component\DependencyInjection\Attribute\Autowire; class MyService { public function __construct( #[Autowire(service: 'sample_service')] private $service, #[Autowire(param: 'sample.url')] private readonly $parameter1, #[Autowire(env: 'SAMPLE_API_KEY')] private readonly $parameter2, ) {} }
#[MapQueryParameter]
クエリパラメータを Controller の引数に渡せるようになりました。
クエリパラメータは実装を確認しないとどんなものが渡されているか分からなかったのですが、引数から確認できるようになるのは非常に便利ですね。
<?php use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; // クエリパラメータが ?firstName=masato&lastName=muronaga&age=26 の場合 public function indexAction( #[MapQueryParameter] string $firstName, #[MapQueryParameter] string $lastName, #[MapQueryParameter] int $age, #[MapQueryParameter] ?string $email = null, ): Response { /** * $firstName = 'masato' * $lastName = 'muronaga' * $age = 26 * $email = null */ }
#[MapQueryString]
上記の#[MapQueryParameter]と似ていますが、DTO(Data Transfer Object)でもクエリパラメータを渡せます。
クエリパラメータをバリデーションできて、かつ、DTOで渡せるのは便利ですね。
<?php use Symfony\Component\Validator\Constraints as Assert; readonly class UserParameterDto { public function __construct( #[Assert\NotBlank] public string $firstName, #[Assert\NotBlank] public string $lastName, #[Assert\Range(min: 0, max: 99)] public int $age, #[Assert\Email] public ?string $email = null, ) { } }
<?php use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; // クエリパラメータが ?firstName=masato&lastName=muronaga&age=26 の場合 public function indexAction( #[MapQueryParameter] UserParameterDto $dto, ): Response { /** * $dto->firstName = 'masato' * $dto->lastName = 'muronaga' * $dto->age = 26 * $dto->email = null */ }
#[MapRequestPayload]
リクエストボディをDTOやEntityにマッピングできるようになりました。
<?php use Symfony\Component\Validator\Constraints as Assert; class UserDto { public function __construct( public readonly int $id, #[Assert\NotBlank] #[Assert\Length(min: 1, max: 10)] public readonly string $userName, ) { } }
<?php use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; public function indexAction( #[MapRequestPayload] UserDto $userDto, ): Response { // ... }
#[Exclude]
任意のクラスでDI(DependencyInjection)を禁止できるようになりました。
SymfonyではDIするとシングルトンになるため、状態を持つクラスなどに付与すると思わぬバグを防げそうですね。
ちなみに、Symfony6.0以前でもディレクトリ一括でDIを禁止することはできます。
<?php use Symfony\Component\DependencyInjection\Attribute\Exclude; #[Exclude] class Kernel extends BaseKernel { use MicroKernelTrait; }
#[ValueResolver] / #[AsTargetedValueResolver]
ArgumentValueResolverInterfaceという旧来のValueResolverがSymfony6.2で非推奨となりました。
Symfony6.3以降は、新しいValueResolverInterfaceの利用が推奨されており、より柔軟かつ簡素な書き方ができるようになりました。
<?php use Symfony\Component\HttpKernel\Attribute\ValueResolver; #[Route('/user/{id}')] public function indexAction( #[ValueResolver('user')] User $user ): Response { // ... }
<?php use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; #[AsTargetedValueResolver('user')] class ItemValueResolver implements ValueResolverInterface { public function __construct( private readonly SampleRepository $sampleRepository ) { } public function resolve(Request $request, ArgumentMetadata $argument): iterable { yield $this->sampleRepository->findBy([ 'id' => $request->get('id'), ]); } }
コマンドでProfilerを使う
コマンドでもProfilerが使えるようになりました。
Web画面と同様にパフォーマンスやログ、クエリなどを確認できて便利ですね。

コマンド実行時、--profileを付けるとProfiler上で確認できるようになります。
実行例:php bin/console --profile app:sample-command
おわりに
お気に入りの新機能は見つかりましたでしょうか?私はSymfonyのアトリビュートを使いたいです。
この記事を読んでオープンワークに興味を持った方は是非採用サイトを覗いて頂けると嬉しいです!
参考URL
PHP8.2 - 8.3
https://www.php.net/manual/ja/migration82.php
https://www.php.net/manual/ja/migration83.php
Symfony6.0 - 6.4
https://symfony.com/blog/category/living-on-the-edge/6.1
https://symfony.com/blog/category/living-on-the-edge/6.2
https://symfony.com/blog/category/living-on-the-edge/6.3
https://symfony.com/blog/category/living-on-the-edge/7.0-6.4