API Platform GraphQL Symfony: Şemalar, Mutasyonlar ve Mülakat Soruları 2026

Symfony'de API Platform ile GraphQL entegrasyonu için kapsamlı rehber. Şemalar, sorgular, mutasyonlar, resolver'lar, güvenlik ve mülakat soruları.

API Platform GraphQL Symfony şemalar mutasyonlar

API Platform GraphQL, Symfony uygulamalarını REST'e özgü aşırı veri çekme ve yetersiz veri çekme sorunlarını çözen güçlü, tip güvenli API'lere dönüştürür. PHP attribute'larından otomatik şema üretimi ve tam Relay spesifikasyonu desteği ile API Platform 4.x, minimum yapılandırma gerektiren üretime hazır bir GraphQL implementasyonu sunar.

Temel Fark: API Platform'da GraphQL vs REST

GraphQL, tek bir sorguda tam olarak ihtiyaç duyulan alanları talep ederken, REST sabit yanıt yapıları döndürür. API Platform, aynı kaynak tanımından her iki endpoint'i de oluşturarak istemcilerin kullanım senaryolarına uygun protokolü seçmesine olanak tanır.

Symfony'de GraphQL Desteğini Yükleme ve Etkinleştirme

API Platform, GraphQL işlevselliğini ayrı bir pakete ayırır. Bu modüler yaklaşım, yalnızca REST'e ihtiyaç duyan projeler için çekirdeği hafif tutar.

bash
# GraphQL desteğini yükle
composer require api-platform/graphql

Yüklendikten sonra /graphql endpoint'i otomatik olarak kullanılabilir hale gelir. Şema, ek yapılandırma olmadan mevcut #[ApiResource] attribute'larından oluşturulur.

src/Entity/Book.phpphp
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;

    // Getter ve setter'lar...
}

Bu tek entity tanımı, kitapları ID'ye göre veya koleksiyon olarak almak için sorgular ile oluşturma, güncelleme ve silme işlemleri için mutasyonlar sunar. GraphQL şeması PHP tiplerini doğrudan yansıtır: string, String! olur; nullable tipler opsiyonel alanlara dönüşür.

GraphQL Sorguları ve Mutasyonları Yazma

GraphQL sorguları, tam olarak hangi alanların döndürüleceğini belirtir. Bu kesinlik, bant genişliği israfını ortadan kaldırır ve istemci tarafında veri dönüşümünü azaltır.

graphql
# Belirli alanlarla tek bir kitap getir
query GetBook {
  book(id: "/books/42") {
    title
    isbn
    publishedAt
  }
}

# Sayfalandırma ile koleksiyon getir
query ListBooks {
  books(first: 10, after: "cursor123") {
    edges {
      node {
        id
        title
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Mutasyonlar, istek takibi için input nesneleri ve clientMutationId ile Relay spesifikasyonunu takip eder.

graphql
# Yeni bir kitap oluştur
mutation CreateBook {
  createBook(input: {
    title: "Domain-Driven Design"
    isbn: "9780321125217"
    publishedAt: "2003-08-30"
    clientMutationId: "create-1"
  }) {
    book {
      id
      title
    }
    clientMutationId
  }
}

# Mevcut bir kitabı güncelle
mutation UpdateBook {
  updateBook(input: {
    id: "/books/42"
    title: "Güncellenmiş Başlık"
    clientMutationId: "update-1"
  }) {
    book {
      id
      title
    }
  }
}

clientMutationId, istemcilerin toplu senaryolarda yanıtları isteklerle ilişkilendirmesine yardımcı olur. API Platform bunu yanıtta değişmeden döndürür.

Karmaşık İş Mantığı İçin Özel Resolver'lar Uygulama

Varsayılan CRUD işlemleri temel durumları kapsar, ancak gerçek uygulamalar özel iş mantığına ihtiyaç duyar. API Platform, sorgular ve mutasyonlar için resolver arayüzleri sağlar.

src/Resolver/BookBestSellerResolver.phpphp
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
    {
        // Context'ten GraphQL argümanlarına erişim
        $limit = $context['args']['limit'] ?? 10;
        $period = $context['args']['period'] ?? 'month';

        return $this->bookRepository->findBestSellers($limit, $period);
    }
}

Özel resolver'ı entity yapılandırmasına kaydetme:

src/Entity/Book.phpphp
#[ApiResource(
    graphQlOperations: [
        new QueryCollection(
            name: 'bestSellers',
            resolver: BookBestSellerResolver::class,
            args: [
                'limit' => ['type' => 'Int', 'default_value' => 10],
                'period' => ['type' => 'String', 'default_value' => 'month'],
            ]
        ),
    ]
)]
class Book
{
    // ...
}

Bu, varsayılan Doctrine sorguları yerine özel repository mantığını çalıştıran limit ve period argümanlarını kabul eden bir bestSellers sorgusu sunar.

Symfony mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

GraphQL İşlemlerini Voter'lar ve İfadelerle Güvence Altına Alma

GraphQL için güvenlik yapılandırması REST'ten bağımsız çalışır. Her işlem, Symfony'nin ifade dilini kullanarak kendi erişim kurallarını tanımlayabilir.

src/Entity/Book.phpphp
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: "Yalnızca yazar bu kitabı güncelleyebilir."
        ),
        new Mutation(
            name: 'delete',
            security: "is_granted('ROLE_ADMIN')"
        ),
    ]
)]
class Book
{
    // ...
}

