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.

Diagrama de arquitectura API Platform Symfony con flujo de trabajo REST API

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.

Requisitos de API Platform 4.2

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.

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

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

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

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

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

El registro del provider en operaciones específicas se realiza así:

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

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);
    }
}
Decoración de Processors

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.

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

El provider mapper transforma entidades a recursos automáticamente:

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

¿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:

php
#[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.

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
}
Error Común en Entrevistas

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 security en operaciones para control de acceso basado en roles
  • securityPostDenormalize para 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
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 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.

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 generados:

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

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

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.']
            ]
        ]);
    }
}

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.

Reto diario

¿Sabrías detectar el bug en Symfony?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador 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

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

Compartir

Artículos relacionados