API Platform com Symfony em 2026: Arquitetura, State Providers e Perguntas de Entrevista

Dominar API Platform 4.2 com Symfony: State Providers, Processors, Object Mapper, JSON Streamer para otimização de performance, e perguntas de entrevista técnica para desenvolvedores seniores.

Diagrama de arquitetura API Platform Symfony com fluxo de trabalho REST API

API Platform 4.2 transforma a maneira como aplicações Symfony expõem APIs REST e GraphQL. Esta versão introduz o Symfony Object Mapper para separação limpa de recursos, o JSON Streamer para ganhos significativos de performance, e um sistema de filtros redesenhado. Para desenvolvedores que se preparam para entrevistas técnicas, compreender esses padrões arquiteturais distingue os perfis seniores dos juniores.

Requisitos do API Platform 4.2

API Platform 4.2 requer Symfony 7.4 ou 8.0. O suporte para Symfony 6.4 e 7.0-7.3 foi descontinuado. O JSON Streamer oferece até 32% mais requisições por segundo em endpoints de coleção.

Configurando API Platform 4.2 com Symfony

API Platform se instala através do Symfony Flex com configuração automática. A configuração padrão lida com a maioria dos casos de uso enquanto permanece totalmente personalizável para requisitos de domínio complexos.

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

A receita Flex configura grupos de serialização, integração com Doctrine e geração de documentação OpenAPI. Os recursos API expõem operações CRUD adicionando um único atributo às classes de entidade.

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...
}

Essa configuração gera cinco endpoints com validação automática, serialização e documentação OpenAPI.

State Providers: Obtendo Dados de Qualquer Fonte

Os State Providers controlam como o API Platform recupera dados para operações GET. O provider Doctrine padrão lida com a recuperação de entidades, mas providers personalizados permitem integração com APIs externas, Elasticsearch ou dados em cache.

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();
    }
}

O registro do provider em operações específicas é feito assim:

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 Processors: Lidando com Mutações e Lógica de Negócio

Os State Processors lidam com operações POST, PUT, PATCH e DELETE. Eles recebem dados deserializados e aplicam lógica de negócio antes da persistência.

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);
    }
}
Decoração de Processors

Decorar o processor Doctrine padrão com #[AsDecorator] preserva o comportamento de persistência enquanto adiciona lógica personalizada. Esse padrão evita duplicar operações ORM.

Object Mapper: Separando Recursos API de Entidades

API Platform 4.2 integra o componente Symfony Object Mapper para desacoplar as representações API das entidades de domínio. Essa separação permite diferentes modelos de leitura/escrita e protege as estruturas internas das entidades.

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;
}

O provider mapper transforma entidades em recursos automaticamente:

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;
    }
}

Pronto para mandar bem nas entrevistas de Symfony?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

JSON Streamer: Melhoria de Performance de 32%

O componente JSON Streamer serializa grandes coleções sem carregar conjuntos de dados inteiros em memória. Os benchmarks na API do Sylius mostraram um aumento de 32,4% em requisições por segundo.

Habilitação do streaming a nível de recurso ou operação:

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

O streaming beneficia particularmente:

  • Endpoints de coleção com mais de 50 itens
  • Recursos com relacionamentos aninhados
  • APIs servindo clientes móveis com largura de banda limitada

A especificação OpenAPI também foi otimizada. A mutualização de JSON Schema reduz o tamanho do arquivo em 30%, melhorando os tempos de carregamento da documentação.

Perguntas de Entrevista: Arquitetura API Platform

Entrevistas técnicas para posições Symfony frequentemente cobrem padrões do API Platform. Essas perguntas avaliam a compreensão da arquitetura do framework além das operações CRUD básicas.

"Explique a diferença entre State Providers e Processors"

Resposta esperada: State Providers obtêm dados para operações de leitura (GET). Eles retornam entidades, DTOs ou arrays. State Processors lidam com operações de escrita (POST, PUT, PATCH, DELETE). Eles recebem entrada deserializada e executam lógica de negócio antes da persistência. A separação segue princípios CQRS: consultas através de Providers, comandos através de Processors.

"Quando utilizar um recurso API personalizado em vez de expor uma entidade diretamente?"

Resposta esperada: Recursos personalizados se aplicam quando:

  • A representação API difere do esquema do banco de dados
  • Campos calculados requerem agregação de múltiplas entidades
  • Os modelos de escrita e leitura precisam de estruturas diferentes
  • Campos internos das entidades devem permanecer ocultos dos consumidores da API
  • A compatibilidade de versão requer contratos estáveis enquanto as entidades evoluem

"Como o API Platform lida com validação?"

Resposta esperada: API Platform usa constraints do Symfony Validator nas propriedades das entidades. A validação é executada automaticamente durante a deserialização antes do State Processor executar. Grupos de validação controlam quais constraints se aplicam por operação. Validadores personalizados se integram através de mecanismos padrão do 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
}
Erro Comum em Entrevistas

Candidatos frequentemente descrevem a validação como "automática" sem mencionar grupos de validação ou constraints personalizadas. Entrevistadores procuram compreensão de como personalizar a validação por operação.

"Quais mecanismos de segurança o API Platform fornece?"

Resposta esperada: API Platform se integra com Symfony Security através de:

  • Atributo security em operações para controle de acesso baseado em roles
  • securityPostDenormalize para verificações a nível de objeto após o binding de dados
  • Voters para lógica de autorização complexa
  • Limitação de taxa através de integração com 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)"
        )
    ]
)]

Filtros e Paginação: Padrões de Consulta Avançados

Os filtros do API Platform permitem que clientes consultem coleções com parâmetros URL. O sistema de filtros na versão 4.2 foi redesenhado para melhor extensibilidade.

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 { /* ... */ }

Endpoints gerados:

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

Testes de Recursos API Platform

API Platform fornece um cliente de testes que simplifica os testes funcionais. A classe ApiTestCase oferece asserções específicas para respostas de 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.']
            ]
        ]);
    }
}

Pontos-Chave para API Platform com Symfony

  • State Providers lidam com operações GET, State Processors lidam com mutações. Essa separação permite arquitetura limpa com fontes de dados personalizadas e lógica de negócio
  • O componente Object Mapper desacopla recursos API de entidades Doctrine, permitindo diferentes modelos de leitura/escrita e protegendo estruturas internas
  • O JSON Streamer oferece melhoria de performance de 32% em endpoints de coleção ao serializar sem alocação completa de memória
  • A segurança se integra através de mecanismos padrão do Symfony: expressões security, Voters e componente Rate Limiter
  • Os grupos de validação personalizam a aplicação de constraints por operação
  • Filtros expõem parâmetros de consulta automaticamente, com filtros de busca, data e ordenação cobrindo a maioria dos casos de uso
  • Perguntas de entrevista focam em decisões arquiteturais: quando usar providers personalizados, como separar modelos de leitura/escrita, e padrões de implementação de segurança

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em Symfony?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 8 de setembro de 2026

Tags

#api-platform
#symfony
#rest-api
#state-providers
#interview

Compartilhar

Artigos relacionados