API Platform Symfony REST: Tutorial Completo y Preguntas de Entrevista 2026

Domina API Platform con Symfony para crear APIs REST de alto rendimiento. Guía completa con instalación, configuración, buenas prácticas y preguntas de entrevista técnica.

API Platform Symfony REST: Tutorial Completo y Preguntas de Entrevista 2026

API Platform se ha consolidado como el framework de referencia para desarrollar APIs REST profesionales con Symfony. Esta solución ofrece productividad excepcional mientras respeta los estándares web modernos. Este tutorial cubre la instalación, configuración avanzada y conceptos esenciales para destacar en entrevistas técnicas.

API Platform genera automáticamente documentación OpenAPI, soporte JSON-LD/Hydra y GraphQL. Estas funcionalidades integradas aceleran considerablemente el desarrollo de APIs conformes a los estándares.

Instalación de API Platform con Symfony

La instalación de API Platform en un proyecto Symfony existente se realiza mediante Composer. El bundle se integra perfectamente con el ecosistema Symfony y Doctrine.

bash
composer require api-platform/core

Para un nuevo proyecto, API Platform ofrece una distribución completa que incluye Symfony, Docker y herramientas de desarrollo preconfiguradas:

bash
composer create-project api-platform/api-platform my-api
cd my-api
docker compose up -d

La configuración mínima se realiza en el archivo config/packages/api_platform.yaml:

yaml
api_platform:
    title: 'Mi API REST'
    version: '1.0.0'
    formats:
        jsonld: ['application/ld+json']
        json: ['application/json']
    docs_formats:
        jsonld: ['application/ld+json']
        jsonopenapi: ['application/vnd.openapi+json']
        html: ['text/html']
    defaults:
        pagination_enabled: true
        pagination_items_per_page: 30

Creación de un Recurso API

La transformación de una entidad Doctrine en recurso API se realiza con el atributo #[ApiResource]. API Platform genera automáticamente las operaciones CRUD estándar.

php
<?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 Post(),
        new Get(),
        new Put(),
        new Delete()
    ],
    paginationItemsPerPage: 20
)]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Assert\Length(min: 3, max: 255)]
    private string $name;

    #[ORM\Column(type: 'text')]
    private string $description;

    #[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
    #[Assert\Positive]
    private string $price;

    #[ORM\Column]
    private \DateTimeImmutable $createdAt;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
    }

    // Getters y setters...
}

Grupos de Serialización y DTO

El control preciso de los datos expuestos se realiza mediante grupos de serialización. Este enfoque permite diferenciar los campos visibles según el contexto.

php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use Symfony\Component\Serializer\Annotation\Groups;

#[ApiResource(
    operations: [
        new GetCollection(normalizationContext: ['groups' => ['product:list']]),
        new Get(normalizationContext: ['groups' => ['product:read']]),
        new Post(denormalizationContext: ['groups' => ['product:write']])
    ]
)]
class Product
{
    #[Groups(['product:list', 'product:read'])]
    private ?int $id = null;

    #[Groups(['product:list', 'product:read', 'product:write'])]
    private string $name;

    #[Groups(['product:read', 'product:write'])]
    private string $description;

    #[Groups(['product:list', 'product:read', 'product:write'])]
    private string $price;

    #[Groups(['product:read'])]
    private \DateTimeImmutable $createdAt;
}

Para casos complejos, los DTO (Data Transfer Objects) ofrecen una separación clara entre la representación API y el modelo de negocio:

php
<?php

namespace App\Dto;

use Symfony\Component\Validator\Constraints as Assert;

final class CreateProductInput
{
    #[Assert\NotBlank]
    #[Assert\Length(min: 3, max: 255)]
    public string $name;

    #[Assert\NotBlank]
    public string $description;

    #[Assert\Positive]
    public float $price;

    #[Assert\NotBlank]
    public string $categoryId;
}

Filtros y Búsqueda Avanzada

API Platform integra un sistema de filtros potente para las operaciones de colección. Los filtros se aplican directamente sobre las entidades.

php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;

#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: [
    'name' => 'partial',
    'category.name' => 'exact'
])]
#[ApiFilter(RangeFilter::class, properties: ['price'])]
#[ApiFilter(OrderFilter::class, properties: ['name', 'price', 'createdAt'])]
#[ApiFilter(DateFilter::class, properties: ['createdAt'])]
class Product
{
    // Propiedades...
}

Las solicitudes HTTP utilizan estos filtros mediante parámetros de query string:

bash
# Búsqueda parcial por nombre
GET /api/products?name=smartphone

# Filtrado por rango de precios
GET /api/products?price[gte]=100&price[lte]=500

# Ordenamiento por precio descendente
GET /api/products?order[price]=desc

# Combinación de filtros
GET /api/products?name=phone&price[gte]=200&order[createdAt]=desc

State Providers y State Processors

Los State Providers permiten personalizar la recuperación de datos, mientras que los State Processors controlan las operaciones de escritura.

php
<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Repository\ProductRepository;

final class ProductStateProvider implements ProviderInterface
{
    public function __construct(
        private ProductRepository $repository
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        if (isset($uriVariables['id'])) {
            return $this->repository->findActiveById($uriVariables['id']);
        }

        return $this->repository->findAllActive();
    }
}

El State Processor gestiona la lógica de negocio durante las creaciones y modificaciones:

