2026年版 API Platform と Symfony: アーキテクチャ設計と技術面接対策ガイド

API Platform 4.2とSymfony 7.4を活用したREST API開発の最新手法を解説。State Provider、State Processor、Object Mapper、JSON Streamerによる32%のパフォーマンス向上まで、技術面接で問われる重要概念を網羅します。

API Platform Symfony 2026 アーキテクチャと面接対策

API Platform 4.2は、SymfonyアプリケーションがREST APIとGraphQL APIを公開する方法を根本から変革しています。このバージョンでは、リソース分離を実現するSymfony Object Mapper、大幅なパフォーマンス向上をもたらすJSON Streamer、そして再設計されたフィルターシステムが導入されました。技術面接に臨む開発者にとって、これらのアーキテクチャパターンを理解することは、シニアとジュニアを分ける重要な要素となります。

API Platform 4.2の動作要件

API Platform 4.2はSymfony 7.4または8.0を必要とします。Symfony 6.4および7.0〜7.3のサポートは終了しました。JSON Streamerはコレクションエンドポイントで最大32%のリクエスト処理能力向上を実現します。

API Platform 4.2とSymfonyのセットアップ

API PlatformはSymfony Flexを通じて自動設定でインストールされます。デフォルトのセットアップはほとんどのユースケースに対応しつつ、複雑なドメイン要件に対しては完全にカスタマイズ可能です。

bash
# Install API Platform
composer require api-platform/symfony

# The API documentation is available at /api/
# Open http://localhost:8000/api/ after starting the server
symfony serve

Flexレシピは、シリアライゼーショングループ、Doctrine統合、OpenAPIドキュメント生成を自動的に設定します。APIリソースは、エンティティクラスに単一のアトリビュートを追加するだけでCRUD操作を公開できます。

src/Entity/Book.phpphp
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
#[ApiResource(
    operations: [
        new GetCollection(),
        new Get(),
        new Post(security: "is_granted('ROLE_ADMIN')"),
        new Put(security: "is_granted('ROLE_ADMIN')"),
        new Delete(security: "is_granted('ROLE_ADMIN')")
    ],
    normalizationContext: ['groups' => ['book:read']],
    denormalizationContext: ['groups' => ['book:write']]
)]
class Book
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Groups(['book:read', 'book:write'])]
    private string $title;

    #[ORM\Column(type: 'text')]
    #[Groups(['book:read', 'book:write'])]
    private string $description;

    #[ORM\Column]
    #[Groups(['book:read'])]
    private \DateTimeImmutable $createdAt;

    // Getters and setters...
}

この設定により、自動バリデーション、シリアライゼーション、OpenAPIドキュメントを備えた5つのエンドポイントが生成されます。

State Provider: 任意のデータソースからのデータ取得

State Providerは、API PlatformがGET操作でデータを取得する方法を制御します。デフォルトのDoctrine Providerはエンティティの取得を処理しますが、カスタムProviderを使用することで、外部API、Elasticsearch、またはキャッシュデータとの統合が可能になります。

src/State/BookStateProvider.phpphp
namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Repository\BookRepository;
use Psr\Cache\CacheItemPoolInterface;

final class BookStateProvider implements ProviderInterface
{
    public function __construct(
        private BookRepository $repository,
        private CacheItemPoolInterface $cache
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        // Single item retrieval
        if (isset($uriVariables['id'])) {
            $cacheKey = sprintf('book_%d', $uriVariables['id']);
            $item = $this->cache->getItem($cacheKey);
            
            if ($item->isHit()) {
                return $item->get();
            }
            
            $book = $this->repository->find($uriVariables['id']);
            $item->set($book)->expiresAfter(3600);
            $this->cache->save($item);
            
            return $book;
        }

        // Collection retrieval with custom filtering
        return $this->repository->findActiveBooks();
    }
}

特定のオペレーションにProviderを登録する方法は以下の通りです。

php
#[ApiResource(
    operations: [
        new GetCollection(provider: BookStateProvider::class),
        new Get(provider: BookStateProvider::class),
        // Other operations use default Doctrine provider
        new Post(),
        new Put(),
    ]
)]
class Book { /* ... */ }

State Processor: ビジネスロジックを含むミューテーション処理

State Processorは、POST、PUT、PATCH、DELETE操作を処理します。デシリアライズされたデータを受け取り、永続化の前にビジネスロジックを適用します。

src/State/BookStateProcessor.phpphp
namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Book;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

final class BookStateProcessor implements ProcessorInterface
{
    public function __construct(
        private EntityManagerInterface $em,
        private MailerInterface $mailer,
        private ProcessorInterface $persistProcessor // Decorated Doctrine processor
    ) {}

    public function process(
        mixed $data,
        Operation $operation,
        array $uriVariables = [],
        array $context = []
    ): mixed {
        // Pre-persist business logic
        if ($data instanceof Book && $operation instanceof Post) {
            $data->setCreatedAt(new \DateTimeImmutable());
            $data->setSlug($this->generateSlug($data->getTitle()));
        }

        // Delegate to Doctrine processor
        $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);

