API Platform GraphQL Symfony: Schema, Mutation và Câu hỏi Phỏng vấn 2026
Hướng dẫn toàn diện tích hợp API Platform GraphQL với Symfony. Tìm hiểu schema tự động, mutation, resolver tùy chỉnh, bảo mật và câu hỏi phỏng vấn kỹ thuật cho developer 2026.

API Platform GraphQL chuyển đổi ứng dụng Symfony thành các API mạnh mẽ và type-safe, giải quyết vấn đề over-fetching và under-fetching vốn có trong REST. Với việc tạo schema tự động từ các attribute PHP và hỗ trợ đầy đủ đặc tả Relay, API Platform 4.x cung cấp một triển khai GraphQL sẵn sàng cho production với cấu hình tối thiểu.
GraphQL yêu cầu chính xác các trường cần thiết trong một truy vấn duy nhất, trong khi REST trả về cấu trúc phản hồi cố định. API Platform tạo cả hai endpoint từ cùng một định nghĩa resource, cho phép client chọn giao thức phù hợp với trường hợp sử dụng của họ.
Cài đặt và Kích hoạt Hỗ trợ GraphQL trong Symfony
API Platform tách chức năng GraphQL vào một package riêng. Cách tiếp cận module này giữ cho core nhẹ cho các dự án chỉ cần REST.
# Install GraphQL support
composer require api-platform/graphqlSau khi cài đặt, endpoint /graphql tự động khả dụng. Schema được tạo từ các attribute #[ApiResource] hiện có mà không cần cấu hình thêm.
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...
}Định nghĩa entity đơn lẻ này expose các query để lấy sách theo ID hoặc như một collection, cộng với các mutation cho các thao tác create, update và delete. Schema GraphQL phản ánh trực tiếp các kiểu PHP: string trở thành String!, các kiểu nullable trở thành các trường tùy chọn.
Viết các Query và Mutation GraphQL
Các query GraphQL chỉ định chính xác các trường nào sẽ được trả về. Sự chính xác này loại bỏ lãng phí băng thông và giảm chuyển đổi dữ liệu phía client.
# 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
}
}
}Các mutation tuân theo đặc tả Relay với các đối tượng input và clientMutationId để theo dõi request.
# 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 giúp client liên kết các response với request trong các tình huống batch. API Platform trả về nó không thay đổi trong response.
Triển khai Custom Resolver cho Logic Nghiệp vụ Phức tạp
Các thao tác CRUD mặc định bao gồm các trường hợp cơ bản, nhưng các ứng dụng thực tế cần logic nghiệp vụ tùy chỉnh. API Platform cung cấp các interface resolver cho các query và mutation.
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);
}
}Đăng ký custom resolver trong cấu hình entity:
#[ApiResource(
graphQlOperations: [
new QueryCollection(
name: 'bestSellers',
resolver: BookBestSellerResolver::class,
args: [
'limit' => ['type' => 'Int', 'default_value' => 10],
'period' => ['type' => 'String', 'default_value' => 'month'],
]
),
]
)]
class Book
{
// ...
}Cấu hình này expose một query bestSellers chấp nhận các đối số limit và period, thực thi logic repository tùy chỉnh thay vì các query Doctrine mặc định.
Sẵn sàng chinh phục phỏng vấn Symfony?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Bảo mật các Thao tác GraphQL với Voter và Expression
Cấu hình bảo mật cho GraphQL hoạt động độc lập với REST. Mỗi thao tác có thể định nghĩa các quy tắc truy cập riêng sử dụng ngôn ngữ biểu thức của 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
{
// ...
}Biến object trong các security expression tham chiếu đến entity đang được truy cập. Điều này cho phép kiểm tra quyền sở hữu chi tiết. Đối với logic ủy quyền phức tạp, Symfony Security voters cung cấp một giải pháp sạch hơn các biểu thức inline.
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,
};
}
}Cập nhật Thời gian thực với GraphQL Subscription
API Platform triển khai các subscription GraphQL thông qua Mercure, một giao thức cho server-sent events. Các subscription đẩy dữ liệu đến client khi resource thay đổi.
use ApiPlatform\Metadata\GraphQl\Subscription;
#[ApiResource(
mercure: true,
graphQlOperations: [
new Query(),
new Mutation(name: 'update'),
new Subscription(),
]
)]
class Book
{
// ...
}Client đăng ký theo dõi các thay đổi sử dụng cú pháp subscription GraphQL chuẩn:
subscription BookUpdates {
updateBookSubscribe(input: { id: "/books/42" }) {
book {
id
title
updatedAt
}
}
}Khi một mutation cập nhật sách, Mercure phát sóng thay đổi đến tất cả các client đã đăng ký. Mô hình này phù hợp cho các ứng dụng cộng tác, dashboard thời gian thực và thông báo trực tiếp. Đối với các tình huống throughput cao, kết hợp subscription với Symfony Messenger để tách xử lý mutation khỏi việc gửi thông báo.
Câu hỏi Phỏng vấn: API Platform GraphQL
Các cuộc phỏng vấn kỹ thuật cho các vị trí Symfony ngày càng bao gồm tích hợp GraphQL. Các câu hỏi này kiểm tra sự hiểu biết về cả đặc tả và triển khai của API Platform.
H: API Platform tạo schema GraphQL như thế nào?
API Platform xem xét các attribute #[ApiResource] và khai báo kiểu PHP để xây dựng schema. Các thuộc tính entity trở thành các trường, với các kiểu PHP được ánh xạ sang các kiểu GraphQL. Schema được tạo lại trên mỗi request trong chế độ development và được cache trong production.
H: Sự khác biệt giữa các thao tác Query và QueryCollection là gì?
Query lấy một item đơn lẻ theo identifier và yêu cầu đối số id. QueryCollection trả về nhiều item với filtering, pagination và sorting tùy chọn. Cả hai có thể có custom resolver, nhưng các interface của chúng khác nhau: QueryItemResolverInterface vs QueryCollectionResolverInterface.
H: Làm thế nào để xử lý vấn đề truy vấn N+1 trong API Platform GraphQL?
Mô hình DataLoader gộp nhóm nhiều truy vấn cơ sở dữ liệu thành một. API Platform tích hợp với eager loading của Doctrine thông qua fetch join trong query extension. Đối với các trường hợp phức tạp, triển khai custom resolver sử dụng addSelect() của Doctrine để lấy các association trong truy vấn ban đầu.
// 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');
}H: Các quy tắc bảo mật REST và GraphQL có thể khác nhau cho cùng một resource không?
Có. Các thao tác REST sử dụng security trên #[Get], #[Post], v.v., trong khi các thao tác GraphQL sử dụng security trên #[Query], #[Mutation], v.v. Sự tách biệt này cho phép các quy tắc nghiêm ngặt hơn cho một giao thức. Một mô hình phổ biến là expose GraphQL chỉ đọc cho các client công cộng trong khi các mutation REST yêu cầu xác thực.
H: Làm thế nào để thêm các kiểu scalar tùy chỉnh vào schema GraphQL?
Đăng ký một kiểu tùy chỉnh trong config/packages/api_platform.yaml và triển khai logic serialization:
# 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);
}
}Chiến lược Phân trang: Cursor vs Page-Based
API Platform mặc định sử dụng phân trang dựa trên cursor theo đặc tả Relay Connection. Cách tiếp cận này xử lý dữ liệu thời gian thực tốt hơn phân trang offset vì các chèn vào không làm dịch chuyển kết quả.
# Cursor-based (default)
query {
books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
edges {
cursor
node {
title
}
}
pageInfo {
endCursor
hasNextPage
}
}
}Phân trang dựa trên trang phù hợp cho các trường hợp sử dụng đơn giản hơn khi client cần truy cập trang trực tiếp:
// 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
}
}
}Phân trang cursor hoạt động tốt hơn ở quy mô lớn vì tránh các truy vấn OFFSET. Đánh đổi là client không thể nhảy đến các trang tùy ý.
Kiểm thử các Endpoint GraphQL trong Symfony
Các bài kiểm thử chức năng xác minh hành vi GraphQL sử dụng test client của Symfony. API Platform cung cấp một trait kiểm thử đặc biệt cho 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);
}
}Các bài kiểm thử này xác nhận cả các thao tác thành công và thực thi bảo mật. Chạy chúng với php bin/phpunit tests/GraphQL/ để phát hiện các hồi quy trong hành vi API.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Những Điểm Chính cho API Platform GraphQL trong Symfony
- Cài đặt
api-platform/graphqlđể kích hoạt endpoint/graphqlvới việc tạo schema tự động từ các attribute#[ApiResource] - Sử dụng các thao tác
Query,QueryCollectionvàMutationđể kiểm soát các thao tác GraphQL nào mỗi resource expose - Triển khai
QueryItemResolverInterfacehoặcMutationResolverInterfacecho logic nghiệp vụ tùy chỉnh ngoài CRUD - Các security expression trên các thao tác GraphQL hoạt động độc lập với REST, cho phép các quy tắc truy cập khác nhau theo giao thức
- Kích hoạt Mercure cho các subscription thời gian thực đẩy các thay đổi đến client khi resource được cập nhật
- Phân trang dựa trên cursor xử lý dữ liệu thời gian thực tốt hơn phân trang dựa trên trang, nhưng hy sinh truy cập trang trực tiếp
- Kiểm thử các endpoint GraphQL với
ApiTestCasevà các request JSON POST đến/graphql - Các câu hỏi phỏng vấn tập trung vào việc tạo schema, vấn đề N+1, tách biệt bảo mật và các custom resolver
Bạn có tìm ra lỗi trong Symfony không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 27 tháng 8, 2026
Chia sẻ
Bài viết liên quan

Bảo mật REST API Symfony năm 2026: OAuth2, Rate Limiting và Câu hỏi Phỏng vấn
Tìm hiểu cách bảo mật REST API Symfony với OAuth2 token introspection, rate limiting và xác thực JWT. Bài viết đề cập các tính năng bảo mật Symfony 7.3, lỗ hổng phổ biến và câu hỏi phỏng vấn kỹ thuật.

API Platform với Symfony 2026: Kiến trúc Hiện đại và Câu hỏi Phỏng vấn
Hướng dẫn toàn diện về API Platform với Symfony năm 2026, bao gồm kiến trúc SmartPlatform, State Providers, custom filters và các câu hỏi phỏng vấn kỹ thuật cho developer.

Bao Mat REST API Symfony: Xac Thuc, JWT va Cau Hoi Phong Van 2026
Huong dan toan dien ve bao mat REST API Symfony voi LexikJWTAuthenticationBundle 3.2. Tim hieu cau hinh JWT, refresh token, voter, rate limiting va cac cau hoi phong van pho bien cho Symfony 7.2.