php
<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Product;
use App\Service\ProductService;
use App\Service\NotificationService;

final class ProductStateProcessor implements ProcessorInterface
{
    public function __construct(
        private ProcessorInterface $persistProcessor,
        private ProductService $productService,
        private NotificationService $notificationService
    ) {}

    public function process(
        mixed $data,
        Operation $operation,
        array $uriVariables = [],
        array $context = []
    ): Product {
        $this->productService->calculateFinalPrice($data);
        
        $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
        
        if ($operation instanceof Post) {
            $this->notificationService->notifyNewProduct($result);
        }
        
        return $result;
    }
}

Seguridad y Control de Acceso

API Platform se integra con el componente Security de Symfony para gestionar autenticación y autorizaciones.

php
<?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;

#[ApiResource(
    operations: [
        new GetCollection(),
        new Get(),
        new Post(security: "is_granted('ROLE_ADMIN')"),
        new Put(security: "is_granted('ROLE_ADMIN') or object.owner == user"),
        new Delete(security: "is_granted('ROLE_ADMIN')")
    ]
)]
class Product
{
    // ...
}

Para seguridad a nivel de campos, los grupos de serialización combinados con voters ofrecen control granular:

php
<?php

namespace App\Serializer;

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class ProductNormalizer implements NormalizerInterface
{
    public function __construct(
        private NormalizerInterface $normalizer,
        private AuthorizationCheckerInterface $authChecker
    ) {}

    public function normalize($object, ?string $format = null, array $context = []): array
    {
        $data = $this->normalizer->normalize($object, $format, $context);
        
        if (!$this->authChecker->isGranted('ROLE_ADMIN')) {
            unset($data['costPrice'], $data['margin']);
        }
        
        return $data;
    }

    public function supportsNormalization($data, ?string $format = null, array $context = []): bool
    {
        return $data instanceof Product;
    }
}

¿Listo para aprobar tus entrevistas de Symfony?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Preguntas de Entrevista sobre API Platform

Las entrevistas técnicas sobre API Platform evalúan la comprensión de conceptos fundamentales y buenas prácticas.

Pregunta: ¿Cuál es la diferencia entre State Provider y State Processor?

El State Provider recupera datos (operaciones GET), mientras que el State Processor los modifica (POST, PUT, PATCH, DELETE). Esta separación respeta el principio de responsabilidad única.

Pregunta: ¿Cómo implementar paginación personalizada?

php
#[ApiResource(
    paginationEnabled: true,
    paginationItemsPerPage: 20,
    paginationMaximumItemsPerPage: 100,
    paginationClientEnabled: true,
    paginationClientItemsPerPage: true
)]

Pregunta: ¿Cómo manejar relaciones en API Platform?

Las relaciones utilizan IRI (Internationalized Resource Identifiers). Para incluir datos relacionados, se configuran grupos de serialización apropiados o se utiliza la opción fetchEager.

Pregunta: ¿Qué estrategia usar para versionado de API?

API Platform soporta varios enfoques: versionado por URL (/api/v1/products), por header (Accept: application/vnd.api+json;version=1) o por query parameter. La configuración se realiza mediante operaciones personalizadas.

Pregunta: ¿Cómo optimizar el rendimiento con API Platform?

Las optimizaciones incluyen: eager loading de relaciones, uso del caché HTTP integrado, paginación apropiada y grupos de serialización específicos para reducir el tamaño de las respuestas.

Pruebas Funcionales de API

Las pruebas de API utilizan el cliente HTTP de Symfony combinado con aserciones específicas de API Platform.

php
<?php

namespace App\Tests\Api;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Product;
use App\Factory\ProductFactory;

final class ProductApiTest extends ApiTestCase
{
    public function testGetCollection(): void
    {
        ProductFactory::createMany(30);

        $response = static::createClient()->request('GET', '/api/products');

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains([
            '@context' => '/api/contexts/Product',
            '@type' => 'Collection',
            'totalItems' => 30
        ]);
        $this->assertCount(20, $response->toArray()['member']);
    }

    public function testCreateProduct(): void
    {
        $response = static::createClient()->request('POST', '/api/products', [
            'json' => [
                'name' => 'Nuevo Producto',
                'description' => 'Descripción del producto',
                'price' => '99.99'
            ],
            'headers' => ['Content-Type' => 'application/ld+json']
        ]);

        $this->assertResponseStatusCodeSame(201);
        $this->assertJsonContains([
            '@type' => 'Product',
            'name' => 'Nuevo Producto'
        ]);
    }

    public function testFilterByPrice(): void
    {
        ProductFactory::createOne(['price' => '50.00']);
        ProductFactory::createOne(['price' => '150.00']);
        ProductFactory::createOne(['price' => '250.00']);

        $response = static::createClient()->request(
            'GET',
            '/api/products?price[gte]=100&price[lte]=200'
        );

        $this->assertResponseIsSuccessful();
        $this->assertCount(1, $response->toArray()['member']);
    }
}

Conclusión

API Platform representa una solución madura y de alto rendimiento para desarrollar APIs REST con Symfony. El dominio de los conceptos de recursos, filtros, state providers/processors y seguridad permite construir APIs robustas y mantenibles. Las preguntas de entrevista generalmente se centran en estos fundamentos junto con las buenas prácticas de diseño de APIs RESTful.

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 25 de agosto de 2026

Compartir

Artículos relacionados