Güvenlik ifadelerindeki object değişkeni, erişilen entity'yi ifade eder. Bu, ayrıntılı sahiplik kontrollerine olanak tanır. Karmaşık yetkilendirme mantığı için Symfony Security voter'ları, inline ifadelerden daha temiz bir çözüm sağlar.

src/Security/Voter/BookVoter.phpphp
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 Subscription'ları ile Gerçek Zamanlı Güncellemeler

API Platform, sunucu tarafından gönderilen olaylar için bir protokol olan Mercure aracılığıyla GraphQL subscription'larını uygular. Subscription'lar, kaynaklar değiştiğinde istemcilere veri iletir.

src/Entity/Book.phpphp
use ApiPlatform\Metadata\GraphQl\Subscription;

#[ApiResource(
    mercure: true,
    graphQlOperations: [
        new Query(),
        new Mutation(name: 'update'),
        new Subscription(),
    ]
)]
class Book
{
    // ...
}

İstemciler, standart GraphQL subscription sözdizimini kullanarak değişikliklere abone olur:

graphql
subscription BookUpdates {
  updateBookSubscribe(input: { id: "/books/42" }) {
    book {
      id
      title
      updatedAt
    }
  }
}

Bir mutasyon kitabı güncellediğinde, Mercure değişikliği tüm abone olan istemcilere yayınlar. Bu model, işbirliği uygulamaları, canlı panolar ve gerçek zamanlı bildirimler için uygundur.

Mülakat Soruları: API Platform GraphQL

Symfony pozisyonları için teknik mülakatlar giderek daha fazla GraphQL entegrasyonunu kapsar. Bu sorular, hem spesifikasyonun hem de API Platform'un implementasyonunun anlaşılmasını test eder.

S: API Platform GraphQL şemasını nasıl oluşturur?

API Platform, şemayı oluşturmak için #[ApiResource] attribute'larını ve PHP tip bildirimlerini inceler. Entity özellikleri alanlara dönüşür ve PHP tipleri GraphQL tiplerine eşlenir. Şema, geliştirme modunda her istekte yeniden oluşturulur ve üretimde önbelleğe alınır.

S: Query ve QueryCollection işlemleri arasındaki fark nedir?

Query, tanımlayıcıya göre tek bir öğe getirir ve bir id argümanı gerektirir. QueryCollection, opsiyonel filtreleme, sayfalandırma ve sıralama ile birden fazla öğe döndürür. Her ikisinin de özel resolver'ları olabilir, ancak arayüzleri farklıdır: QueryItemResolverInterface ve QueryCollectionResolverInterface.

S: API Platform GraphQL'de N+1 sorgu problemleri nasıl ele alınır?

DataLoader deseni, birden fazla veritabanı sorgusunu tek bir sorguda gruplar. API Platform, sorgu uzantısındaki fetch join'ler aracılığıyla Doctrine'in eager loading'i ile entegre olur. Karmaşık durumlar için, başlangıç sorgusunda ilişkileri getirmek üzere Doctrine'in addSelect() metodunu kullanan özel bir resolver uygulanmalıdır.

php
// Eager loading için özel sorgu uzantısı
public function applyToCollection(
    QueryBuilder $queryBuilder,
    QueryNameGeneratorInterface $queryNameGenerator,
    string $resourceClass,
    ?Operation $operation = null,
    array $context = []
): void {
    $queryBuilder
        ->addSelect('author')
        ->leftJoin('o.author', 'author');
}

