以下の内容はhttps://techblog.openwork.co.jp/entry/php83-symfony64-new-featuresより取得しました。


PHP8.2-8.3 + Symfony6.0-6.4のおすすめ新機能の紹介

はじめに

こんにちは!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;
}

www.php.net

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'

www.php.net

www.php.net

Traitでconst定数が使えるようになった。

読んで字のごとくですが、Traitでもconst定数が使えるようになりました。

<?php
trait Sample {
    private const string PHP_TEXT = 'php';
}

www.php.net

#[Override]

オーバーライドされたメソッドであることを明示できます。
継承先のメソッドを意図せず書き換えたり、パッケージのバージョン変更などで継承が外れてしまった場合などに気付けそうですね。
interfaceでも使えます。

<?php
interface Parent {
    public function sample(): void {}
}

class Child implements Parent {
    #[\Override]
    public function sample(): void {}

    #[\Override]
    public function error(): void {}  // こちらはエラーが発生する
}

www.php.net

#[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');

www.php.net

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,
    ) {}
}

symfony.com

symfony.com

#[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
     */
}

symfony.com

symfony.com

#[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 {
    // ...
}

symfony.com

#[Exclude]

任意のクラスでDI(DependencyInjection)を禁止できるようになりました。
SymfonyではDIするとシングルトンになるため、状態を持つクラスなどに付与すると思わぬバグを防げそうですね。
ちなみに、Symfony6.0以前でもディレクトリ一括でDIを禁止することはできます。

<?php
use Symfony\Component\DependencyInjection\Attribute\Exclude;

#[Exclude]
class Kernel extends BaseKernel
{
    use MicroKernelTrait;
}

symfony.com

#[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'),
        ]);
    }
}

symfony.com

コマンドでProfilerを使う

コマンドでもProfilerが使えるようになりました。
Web画面と同様にパフォーマンスやログ、クエリなどを確認できて便利ですね。

コマンド実行時、--profileを付けるとProfiler上で確認できるようになります。

実行例:php bin/console --profile app:sample-command

symfony.com

おわりに

お気に入りの新機能は見つかりましたでしょうか?私はSymfonyのアトリビュートを使いたいです。
この記事を読んでオープンワークに興味を持った方は是非採用サイトを覗いて頂けると嬉しいです!

www.openwork.co.jp

参考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




以上の内容はhttps://techblog.openwork.co.jp/entry/php83-symfony64-new-featuresより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14