# 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. - Published: 2026-09-08 - Updated: 2026-09-08 - Author: Anthony Fillion-Maillet - Tags: api-platform, symfony, rest-api, state-providers, interview - Reading time: 9 min --- 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. ```php // src/Entity/Book.php 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. ```php // src/State/BookStateProvider.php 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](https://api-platform.com/docs/core/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. ```php // src/State/BookStateProcessor.php 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](https://symfony.com/doc/current/components/object_mapper.html) 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. ```php // src/ApiResource/BookResource.php 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: ```php // src/State/BookResourceProvider.php 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; } } ``` ## 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](https://soyuka.me/api-platform-4-2-redefining-api-development/). 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](https://api-platform.com/docs/core/filters/) 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: ``` 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. ```php // tests/Api/BookTest.php 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](/technologies/symfony/interview-questions/testing) 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/symfony/api-platform-symfony-architecture-interview-questions