API Platform Symfony REST:完全チュートリアルと面接対策 2026年版
API Platform 4.3を使用してSymfonyでREST APIを構築する方法を解説します。State Provider/Processorパターン、カスタムフィルター、セキュリティ設定、および技術面接で頻出する質問と回答を網羅した実践ガイドです。

API Platform 4.3は、OpenAPIドキュメントの自動生成、コンテンツネゴシエーション、そして読み取り操作と書き込み操作を分離するクリーンなアーキテクチャを備え、SymfonyをREST APIの強力なフレームワークへと変貌させます。本チュートリアルでは、セットアップから高度なパターン、そしてシニア開発者とジュニア開発者を分ける面接質問まで解説します。
API Platform 4では、GET操作にState Provider、POST/PUT/PATCH/DELETE操作にState Processorを使用します。この分離はCQRS原則に沿っており、テストを容易にします。
Symfony 7へのAPI Platformのインストール
API Platformのインストールには、Symfony 7.2以上が必要です。バンドルはデフォルトでDoctrine ORMと統合されますが、Provider/Processorパターンを通じてカスタムデータソースもサポートしています。
# Symfony FlexでAPI Platformをインストール
composer require api
# インストールを確認
php bin/console debug:router | grep apiapiレシピは、api-platform/symfonyとともにserializer、validator、property-accessコンポーネントをインストールします。Symfony Flexは自動的に/api配下にルートを設定します。
# config/packages/api_platform.yaml
api_platform:
title: 'My API'
version: '1.0.0'
formats:
jsonld: ['application/ld+json']
json: ['application/json']
docs_formats:
jsonld: ['application/ld+json']
jsonopenapi: ['application/vnd.openapi+json']
html: ['text/html']
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Accept-Language']stateless: true設定により、すべてのAPIエンドポイントでPHPセッションが無効になり、メモリオーバーヘッドが削減され、水平スケーリングが可能になります。
Attributeを使用した最初のAPIリソースの作成
API Platform 4では、PHP 8のAttributeを使用してAPIリソースを宣言します。各エンティティは#[ApiResource]属性を通じてAPIエンドポイントになります。
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')")
],
paginationItemsPerPage: 30
)]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
private string $name;
#[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
#[Assert\Positive]
private string $price;
#[ORM\Column]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getPrice(): string
{
return $this->price;
}
public function setPrice(string $price): self
{
$this->price = $price;
return $this;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
}securityパラメータはSymfony Expression Languageを使用してアクセスを制御します。これにより、通常のセキュリティvoterと完全に統合されます。
カスタムState Providerの実装
State Providerは読み取り操作のデータ取得を処理します。カスタムProviderにより、Doctrine以外のデータソースとの統合やビジネスロジックの追加が可能になります。
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Repository\ProductRepository;
use Psr\Cache\CacheItemPoolInterface;
final class ProductStateProvider implements ProviderInterface
{
public function __construct(
private ProductRepository $repository,
private CacheItemPoolInterface $cache
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (isset($uriVariables['id'])) {
$cacheKey = 'product_' . $uriVariables['id'];
$item = $this->cache->getItem($cacheKey);
if ($item->isHit()) {
return $item->get();
}
$product = $this->repository->find($uriVariables['id']);
if ($product) {
$item->set($product)->expiresAfter(3600);
$this->cache->save($item);
}
return $product;
}
return $this->repository->findActiveProducts();
}
}Providerをエンティティに登録するには、operationに指定します。
#[ApiResource(
operations: [
new Get(provider: ProductStateProvider::class),
new GetCollection(provider: ProductStateProvider::class)
]
)]カスタムState Processorによる書き込み操作
State Processorは作成、更新、削除の操作を処理します。イベントのディスパッチやデータ変換のカスタムロジックに最適です。
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use App\Message\ProductCreated;
final class ProductStateProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private MessageBusInterface $messageBus
) {}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
): Product {
if ($operation instanceof \ApiPlatform\Metadata\Post) {
$this->em->persist($data);
$this->em->flush();
$this->messageBus->dispatch(new ProductCreated($data->getId()));
return $data;
}
if ($operation instanceof \ApiPlatform\Metadata\Delete) {
$this->em->remove($data);
$this->em->flush();
return $data;
}
$this->em->flush();
return $data;
}
}Processorをoperationに適用します。
#[ApiResource(
operations: [
new Post(processor: ProductStateProcessor::class),
new Put(processor: ProductStateProcessor::class),
new Delete(processor: ProductStateProcessor::class)
]
)]カスタムフィルターとソート
API Platformは組み込みフィルターを提供しますが、カスタムフィルターによりフィルタリングロジックの完全な制御が可能です。
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
final class PriceRangeFilter extends AbstractFilter
{
protected function filterProperty(
string $property,
mixed $value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
if ($property !== 'priceRange') {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$minParam = $queryNameGenerator->generateParameterName('minPrice');
$maxParam = $queryNameGenerator->generateParameterName('maxPrice');
if (isset($value['min'])) {
$queryBuilder
->andWhere("$alias.price >= :$minParam")
->setParameter($minParam, $value['min']);
}
if (isset($value['max'])) {
$queryBuilder
->andWhere("$alias.price <= :$maxParam")
->setParameter($maxParam, $value['max']);
}
}
public function getDescription(string $resourceClass): array
{
return [
'priceRange[min]' => [
'property' => 'price',
'type' => 'float',
'required' => false,
'description' => '最低価格',
],
'priceRange[max]' => [
'property' => 'price',
'type' => 'float',
'required' => false,
'description' => '最高価格',
],
];
}
}エンティティにフィルターを適用します。
use ApiPlatform\Metadata\ApiFilter;
use App\Filter\PriceRangeFilter;
#[ApiResource]
#[ApiFilter(PriceRangeFilter::class)]
class Product
{
// ...
}シリアライゼーショングループとDTO
シリアライゼーショングループは、異なるコンテキストで公開されるフィールドを制御します。DTOはAPIレスポンスと内部エンティティを分離します。
use Symfony\Component\Serializer\Annotation\Groups;
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']]
)]
class User
{
#[Groups(['user:read'])]
private ?int $id = null;
#[Groups(['user:read', 'user:write'])]
private string $email;
#[Groups(['user:write'])]
private string $password;
#[Groups(['user:read'])]
private \DateTimeImmutable $createdAt;
}DTO変換には、State ProviderとProcessorを使用します。
namespace App\Dto;
final class ProductOutput
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly string $formattedPrice,
public readonly string $createdAt
) {}
}namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Dto\ProductOutput;
use App\Repository\ProductRepository;
final class ProductOutputProvider implements ProviderInterface
{
public function __construct(private ProductRepository $repository) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?ProductOutput
{
$product = $this->repository->find($uriVariables['id']);
if (!$product) {
return null;
}
return new ProductOutput(
$product->getId(),
$product->getName(),
'¥' . number_format((float) $product->getPrice()),
$product->getCreatedAt()->format('Y年m月d日')
);
}
}セキュリティとAPI認証
API Platformは、Symfony SecurityおよびJWT認証と統合されます。API Platform 4では、operationレベルとresourceレベルの両方でセキュリティを設定できます。
#[ApiResource(
security: "is_granted('ROLE_USER')",
operations: [
new GetCollection(),
new Get(security: "is_granted('ROLE_USER') and object.owner == user"),
new Post(security: "is_granted('ROLE_ADMIN')"),
new Put(
security: "is_granted('ROLE_ADMIN') or object.owner == user",
securityMessage: 'このリソースを編集する権限がありません。'
),
new Delete(security: "is_granted('ROLE_ADMIN')")
]
)]
class Order
{
// ...
}JWT認証の設定には、lexik/jwt-authentication-bundleを使用します。
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600# config/packages/security.yaml
security:
firewalls:
api:
pattern: ^/api
stateless: true
jwt: ~
access_control:
- { path: ^/api/login, roles: PUBLIC_ACCESS }
- { path: ^/api, roles: ROLE_USER }Symfonyの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
面接で頻出するAPI Platform質問
技術面接では、API Platformの深い理解が求められます。以下の質問と回答は、シニア開発者レベルの知識をカバーしています。
State ProviderとState Processorの違いは何ですか?
State Providerは読み取り操作(GET、GetCollection)でデータを取得する役割を担います。データベースクエリ、キャッシュ読み取り、外部API呼び出しなどを処理します。一方、State Processorは書き込み操作(POST、PUT、PATCH、DELETE)を処理し、データの永続化、イベントのディスパッチ、関連サービスの呼び出しを行います。この分離はCQRSパターンに従い、読み取りと書き込みのロジックを独立してスケーリングおよびテストできます。
API Platformでカスタムoperationを作成する方法は?
カスタムoperationは、標準のCRUD操作では対応できないビジネスロジックに使用します。独自のState Processorを実装し、operationで参照します。
#[ApiResource(
operations: [
new Post(
uriTemplate: '/products/{id}/publish',
controller: PublishProductController::class,
name: 'publish_product'
)
]
)]N+1問題をAPI Platformで解決するには?
Doctrine extensionを使用してeager loadingを設定するか、カスタムState Providerでクエリを最適化します。#[ApiResource(fetchPartial: true)]オプションも部分的なfetchを有効にし、パフォーマンスを向上させます。
// Repository method with optimized query
public function findWithRelations(): array
{
return $this->createQueryBuilder('p')
->leftJoin('p.category', 'c')
->addSelect('c')
->getQuery()
->getResult();
}API Platformでのバリデーションはどのように機能しますか?
API PlatformはSymfony Validatorコンポーネントを使用します。エンティティのプロパティにバリデーション制約を追加すると、API Platformが自動的にリクエストデータを検証します。検証エラーは標準化されたJSON形式で返されます。グループを使用して、operation別に異なるバリデーションルールを適用することも可能です。
#[ApiResource(
operations: [
new Post(validationContext: ['groups' => ['create']]),
new Put(validationContext: ['groups' => ['update']])
]
)]API Platformのページネーションをカスタマイズするには?
デフォルトのページネーションはresourceレベルで設定できます。クライアント側の制御を許可することも、カスタムPaginatorを実装することも可能です。
#[ApiResource(
paginationItemsPerPage: 30,
paginationMaximumItemsPerPage: 100,
paginationClientItemsPerPage: true
)]テストとCI/CD統合
API Platformは、API Testの機能テストをサポートしています。
namespace App\Tests\Api;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
class ProductTest extends ApiTestCase
{
public function testGetProducts(): void
{
$response = static::createClient()->request('GET', '/api/products');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains(['@context' => '/api/contexts/Product']);
}
public function testCreateProduct(): void
{
$response = static::createClient()->request('POST', '/api/products', [
'json' => [
'name' => 'テスト商品',
'price' => '1500.00'
],
'headers' => ['Authorization' => 'Bearer ' . $this->getToken()]
]);
$this->assertResponseStatusCodeSame(201);
$this->assertJsonContains(['name' => 'テスト商品']);
}
}結論
API Platform 4.3は、SymfonyでREST APIを構築するための最も効率的なソリューションです。State Provider/Processorアーキテクチャにより、関心の分離が実現され、テストと保守が容易になります。本チュートリアルで解説したパターンは、プロダクション環境で実証済みであり、面接でも頻繁に問われる内容です。カスタムフィルター、DTOによるレスポンス変換、JWTセキュリティの統合をマスターすることで、スケーラブルなAPIアーキテクチャを設計できるようになります。
Symfony のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年8月25日 更新
タグ
共有
関連記事

2026年版 API Platform と Symfony: アーキテクチャ設計と技術面接対策ガイド
API Platform 4.2とSymfony 7.4を活用したREST API開発の最新手法を解説。State Provider、State Processor、Object Mapper、JSON Streamerによる32%のパフォーマンス向上まで、技術面接で問われる重要概念を網羅します。

Symfony 8の新機能を徹底解説:PHP 8.4レイジーオブジェクト、マルチステップフォーム、面接対策まで
Symfony 8はPHP 8.4を必須とし、ネイティブレイジーオブジェクト、AbstractFlowType、呼び出し可能コマンドなど多数の新機能を搭載しています。本記事では主要機能をコード例とともに解説し、2026年の面接対策ポイントも紹介します。

Doctrine ORM:Symfonyにおけるリレーションのマスター
SymfonyにおけるDoctrine ORMリレーションの完全ガイド。OneToMany、ManyToMany、ロード戦略、パフォーマンス最適化を実例とともに解説します。