# Symfony 7: API Platform e Boas Praticas > Guia completo para criar APIs REST profissionais com Symfony 7 e API Platform 4. State Providers, Processors, validacao e serializacao explicados com exemplos praticos. - Published: 2026-01-12 - Updated: 2026-04-07 - Author: SharpSkill - Tags: symfony, api platform, php, rest api, api development - Reading time: 14 min --- O API Platform 4 transforma radicalmente a criacao de APIs REST e GraphQL com Symfony 7. Esta nova versao traz uma filosofia repensada: separacao clara de responsabilidades, State Providers e Processors simplificados, e integracao nativa do Symfony Object Mapper. Construir uma API profissional nunca foi tao intuitivo. > **Novidades no API Platform 4.2** > > A versao 4.2 introduz o JSON Streamer com ganhos de desempenho de ate +32% de RPS, um sistema de filtros redesenhado e Mutators para personalizar operacoes sem alterar o nucleo. O suporte ao Symfony 7 e 8 e nativo. ## Instalacao e Configuracao Inicial O API Platform e instalado em poucos comandos com o Symfony Flex. A configuracao padrao cobre a maioria dos casos de uso e permanece totalmente personalizavel. ```bash # terminal # Create a new Symfony project with API Platform composer create-project symfony/skeleton my-api cd my-api # Install API Platform with Doctrine ORM composer require api # Verify installation php bin/console debug:router | grep api ``` O Symfony Flex configura automaticamente as rotas, a documentacao OpenAPI e a interface Swagger UI acessivel em `/api`. ```yaml # config/packages/api_platform.yaml api_platform: title: 'My API' version: '1.0.0' # Supported response formats formats: jsonld: ['application/ld+json'] json: ['application/json'] # OpenAPI documentation swagger: versions: [3] # Default pagination defaults: pagination_items_per_page: 30 pagination_maximum_items_per_page: 100 ``` Essa configuracao define os formatos de serializacao, a paginacao global e os metadados da documentacao da API. ## Criacao de um Recurso API Simples O atributo `#[ApiResource]` expoe uma entidade Doctrine como recurso REST. O API Platform gera automaticamente os endpoints CRUD, a documentacao OpenAPI e as validacoes basicas. ```php 'DESC'], // Pagination configuration for this resource paginationItemsPerPage: 20 )] class Book { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 255)] #[Assert\NotBlank(message: 'Title is required')] #[Assert\Length(min: 2, max: 255)] private ?string $title = null; #[ORM\Column(type: 'text')] #[Assert\NotBlank] private ?string $description = null; #[ORM\Column(length: 13, unique: true)] #[Assert\Isbn] private ?string $isbn = null; #[ORM\Column] private ?\DateTimeImmutable $publishedAt = null; // Getters and setters... public function getId(): ?int { return $this->id; } public function getTitle(): ?string { return $this->title; } public function setTitle(string $title): static { $this->title = $title; return $this; } public function getDescription(): ?string { return $this->description; } public function setDescription(string $description): static { $this->description = $description; return $this; } public function getIsbn(): ?string { return $this->isbn; } public function setIsbn(string $isbn): static { $this->isbn = $isbn; return $this; } public function getPublishedAt(): ?\DateTimeImmutable { return $this->publishedAt; } public function setPublishedAt(\DateTimeImmutable $publishedAt): static { $this->publishedAt = $publishedAt; return $this; } } ``` Essa entidade gera seis endpoints: `GET /api/books`, `POST /api/books`, `GET /api/books/{id}`, `PUT /api/books/{id}`, `PATCH /api/books/{id}` e `DELETE /api/books/{id}`. > **UUID v7 Recomendado** > > O API Platform suporta nativamente o UUID v7 como identificador. Essa abordagem melhora a seguranca (identificadores nao previssiveis) e o desempenho (ordenacao natural por data de criacao). ## Grupos de Serializacao para Controlar Dados Expostos Os grupos de serializacao definem com precisao quais propriedades sao expostas para leitura (normalizacao) e escrita (desnormalizacao). Essa separacao e essencial para a seguranca e flexibilidade da API. ```php ['Default', 'user:create']]), new Get(), new Put(processor: UserPasswordHasher::class), new Patch(processor: UserPasswordHasher::class), new Delete(), ], // Properties exposed when reading normalizationContext: ['groups' => ['user:read']], // Properties accepted when writing denormalizationContext: ['groups' => ['user:create', 'user:update']], )] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['user:read'])] private ?int $id = null; #[ORM\Column(length: 180, unique: true)] #[Assert\NotBlank] #[Assert\Email] #[Groups(['user:read', 'user:create', 'user:update'])] private ?string $email = null; #[ORM\Column] private ?string $password = null; // Never exposed when reading, only when writing #[Assert\NotBlank(groups: ['user:create'])] #[Groups(['user:create', 'user:update'])] private ?string $plainPassword = null; #[ORM\Column(length: 100)] #[Groups(['user:read', 'user:create', 'user:update'])] private ?string $fullName = null; #[ORM\Column(type: 'json')] #[Groups(['user:read'])] private array $roles = []; #[ORM\Column] #[Groups(['user:read'])] private ?\DateTimeImmutable $createdAt = null; public function __construct() { $this->createdAt = new \DateTimeImmutable(); } // UserInterface implementation public function getUserIdentifier(): string { return (string) $this->email; } public function getRoles(): array { $roles = $this->roles; $roles[] = 'ROLE_USER'; return array_unique($roles); } public function getPassword(): string { return $this->password; } public function eraseCredentials(): void { $this->plainPassword = null; } // Getters and setters... public function getId(): ?int { return $this->id; } public function getEmail(): ?string { return $this->email; } public function setEmail(string $email): static { $this->email = $email; return $this; } public function setPassword(string $password): static { $this->password = $password; return $this; } public function getPlainPassword(): ?string { return $this->plainPassword; } public function setPlainPassword(?string $plainPassword): static { $this->plainPassword = $plainPassword; return $this; } public function getFullName(): ?string { return $this->fullName; } public function setFullName(string $fullName): static { $this->fullName = $fullName; return $this; } public function setRoles(array $roles): static { $this->roles = $roles; return $this; } public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; } } ``` Com essa configuracao, `plainPassword` nunca e exposto nas respostas, mas pode ser enviado durante a criacao ou atualizacoes. ## State Processors para Logica de Negocio Os State Processors interceptam as operacoes de persistencia para adicionar logica de negocio. O API Platform 4 simplifica sua criacao por meio de injecao de dependencia baseada em atributos. ```php */ final class UserPasswordHasher implements ProcessorInterface { public function __construct( // Injection of standard Doctrine processor #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')] private ProcessorInterface $persistProcessor, private UserPasswordHasherInterface $passwordHasher, ) { } public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): User { // Hash password if provided if ($data->getPlainPassword()) { $hashedPassword = $this->passwordHasher->hashPassword( $data, $data->getPlainPassword() ); $data->setPassword($hashedPassword); $data->eraseCredentials(); } // Delegate persistence to standard processor return $this->persistProcessor->process($data, $operation, $uriVariables, $context); } } ``` Esse padrao de composicao permite adicionar qualquer logica (envio de e-mails, eventos, logs) preservando o comportamento padrao de persistencia. ### Processor com Logica Condicional Um processor pode adaptar seu comportamento com base no tipo de operacao. ```php */ final class BookProcessor implements ProcessorInterface { public function __construct( #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')] private ProcessorInterface $persistProcessor, #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')] private ProcessorInterface $removeProcessor, private NotificationService $notifications, private SearchIndexer $searchIndexer, ) { } public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed { // Deletion: use remove processor if ($operation instanceof DeleteOperationInterface) { $this->searchIndexer->remove($data); return $this->removeProcessor->process($data, $operation, $uriVariables, $context); } // Creation: set publication date if ($operation instanceof Post) { $data->setPublishedAt(new \DateTimeImmutable()); } // Standard persistence $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context); // Post-processing: indexing and notification $this->searchIndexer->index($result); if ($operation instanceof Post) { $this->notifications->notifyNewBook($result); } return $result; } } ``` ## State Providers para Fontes de Dados Personalizadas Os State Providers buscam dados de qualquer fonte: APIs externas, cache, arquivos ou logica de negocio complexa. ```php */ final class PopularBooksProvider implements ProviderInterface { public function __construct( private BookRepository $bookRepository, private CacheInterface $cache, ) { } public function provide(Operation $operation, array $uriVariables = [], array $context = []): array { // 5-minute cache for popular books return $this->cache->get('popular_books', function (ItemInterface $item) { $item->expiresAfter(300); return $this->bookRepository->findPopular(limit: 10); }); } } ``` Esse provider e utilizado em uma operacao dedicada. ```php ['Default', 'article:create']]), // More lenient validation for updates new Put(validationContext: ['groups' => ['Default', 'article:update']]), ], )] class Article { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 255)] #[Assert\NotBlank] #[Assert\Length(min: 10, max: 255)] private ?string $title = null; #[ORM\Column(type: 'text')] #[Assert\NotBlank] // Minimum 500 characters on creation #[Assert\Length(min: 500, groups: ['article:create'])] // Minimum 100 characters for updates #[Assert\Length(min: 100, groups: ['article:update'])] private ?string $content = null; #[ORM\Column(length: 50)] #[Assert\NotBlank(groups: ['article:create'])] #[Assert\Choice(choices: ['draft', 'published', 'archived'])] private ?string $status = 'draft'; #[ORM\Column(nullable: true)] // Required only if status is "published" #[Assert\NotBlank(groups: ['article:publish'])] private ?\DateTimeImmutable $publishedAt = null; // Getters and setters... } ``` ### Validacao Dinamica com um Servico Para regras de validacao complexas, um gerador de grupos personalizado oferece flexibilidade total. ```php security->isGranted('ROLE_ADMIN')) { $groups[] = 'admin'; return $groups; } // Additional validation if publishing if ($object->getStatus() === 'published') { $groups[] = 'article:publish'; } return $groups; } } ``` > **Desempenho de Validacao** > > Validacoes complexas podem impactar o desempenho. Para importacoes em massa, considere desabilitar temporariamente certas validacoes ou utilizar constraints assincronas. ## Filtros para Consultas Flexiveis O API Platform 4.2 repensa completamente o sistema de filtros com separacao clara de responsabilidades. Os filtros permitem que os clientes da API pesquisem e ordenem dados. ```php 'partial', // LIKE %value% 'description' => 'partial', 'category.name' => 'exact', // Search on relation 'sku' => 'exact', // Exact match ])] // Range filtering #[ApiFilter(RangeFilter::class, properties: ['price', 'stock'])] // Date filtering #[ApiFilter(DateFilter::class, properties: ['createdAt', 'updatedAt'])] // Boolean filtering #[ApiFilter(BooleanFilter::class, properties: ['isActive', 'isFeatured'])] // Customizable sorting #[ApiFilter(OrderFilter::class, properties: [ 'name', 'price', 'createdAt', ], arguments: ['orderParameterName' => 'sort'])] class Product { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 255)] private ?string $name = null; #[ORM\Column(type: 'text', nullable: true)] private ?string $description = null; #[ORM\Column(length: 50, unique: true)] private ?string $sku = null; #[ORM\Column(type: 'decimal', precision: 10, scale: 2)] private ?string $price = null; #[ORM\Column] private ?int $stock = null; #[ORM\Column] private ?bool $isActive = true; #[ORM\Column] private ?bool $isFeatured = false; #[ORM\ManyToOne(targetEntity: Category::class)] private ?Category $category = null; #[ORM\Column] private ?\DateTimeImmutable $createdAt = null; #[ORM\Column(nullable: true)] private ?\DateTimeImmutable $updatedAt = null; // Getters and setters... } ``` Esses filtros geram automaticamente documentacao OpenAPI e habilitam consultas como: ``` GET /api/products?name=phone&price[gte]=100&price[lte]=500&isActive=true&sort[price]=asc ``` ## Relacoes e Subrecursos O API Platform trata de forma elegante as relacoes entre entidades, com opcoes de serializacao e subrecursos. ```php ['author:read']], )] class Author { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['author:read', 'book:read'])] private ?int $id = null; #[ORM\Column(length: 255)] #[Groups(['author:read', 'book:read'])] private ?string $name = null; #[ORM\Column(type: 'text', nullable: true)] #[Groups(['author:read'])] private ?string $biography = null; #[ORM\OneToMany(mappedBy: 'author', targetEntity: Book::class)] #[Groups(['author:read'])] private Collection $books; public function __construct() { $this->books = new ArrayCollection(); } // Getters and setters... } ``` ```php ['book:read']], )] // Subresource: GET /api/authors/{authorId}/books #[ApiResource( uriTemplate: '/authors/{authorId}/books', operations: [new GetCollection()], uriVariables: [ 'authorId' => new Link( fromProperty: 'books', fromClass: Author::class ), ], normalizationContext: ['groups' => ['book:read']], )] class Book { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['book:read', 'author:read'])] private ?int $id = null; #[ORM\Column(length: 255)] #[Groups(['book:read', 'author:read'])] private ?string $title = null; #[ORM\ManyToOne(targetEntity: Author::class, inversedBy: 'books')] #[ORM\JoinColumn(nullable: false)] #[Groups(['book:read'])] private ?Author $author = null; // Getters and setters... } ``` ## Seguranca e Controle de Acesso O API Platform se integra perfeitamente ao sistema de seguranca do Symfony. Voters e expressoes de seguranca controlam o acesso aos recursos. ```php customer; } } ``` A expressao `object.getCustomer() == user` permite acessar a entidade atual e o usuario conectado para verificacoes granulares. ## Testes Automatizados de API O API Platform fornece traits do PHPUnit para testar endpoints de forma simples e eficaz. ```php request('GET', '/api/books'); // Assertions $this->assertResponseIsSuccessful(); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ '@context' => '/api/contexts/Book', '@type' => 'Collection', 'totalItems' => 30, ]); // Verify pagination (20 items per page) $this->assertCount(20, $response->toArray()['member']); } public function testCreateBook(): void { $user = UserFactory::createOne(['roles' => ['ROLE_ADMIN']]); static::createClient()->request('POST', '/api/books', [ 'auth_bearer' => $this->getToken($user), 'json' => [ 'title' => 'Clean Code', 'description' => 'A Handbook of Agile Software Craftsmanship', 'isbn' => '9780132350884', ], ]); $this->assertResponseStatusCodeSame(201); $this->assertJsonContains([ '@type' => 'Book', 'title' => 'Clean Code', ]); } public function testCreateBookValidationFails(): void { $user = UserFactory::createOne(['roles' => ['ROLE_ADMIN']]); static::createClient()->request('POST', '/api/books', [ 'auth_bearer' => $this->getToken($user), 'json' => [ 'title' => '', // Empty title = error 'isbn' => 'invalid-isbn', ], ]); $this->assertResponseStatusCodeSame(422); $this->assertJsonContains([ '@type' => 'ConstraintViolationList', 'violations' => [ ['propertyPath' => 'title', 'message' => 'Title is required'], ], ]); } private function getToken(object $user): string { // Implementation depends on your authentication system return 'test_token'; } } ``` ## Conclusao O API Platform 4 com Symfony 7 representa o estado da arte na criacao de APIs REST profissionais em PHP. A separacao clara entre State Providers (leitura) e State Processors (escrita), combinada com grupos de serializacao e o sistema de validacao, permite construir APIs robustas e de facil manutencao. ### Checklist para APIs de Qualidade - Utilize grupos de serializacao distintos para leitura e escrita - Implemente State Processors para logica de negocio (hash de senha, notificacoes) - Configure filtros para pesquisa e ordenacao - Aplique validacoes por operacao com grupos - Proteja os endpoints com expressoes de seguranca - Escreva testes funcionais para cada endpoint - Documente a API por meio de metadados OpenAPI A filosofia do API Platform 4 privilegia a composicao em detrimento da heranca e a configuracao em vez da convencao. O resultado: APIs escalaveis, testaveis e conformes aos padroes REST/JSON-LD, prontas para producao desde o primeiro dia. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/symfony/symfony-7-api-platform-best-practices