S: Aynı kaynak için REST ve GraphQL güvenlik kuralları farklı olabilir mi?

Evet. REST işlemleri #[Get], #[Post] vb. üzerinde security kullanırken, GraphQL işlemleri #[Query], #[Mutation] vb. üzerinde security kullanır. Bu ayrım, bir protokol için daha katı kurallar tanımlanmasına olanak tanır. Yaygın bir model, genel istemciler için salt okunur GraphQL sunarken REST mutasyonları için kimlik doğrulama gerektirir.

S: GraphQL şemasına özel skaler tipler nasıl eklenir?

config/packages/api_platform.yaml dosyasında özel bir tip kaydedilmeli ve serileştirme mantığı uygulanmalıdır:

yaml
# config/packages/api_platform.yaml
api_platform:
    graphql:
        enabled: true
        graphiql:
            enabled: true
src/GraphQL/Type/DateTimeType.phpphp
namespace 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);
    }
}

Sayfalandırma Stratejileri: Cursor vs Sayfa Tabanlı

API Platform, Relay Connection spesifikasyonunu takip eden cursor tabanlı sayfalandırmayı varsayılan olarak kullanır. Bu yaklaşım, eklemeler sonuçları kaydırmadığından gerçek zamanlı verileri offset sayfalandırmasından daha iyi işler.

graphql
# Cursor tabanlı (varsayılan)
query {
  books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
    edges {
      cursor
      node {
        title
      }
    }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}

Sayfa tabanlı sayfalandırma, istemcilerin doğrudan sayfa erişimine ihtiyaç duyduğu basit kullanım durumlarına uygundur:

php
// Sayfa tabanlı sayfalandırmayı etkinleştir
#[ApiResource(
    paginationType: 'page',
    graphQlOperations: [
        new QueryCollection(paginationType: 'page'),
    ]
)]
class Book {}
graphql
# Sayfa tabanlı
query {
  books(page: 2, itemsPerPage: 20) {
    collection {
      title
    }
    paginationInfo {
      totalCount
      lastPage
    }
  }
}

Cursor sayfalandırması, OFFSET sorgularından kaçındığı için ölçekte daha iyi performans gösterir. Ödünleşim, istemcilerin rastgele sayfalara atlayamamasıdır.

Symfony'de GraphQL Endpoint'lerini Test Etme

Fonksiyonel testler, Symfony'nin test istemcisini kullanarak GraphQL davranışını doğrular. API Platform, GraphQL'e özgü bir test trait'i sağlar.

tests/GraphQL/BookTest.phpphp
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();

        // Test verisi oluştur
        $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();

        // GraphQL sorgusunu çalıştır
        $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);
    }
}

Bu testler hem başarılı işlemleri hem de güvenlik uygulamasını doğrular. php bin/phpunit tests/GraphQL/ komutuyla çalıştırılarak API davranışındaki regresyonlar yakalanabilir.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Symfony'de API Platform GraphQL İçin Temel Çıkarımlar

  • api-platform/graphql yüklemesi, #[ApiResource] attribute'larından otomatik şema üretimi ile /graphql endpoint'ini etkinleştirir
  • Query, QueryCollection ve Mutation işlemlerinin kullanımı, her kaynağın hangi GraphQL işlemlerini sunacağını kontrol eder
  • QueryItemResolverInterface veya MutationResolverInterface implementasyonu, CRUD'un ötesinde özel iş mantığını işler
  • GraphQL işlemlerindeki güvenlik ifadeleri REST'ten bağımsız çalışır ve protokol başına farklı erişim kurallarına olanak tanır
  • Mercure'un etkinleştirilmesi, kaynaklar güncellendiğinde değişiklikleri istemcilere ileten gerçek zamanlı subscription'lar sağlar
  • Cursor tabanlı sayfalandırma, gerçek zamanlı verileri sayfa tabanlıdan daha iyi işler ancak doğrudan sayfa erişiminden vazgeçer
  • ApiTestCase ve /graphql'e JSON POST istekleri ile GraphQL endpoint'lerinin test edilmesi
  • Mülakat soruları şema üretimi, N+1 problemleri, güvenlik ayrımı ve özel resolver'lara odaklanır
Günün meydan okuması

Symfony kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

27 Ağustos 2026 tarihinde güncellendi

Etiketler

#symfony
#graphql
#api-platform
#php
#backend

Paylaş

İlgili makaleler