API Platform con Symfony en 2026: Arquitectura, State Providers y Preguntas de Entrevista
Dominar API Platform 4.2 con Symfony: State Providers, Processors, Object Mapper, JSON Streamer para optimización de rendimiento, y preguntas de entrevista técnica para desarrolladores senior.

API Platform 4.2 transforma la manera en que las aplicaciones Symfony exponen APIs REST y GraphQL. Esta versión introduce el Symfony Object Mapper para una separación limpia de recursos, el JSON Streamer para ganancias significativas de rendimiento, y un sistema de filtros rediseñado. Para desarrolladores que se preparan para entrevistas técnicas, comprender estos patrones arquitectónicos distingue a los perfiles senior de los junior.
API Platform 4.2 requiere Symfony 7.4 o 8.0. El soporte para Symfony 6.4 y 7.0-7.3 ha sido descontinuado. El JSON Streamer ofrece hasta un 32% más de solicitudes por segundo en endpoints de colección.
Configuración de API Platform 4.2 con Symfony
API Platform se instala a través de Symfony Flex con configuración automática. La configuración predeterminada maneja la mayoría de los casos de uso mientras permanece completamente personalizable para requisitos de dominio complejos.
# 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 serveLa receta Flex configura grupos de serialización, integración con Doctrine y generación de documentación OpenAPI. Los recursos API exponen operaciones CRUD agregando un único atributo a las clases de entidad.
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...
}Esta configuración genera cinco endpoints con validación automática, serialización y documentación OpenAPI.
State Providers: Obtención de Datos desde Cualquier Fuente
Los State Providers controlan cómo API Platform recupera datos para operaciones GET. El provider Doctrine predeterminado maneja la recuperación de entidades, pero los providers personalizados permiten integración con APIs externas, Elasticsearch o datos en caché.
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();
}
}El registro del provider en operaciones específicas se realiza así:
#[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: Manejo de Mutaciones con Lógica de Negocio
Los State Processors manejan operaciones POST, PUT, PATCH y DELETE. Reciben datos deserializados y aplican lógica de negocio antes de la persistencia.
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);
}
}Decorar el processor Doctrine predeterminado con #[AsDecorator] preserva el comportamiento de persistencia mientras agrega lógica personalizada. Este patrón evita duplicar operaciones ORM.
Object Mapper: Separación de Recursos API de Entidades
API Platform 4.2 integra el componente Symfony Object Mapper para desacoplar las representaciones API de las entidades de dominio. Esta separación permite diferentes modelos de lectura/escritura y protege las estructuras internas de las entidades.
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;
}El provider mapper transforma entidades a recursos automáticamente:
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;
}
}¿Listo para aprobar tus entrevistas de Symfony?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
JSON Streamer: Mejora de Rendimiento del 32%
El componente JSON Streamer serializa grandes colecciones sin cargar conjuntos de datos completos en memoria. Los benchmarks en la API de Sylius mostraron un aumento del 32.4% en solicitudes por segundo.
Habilitación del streaming a nivel de recurso u operación:
#[ApiResource(
operations: [
new GetCollection(
jsonStream: true, // Enable JSON streaming
paginationItemsPerPage: 100
),
new Get()
]
)]
class Book { /* ... */ }El streaming beneficia particularmente a:
- Endpoints de colección con más de 50 elementos
- Recursos con relaciones anidadas
- APIs que sirven clientes móviles con ancho de banda limitado
La especificación OpenAPI también ha sido optimizada. La mutualización de JSON Schema reduce el tamaño del archivo en un 30%, mejorando los tiempos de carga de la documentación.
Preguntas de Entrevista: Arquitectura API Platform
Las entrevistas técnicas para posiciones Symfony frecuentemente cubren patrones de API Platform. Estas preguntas evalúan la comprensión de la arquitectura del framework más allá de las operaciones CRUD básicas.
"Explique la diferencia entre State Providers y Processors"
Respuesta esperada: Los State Providers obtienen datos para operaciones de lectura (GET). Devuelven entidades, DTOs o arrays. Los State Processors manejan operaciones de escritura (POST, PUT, PATCH, DELETE). Reciben entrada deserializada y ejecutan lógica de negocio antes de la persistencia. La separación sigue principios CQRS: consultas a través de Providers, comandos a través de Processors.
"¿Cuándo utilizaría un recurso API personalizado en lugar de exponer directamente una entidad?"
Respuesta esperada: Los recursos personalizados se aplican cuando:
- La representación API difiere del esquema de base de datos
- Los campos calculados requieren agregación de múltiples entidades
- Los modelos de escritura y lectura necesitan estructuras diferentes
- Los campos internos de entidades deben permanecer ocultos de los consumidores de la API
- La compatibilidad de versiones requiere contratos estables mientras las entidades evolucionan
"¿Cómo maneja API Platform la validación?"
Respuesta esperada: API Platform usa restricciones de Symfony Validator en las propiedades de las entidades. La validación se ejecuta automáticamente durante la deserialización antes de que el State Processor se ejecute. Los grupos de validación controlan qué restricciones se aplican por operación. Los validadores personalizados se integran a través de mecanismos estándar de Symfony.
#[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
}Los candidatos frecuentemente describen la validación como "automática" sin mencionar grupos de validación o restricciones personalizadas. Los entrevistadores buscan comprensión de cómo personalizar la validación por operación.
"¿Qué mecanismos de seguridad proporciona API Platform?"
Respuesta esperada: API Platform se integra con Symfony Security a través de:
- Atributo
securityen operaciones para control de acceso basado en roles securityPostDenormalizepara verificaciones a nivel de objeto después del binding de datos- Voters para lógica de autorización compleja
- Limitación de tasa a través de integración con Symfony Rate Limiter
#[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 y Paginación: Patrones de Consulta Avanzados
Los filtros de API Platform permiten a los clientes consultar colecciones con parámetros URL. El sistema de filtros en 4.2 ha sido rediseñado para mejor extensibilidad.
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 generados:
GET /api/books?title=symfony # Search by title
GET /api/books?createdAt[after]=2026-01-01 # Date range
GET /api/books?order[createdAt]=desc # SortingTesting de Recursos API Platform
API Platform proporciona un cliente de testing que simplifica las pruebas funcionales. La clase ApiTestCase ofrece aserciones específicas para respuestas de API.
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.']
]
]);
}
}Puntos Clave para API Platform con Symfony
- Los State Providers manejan operaciones GET, los State Processors manejan mutaciones. Esta separación permite arquitectura limpia con fuentes de datos personalizadas y lógica de negocio
- El componente Object Mapper desacopla recursos API de entidades Doctrine, permitiendo diferentes modelos de lectura/escritura y protegiendo estructuras internas
- El JSON Streamer ofrece mejora de rendimiento del 32% en endpoints de colección al serializar sin asignación completa de memoria
- La seguridad se integra a través de mecanismos estándar de Symfony: expresiones
security, Voters y componente Rate Limiter - Los grupos de validación personalizan la aplicación de restricciones por operación
- Los filtros exponen parámetros de consulta automáticamente, con filtros de búsqueda, fecha y orden cubriendo la mayoría de los casos de uso
- Las preguntas de entrevista se centran en decisiones arquitectónicas: cuándo usar providers personalizados, cómo separar modelos de lectura/escritura, y patrones de implementación de seguridad
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
¿Sabrías detectar el bug en Symfony?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador de SharpSkill
Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.
Actualizado el 8 de septiembre de 2026
Etiquetas
Compartir
Artículos relacionados

Preguntas de entrevista Symfony: Top 25 en 2026
Las 25 preguntas de entrevista Symfony más frecuentes. Arquitectura, Doctrine ORM, servicios, seguridad, formularios y tests con respuestas detalladas y ejemplos de código.

Seguridad en Symfony en 2026: Voters, Firewalls y Preguntas de Entrevista Técnica
Análisis profundo de la seguridad en Symfony que cubre voters, firewalls, access tokens y autenticación, con las preguntas de entrevista técnica más frecuentes para desarrolladores Symfony.

Doctrine ORM: Dominar las relaciones en Symfony
Guía completa de las relaciones Doctrine ORM en Symfony. OneToMany, ManyToMany, estrategias de carga y optimización del rendimiento con ejemplos prácticos.