API Platform GraphQL Symfony: スキーマ、ミューテーション、面接対策 2026
SymfonyでAPI Platform GraphQLを構築する方法を解説。スキーマ自動生成、カスタムリゾルバー、セキュリティ設定、リアルタイムサブスクリプション、技術面接の質問と回答を網羅。

API Platform GraphQLは、SymfonyアプリケーションをRESTに内在する過剰取得・過少取得の問題を解決する、型安全で強力なAPIに変換します。PHP属性からの自動スキーマ生成とRelay仕様の完全サポートにより、API Platform 4.xは最小限の設定で本番環境に対応したGraphQL実装を提供します。
GraphQLはリクエストで必要なフィールドだけを単一のクエリで取得しますが、RESTは固定されたレスポンス構造を返します。API Platformは同じリソース定義から両方のエンドポイントを生成し、クライアントがユースケースに合ったプロトコルを選択できます。
SymfonyでのGraphQLサポートのインストールと有効化
API PlatformはGraphQL機能を専用パッケージに分離しています。このモジュラーアプローチにより、RESTのみを必要とするプロジェクトではコアを軽量に保てます。
# Install GraphQL support
composer require api-platform/graphqlインストール後、/graphqlエンドポイントが自動的に利用可能になります。スキーマは追加設定なしで既存の#[ApiResource]属性から生成されます。
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Mutation;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource(
graphQlOperations: [
new Query(),
new QueryCollection(),
new Mutation(name: 'create'),
new Mutation(name: 'update'),
new Mutation(name: 'delete'),
]
)]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $title;
#[ORM\Column(length: 13)]
private string $isbn;
#[ORM\Column]
private \DateTimeImmutable $publishedAt;
// Getters and setters...
}この単一のエンティティ定義により、IDまたはコレクションによる書籍取得のクエリと、作成・更新・削除操作のミューテーションが公開されます。GraphQLスキーマはPHP型を直接反映します:stringはString!に、nullable型はオプションフィールドになります。
GraphQLクエリとミューテーションの記述
GraphQLクエリは返すフィールドを正確に指定します。この精度により、帯域幅の無駄を排除し、クライアント側のデータ変換を削減できます。
# Fetch a single book with specific fields
query GetBook {
book(id: "/books/42") {
title
isbn
publishedAt
}
}
# Fetch a collection with pagination
query ListBooks {
books(first: 10, after: "cursor123") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}ミューテーションはRelay仕様に従い、inputオブジェクトとリクエスト追跡用のclientMutationIdを使用します。
# Create a new book
mutation CreateBook {
createBook(input: {
title: "Domain-Driven Design"
isbn: "9780321125217"
publishedAt: "2003-08-30"
clientMutationId: "create-1"
}) {
book {
id
title
}
clientMutationId
}
}
# Update an existing book
mutation UpdateBook {
updateBook(input: {
id: "/books/42"
title: "Updated Title"
clientMutationId: "update-1"
}) {
book {
id
title
}
}
}clientMutationIdは、バッチシナリオでクライアントがリクエストとレスポンスを関連付けるのに役立ちます。API Platformはこれをレスポンスでそのまま返します。
複雑なビジネスロジック用カスタムリゾルバーの実装
デフォルトのCRUD操作は基本的なケースをカバーしますが、実際のアプリケーションにはカスタムビジネスロジックが必要です。API Platformはクエリとミューテーション用のリゾルバーインターフェースを提供しています。
namespace App\Resolver;
use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
use App\Repository\BookRepository;
final class BookBestSellerResolver implements QueryCollectionResolverInterface
{
public function __construct(
private readonly BookRepository $bookRepository
) {}
/**
* @param iterable<Book> $collection
* @return iterable<Book>
*/
public function __invoke(iterable $collection, array $context): iterable
{
// Access GraphQL arguments from context
$limit = $context['args']['limit'] ?? 10;
$period = $context['args']['period'] ?? 'month';
return $this->bookRepository->findBestSellers($limit, $period);
}
}カスタムリゾルバーをエンティティ設定に登録します:
#[ApiResource(
graphQlOperations: [
new QueryCollection(
name: 'bestSellers',
resolver: BookBestSellerResolver::class,
args: [
'limit' => ['type' => 'Int', 'default_value' => 10],
'period' => ['type' => 'String', 'default_value' => 'month'],
]
),
]
)]
class Book
{
// ...
}これにより、limitとperiod引数を受け取るbestSellersクエリが公開され、デフォルトのDoctrineクエリではなくカスタムリポジトリロジックが実行されます。
Symfonyの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
VoterとExpressionを使用したGraphQL操作のセキュリティ
GraphQLのセキュリティ設定はRESTとは独立して動作します。各操作はSymfonyのエクスプレッション言語を使用して独自のアクセスルールを定義できます。
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\Mutation;
#[ApiResource(
graphQlOperations: [
new Query(
security: "is_granted('ROLE_USER')"
),
new QueryCollection(
security: "is_granted('ROLE_USER')"
),
new Mutation(
name: 'create',
security: "is_granted('ROLE_EDITOR')"
),
new Mutation(
name: 'update',
security: "is_granted('ROLE_EDITOR') and object.getAuthor() == user",
securityMessage: "Only the author can update this book."
),
new Mutation(
name: 'delete',
security: "is_granted('ROLE_ADMIN')"
),
]
)]
class Book
{
// ...
}セキュリティエクスプレッションのobject変数は、アクセスされているエンティティを参照します。これにより、きめ細かな所有権チェックが可能になります。複雑な認可ロジックには、インライン式よりもSymfony Security votersがクリーンなソリューションを提供します。
namespace App\Security\Voter;
use App\Entity\Book;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
class BookVoter extends Voter
{
public const EDIT = 'BOOK_EDIT';
public const DELETE = 'BOOK_DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE])
&& $subject instanceof Book;
}
protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool {
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** @var Book $book */
$book = $subject;
return match($attribute) {
self::EDIT => $book->getAuthor() === $user,
self::DELETE => in_array('ROLE_ADMIN', $user->getRoles()),
default => false,
};
}
}GraphQLサブスクリプションによるリアルタイム更新
API PlatformはMercureを通じてGraphQLサブスクリプションを実装しています。Mercureはサーバー送信イベント用のプロトコルです。サブスクリプションは、リソースが変更されたときにデータをクライアントにプッシュします。
use ApiPlatform\Metadata\GraphQl\Subscription;
#[ApiResource(
mercure: true,
graphQlOperations: [
new Query(),
new Mutation(name: 'update'),
new Subscription(),
]
)]
class Book
{
// ...
}クライアントは標準のGraphQLサブスクリプション構文を使用して変更を購読します:
subscription BookUpdates {
updateBookSubscribe(input: { id: "/books/42" }) {
book {
id
title
updatedAt
}
}
}ミューテーションが書籍を更新すると、Mercureが購読しているすべてのクライアントに変更をブロードキャストします。このパターンは、コラボレーションアプリケーション、ライブダッシュボード、リアルタイム通知に適しています。高スループットシナリオでは、サブスクリプションをSymfony Messengerと組み合わせて、ミューテーション処理と通知ディスパッチを分離します。
面接対策:API Platform GraphQL
Symfonyポジションの技術面接では、GraphQL統合に関する質問が増えています。これらの質問は、仕様とAPI Platformの実装の両方の理解をテストします。
Q: API PlatformはGraphQLスキーマをどのように生成しますか?
API Platformは#[ApiResource]属性とPHP型宣言をイントロスペクトしてスキーマを構築します。エンティティプロパティがフィールドになり、PHP型がGraphQL型にマッピングされます。開発モードではリクエストごとにスキーマが再生成され、本番環境ではキャッシュされます。
Q: QueryとQueryCollection操作の違いは何ですか?
Queryは識別子で単一のアイテムを取得し、id引数が必要です。QueryCollectionはオプションのフィルタリング、ページネーション、ソートとともに複数のアイテムを返します。両方ともカスタムリゾルバーを持つことができますが、インターフェースが異なります:QueryItemResolverInterfaceとQueryCollectionResolverInterfaceです。
Q: API Platform GraphQLでN+1クエリ問題をどのように処理しますか?
DataLoaderパターンは複数のデータベースクエリを1つにバッチ処理します。API PlatformはクエリエクステンションのフェッチジョインによるDoctrineの即時読み込みと統合されています。複雑なケースでは、DoctrineのaddSelect()を使用して初期クエリで関連付けを取得するカスタムリゾルバーを実装します。
// Custom query extension for eager loading
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
$queryBuilder
->addSelect('author')
->leftJoin('o.author', 'author');
}Q: 同じリソースでRESTとGraphQLのセキュリティルールを異なるものにできますか?
はい。REST操作は#[Get]、#[Post]などでsecurityを使用し、GraphQL操作は#[Query]、#[Mutation]などでsecurityを使用します。この分離により、一方のプロトコルに対してより厳格なルールを設定できます。一般的なパターンは、パブリッククライアント向けに読み取り専用GraphQLを公開し、RESTミューテーションには認証を要求するものです。
Q: GraphQLスキーマにカスタムスカラー型を追加するにはどうすればよいですか?
config/packages/api_platform.yamlでカスタム型を登録し、シリアライゼーションロジックを実装します:
# config/packages/api_platform.yaml
api_platform:
graphql:
enabled: true
graphiql:
enabled: truenamespace App\GraphQL\Type;
use GraphQL\Type\Definition\ScalarType;
final class DateTimeType extends ScalarType
{
public string $name = 'DateTime';
public function serialize($value): string
{
return $value->format(\DateTimeInterface::RFC3339);
}
public function parseValue($value): \DateTimeImmutable
{
return new \DateTimeImmutable($value);
}
public function parseLiteral($valueNode, ?array $variables = null): \DateTimeImmutable
{
return new \DateTimeImmutable($valueNode->value);
}
}ページネーション戦略:カーソル vs ページベース
API PlatformはRelay Connection仕様に従い、デフォルトでカーソルベースのページネーションを使用します。挿入が結果をシフトしないため、このアプローチはリアルタイムデータをオフセットページネーションよりもうまく処理します。
# Cursor-based (default)
query {
books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
edges {
cursor
node {
title
}
}
pageInfo {
endCursor
hasNextPage
}
}
}ページベースのページネーションは、クライアントが直接ページアクセスを必要とするシンプルなユースケースに適しています:
// Enable page-based pagination
#[ApiResource(
paginationType: 'page',
graphQlOperations: [
new QueryCollection(paginationType: 'page'),
]
)]
class Book {}# Page-based
query {
books(page: 2, itemsPerPage: 20) {
collection {
title
}
paginationInfo {
totalCount
lastPage
}
}
}カーソルページネーションはOFFSETクエリを回避するため、スケール時のパフォーマンスが向上します。トレードオフは、クライアントが任意のページにジャンプできないことです。
SymfonyでのGraphQLエンドポイントのテスト
機能テストはSymfonyのテストクライアントを使用してGraphQLの動作を検証します。API PlatformはGraphQL専用のテストトレイトを提供しています。
namespace App\Tests\GraphQL;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;
class BookTest extends ApiTestCase
{
public function testQueryBook(): void
{
$client = static::createClient();
// Create test data
$book = new Book();
$book->setTitle('Test Book');
$book->setIsbn('1234567890123');
$book->setPublishedAt(new \DateTimeImmutable());
$em = static::getContainer()->get('doctrine')->getManager();
$em->persist($book);
$em->flush();
// Execute GraphQL query
$response = $client->request('POST', '/graphql', [
'json' => [
'query' => '
query GetBook($id: ID!) {
book(id: $id) {
title
isbn
}
}
',
'variables' => [
'id' => '/books/' . $book->getId(),
],
],
]);
$this->assertResponseIsSuccessful();
$data = $response->toArray();
$this->assertEquals('Test Book', $data['data']['book']['title']);
$this->assertEquals('1234567890123', $data['data']['book']['isbn']);
}
public function testMutationRequiresAuthentication(): void
{
$client = static::createClient();
$response = $client->request('POST', '/graphql', [
'json' => [
'query' => '
mutation CreateBook {
createBook(input: {
title: "Unauthorized Book"
isbn: "0000000000000"
clientMutationId: "test"
}) {
book { id }
}
}
',
],
]);
$data = $response->toArray();
$this->assertArrayHasKey('errors', $data);
}
}これらのテストは、成功した操作とセキュリティの適用の両方を検証します。php bin/phpunit tests/GraphQL/で実行して、API動作のリグレッションを検出します。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
API Platform GraphQL in Symfony 重要ポイント
api-platform/graphqlをインストールすると、#[ApiResource]属性からの自動スキーマ生成で/graphqlエンドポイントが有効になりますQuery、QueryCollection、Mutation操作を使用して、各リソースが公開するGraphQL操作を制御します- CRUD以外のカスタムビジネスロジックには
QueryItemResolverInterfaceまたはMutationResolverInterfaceを実装します - GraphQL操作のセキュリティ式はRESTとは独立して機能し、プロトコルごとに異なるアクセスルールを許可します
- リソースが更新されたときにクライアントに変更をプッシュするリアルタイムサブスクリプションにはMercureを有効にします
- カーソルベースのページネーションはページベースよりもリアルタイムデータをうまく処理しますが、直接ページアクセスを犠牲にします
ApiTestCaseとJSONのPOSTリクエストを使用して/graphqlへのGraphQLエンドポイントをテストします- 面接の質問はスキーマ生成、N+1問題、セキュリティ分離、カスタムリゾルバーに焦点を当てます
Symfony のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

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

2026年版 Symfony REST APIセキュリティ完全ガイド: OAuth2、レート制限、面接対策
Symfony 7.3で実装するREST APIセキュリティの実践ガイド。OAuth2トークンイントロスペクション、RateLimiterコンポーネント、Voter認可、技術面接対策を解説します。

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

Symfony REST APIセキュリティ:JWT認証と面接質問 2026年完全ガイド
Symfony REST APIのセキュリティ対策について、JWT認証の実装からファイアウォール設定、リフレッシュトークン、面接質問まで網羅的に解説する2026年版ガイド。