        // Post-persist notification
        if ($operation instanceof Post) {
            $this->notifyNewBook($data);
        }

        return $result;
    }

    private function generateSlug(string $title): string
    {
        return strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', $title));
    }

    private function notifyNewBook(Book $book): void
    {
        $email = (new Email())
            ->to('catalog@example.com')
            ->subject('New book added: ' . $book->getTitle())
            ->text('A new book has been added to the catalog.');
        $this->mailer->send($email);
    }
}
Processorのデコレーション

デフォルトのDoctrine Processorを#[AsDecorator]でデコレートすることで、永続化の動作を維持しながらカスタムロジックを追加できます。このパターンにより、ORM操作の重複を避けることができます。

Object Mapper: APIリソースとエンティティの分離

API Platform 4.2は、Symfony Object Mapperコンポーネントを統合し、API表現をドメインエンティティから分離します。この分離により、異なる読み取り/書き込みモデルが可能になり、内部エンティティ構造を保護できます。

src/ApiResource/BookResource.phpphp
namespace App\ApiResource;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use App\Entity\Book;
use Symfony\Component\ObjectMapper\Attribute\Map;

#[ApiResource(
    shortName: 'Book',
    operations: [
        new GetCollection(),
        new Get()
    ]
)]
#[Map(target: Book::class)]
class BookResource
{
    public ?int $id = null;
    
    public string $title;
    
    public string $description;
    
    // Computed field not in entity
    public int $wordCount;
    
    // Formatted date for API consumers
    public string $publishedDate;
}

Mapperプロバイダーはエンティティをリソースに自動的に変換します。

src/State/BookResourceProvider.phpphp
namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\BookResource;
use App\Repository\BookRepository;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;

final class BookResourceProvider implements ProviderInterface
{
    public function __construct(
        private BookRepository $repository,
        private ObjectMapperInterface $mapper
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        if (isset($uriVariables['id'])) {
            $book = $this->repository->find($uriVariables['id']);
            return $book ? $this->toResource($book) : null;
        }

        return array_map(
            fn(Book $book) => $this->toResource($book),
            $this->repository->findAll()
        );
    }

    private function toResource(Book $book): BookResource
    {
        $resource = $this->mapper->map($book, BookResource::class);
        $resource->wordCount = str_word_count($book->getDescription());
        $resource->publishedDate = $book->getCreatedAt()->format('F j, Y');
        return $resource;
    }
}

Symfonyの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

JSON Streamer: 32%のパフォーマンス向上

JSON Streamerコンポーネントは、データセット全体をメモリにロードせずに大規模なコレクションをシリアライズします。Sylius APIでのベンチマークでは、1秒あたりのリクエスト数が32.4%増加しました。

リソースまたはオペレーションレベルでストリーミングを有効にする方法は以下の通りです。

php
#[ApiResource(
    operations: [
        new GetCollection(
            jsonStream: true,  // Enable JSON streaming
            paginationItemsPerPage: 100
        ),
        new Get()
    ]
)]
class Book { /* ... */ }

ストリーミングが特に効果的な場面は以下の通りです。

  • 50件以上のアイテムを含むコレクションエンドポイント
  • ネストされたリレーションシップを持つリソース
  • 帯域幅が限られたモバイルクライアントへのAPI提供

OpenAPI仕様も最適化されました。JSON Schemaの共通化により、ファイルサイズが30%削減され、ドキュメントの読み込み時間が改善されました。

技術面接: API Platformアーキテクチャに関する質問

Symfonyポジションの技術面接では、API Platformのパターンが頻繁に取り上げられます。これらの質問は、基本的なCRUD操作を超えたフレームワークのアーキテクチャ理解を評価します。

「State ProviderとProcessorの違いを説明してください」

期待される回答: State Providerは読み取り操作(GET)のデータ取得を処理します。エンティティ、DTO、または配列を返します。State Processorは書き込み操作(POST、PUT、PATCH、DELETE)を処理します。デシリアライズされた入力を受け取り、永続化の前にビジネスロジックを実行します。この分離はCQRS原則に従っています:クエリはProviderを通じて、コマンドはProcessorを通じて処理されます。

「エンティティを直接公開する代わりに、カスタムAPIリソースを使用するのはどのような場合ですか?」

期待される回答: カスタムリソースは以下の場合に適用されます。

  • API表現がデータベーススキーマと異なる場合
  • 計算フィールドが複数のエンティティからの集計を必要とする場合
  • 書き込みモデルと読み取りモデルで異なる構造が必要な場合
  • 内部エンティティフィールドをAPIコンシューマーから隠す必要がある場合
  • エンティティが進化する一方で、バージョン互換性のために安定した契約が必要な場合

「API Platformはバリデーションをどのように処理しますか?」

期待される回答: API Platformは、エンティティプロパティのSymfony Validatorコンストレイントを使用します。バリデーションは、State Processorが実行される前のデシリアライゼーション中に自動的に実行されます。バリデーショングループは、オペレーションごとに適用されるコンストレイントを制御します。カスタムバリデーターは標準的なSymfonyメカニズムを通じて統合されます。

php
#[ApiResource(
    operations: [
        new Post(validationContext: ['groups' => ['create']]),
        new Put(validationContext: ['groups' => ['update']])
    ]
)]
class Book
{
    #[Assert\NotBlank(groups: ['create', 'update'])]
    private string $title;

    #[Assert\Isbn(groups: ['create'])]
    private string $isbn;  // Required only on creation
}
面接でよくある間違い

候補者は、バリデーショングループやカスタムコンストレイントに言及せずに、バリデーションを「自動」と説明することがよくあります。面接官は、オペレーションごとにバリデーションをカスタマイズする方法の理解を確認しています。

「API Platformが提供するセキュリティメカニズムは何ですか?」

期待される回答: API Platformは以下を通じてSymfony Securityと統合します。

  • ロールベースアクセス用のオペレーションのsecurity属性
  • データバインディング後のオブジェクトレベルチェック用のsecurityPostDenormalize
  • 複雑な認可ロジック用のVoter
  • Symfony Rate Limiter統合によるレート制限
php
#[ApiResource(
    operations: [
        new Get(
            security: "is_granted('ROLE_USER')"
        ),
        new Put(
            security: "is_granted('ROLE_ADMIN') or object.getOwner() == user",
            securityPostDenormalize: "is_granted('BOOK_EDIT', object)"
        )
    ]
)]

フィルターとページネーション: 高度なクエリパターン

API Platformフィルターにより、クライアントはURLパラメータでコレクションをクエリできます。4.2のフィルターシステムは、拡張性を向上させるために再設計されました。

php
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Metadata\ApiFilter;

#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: [
    'title' => 'partial',      // LIKE %value%
    'author.name' => 'exact'   // Nested property
])]
#[ApiFilter(DateFilter::class, properties: ['createdAt'])]
#[ApiFilter(OrderFilter::class, properties: ['title', 'createdAt'])]
class Book { /* ... */ }

生成されるエンドポイント:

text
GET /api/books?title=symfony           # Search by title
GET /api/books?createdAt[after]=2026-01-01  # Date range
GET /api/books?order[createdAt]=desc   # Sorting

API Platformリソースのテスト

API Platformは、機能テストを簡素化するテストクライアントを提供しています。ApiTestCaseクラスは、APIレスポンスに特化したアサーションを提供します。

tests/Api/BookTest.phpphp
namespace App\Tests\Api;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;

class BookTest extends ApiTestCase
{
    public function testGetCollection(): void
    {
        $response = static::createClient()->request('GET', '/api/books');

        $this->assertResponseIsSuccessful();
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
        $this->assertJsonContains([
            '@context' => '/api/contexts/Book',
            '@type' => 'Collection'
        ]);
    }

    public function testCreateBook(): void
    {
        $response = static::createClient()->request('POST', '/api/books', [
            'json' => [
                'title' => 'Symfony Best Practices',
                'description' => 'A guide to modern Symfony development'
            ],
            'headers' => ['Authorization' => 'Bearer ' . $this->getToken()]
        ]);

        $this->assertResponseStatusCodeSame(201);
        $this->assertJsonContains(['title' => 'Symfony Best Practices']);
    }

    public function testCreateBookValidationFails(): void
    {
        $response = static::createClient()->request('POST', '/api/books', [
            'json' => ['description' => 'Missing title'],
            'headers' => ['Authorization' => 'Bearer ' . $this->getToken()]
        ]);

        $this->assertResponseStatusCodeSame(422);
        $this->assertJsonContains([
            'violations' => [
                ['propertyPath' => 'title', 'message' => 'This value should not be blank.']
            ]
        ]);
    }
}

API PlatformとSymfonyの重要ポイント

  • State ProviderはGET操作を、State Processorはミューテーションを処理します。この分離により、カスタムデータソースとビジネスロジックを備えたクリーンなアーキテクチャが実現します
  • Object MapperコンポーネントはAPIリソースをDoctrineエンティティから分離し、異なる読み取り/書き込みモデルを可能にし、内部構造を保護します
  • JSON Streamerは、完全なメモリ割り当てなしでシリアライズすることで、コレクションエンドポイントで32%のパフォーマンス向上を実現します
  • セキュリティは標準的なSymfonyメカニズムを通じて統合されます:security式、Voter、Rate Limiterコンポーネント
  • バリデーショングループはオペレーションごとにコンストレイント適用をカスタマイズします
  • フィルターは自動的にクエリパラメータを公開し、検索、日付、順序フィルターがほとんどのユースケースをカバーします
  • 面接の質問はアーキテクチャ上の決定に焦点を当てます:カスタムProviderを使用するタイミング、読み取り/書き込みモデルの分離方法、セキュリティ実装パターン

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

今日のチャレンジ

Symfony のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月8日 更新

タグ

#symfony
#api-platform
#rest-api
#interview

共有

関連記事