API Platform met Symfony in 2026: Architectuur en Sollicitatievragen voor Ontwikkelaars

Uitgebreide gids over API Platform met Symfony in 2026. Leer REST API-architectuur, State Providers, Processors en veelgestelde sollicitatievragen voor Symfony-ontwikkelaars.

API Platform met Symfony in 2026: Architectuur en Sollicitatievragen voor Ontwikkelaars

API Platform heeft zich gevestigd als het toonaangevende framework voor het ontwikkelen van REST- en GraphQL-API's met Symfony. In 2026 biedt de huidige versie krachtige functionaliteiten zoals State Providers, State Processors en uitgebreide OpenAPI-documentatie. Dit artikel behandelt de moderne architectuur van API Platform en bereidt ontwikkelaars voor op technische sollicitatiegesprekken.

API Platform 4.x vereist Symfony 7.x en PHP 8.3+. De voorbeelden in dit artikel maken gebruik van de nieuwste functies en best practices voor productierijpe API's.

Installatie en Projectconfiguratie

Het opzetten van een nieuw API Platform-project gebeurt via Composer. Het framework integreert naadloos met bestaande Symfony-applicaties.

bash
composer create-project api-platform/api-platform my-api
cd my-api
composer require api-platform/core

De basisconfiguratie in config/packages/api_platform.yaml definieert belangrijke parameters:

yaml
api_platform:
    title: "My API"
    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:
        stateless: true
        cache_headers:
            vary: ["Content-Type", "Authorization", "Origin"]
        extra_properties:
            standard_put: true
            rfc_7807_compliant_errors: true

Entiteiten Definiëren als API-Resources

API Platform gebruikt PHP-attributen voor het configureren van API-resources. Een typische entiteit combineert Doctrine-mapping met API-definities.

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 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')")
    ],
    paginationItemsPerPage: 20,
    order: ["createdAt" => "DESC"]
)]
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: "decimal", precision: 10, scale: 2)]
    #[Assert\Positive]
    private string $price;

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

    #[ORM\ManyToOne(targetEntity: User::class)]
    private ?User $owner = null;

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

    // Getters and setters...
}

State Providers voor Aangepaste Datalogica

State Providers vervangen de eerdere Data Providers en bieden meer flexibiliteit bij het ophalen van gegevens. Ze maken integratie van externe databronnen of complexe bedrijfslogica mogelijk.

php
<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\RequestStack;

class ProductStateProvider implements ProviderInterface
{
    public function __construct(
        private ProductRepository $repository,
        private RequestStack $requestStack
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        $request = $this->requestStack->getCurrentRequest();
        
        if ($operation instanceof GetCollection) {
            $category = $request?->query->get('category');
            
            if ($category) {
                return $this->repository->findByCategory($category);
            }
            
            return $this->repository->findAllActive();
        }

        return $this->repository->find($uriVariables['id']);
    }
}

De registratie gebeurt via het provider-attribuut:

php
#[ApiResource(
    provider: ProductStateProvider::class
)]
class Product
{
    // ...
}

State Processors voor Schrijfoperaties

State Processors verwerken POST-, PUT-, PATCH- en DELETE-requests. Ze encapsuleren de bedrijfslogica voor datawijzigingen.

php
<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class ProductStateProcessor implements ProcessorInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private MailerInterface $mailer
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Product
    {
        if ($data instanceof Product && $operation instanceof Post) {
            $data->setCreatedAt(new \DateTimeImmutable());
            
            $this->entityManager->persist($data);
            $this->entityManager->flush();
            
            $this->sendNotification($data);
            
            return $data;
        }

        $this->entityManager->flush();
        
        return $data;
    }

    private function sendNotification(Product $product): void
    {
        $email = (new Email())
            ->to('admin@example.com')
            ->subject('New Product Created')
            ->text(sprintf('Product %s was created.', $product->getName()));
            
        $this->mailer->send($email);
    }
}

DTO's en Input/Output-Transformaties

Data Transfer Objects maken scheiding mogelijk tussen API-representatie en interne entiteiten. Deze architectuur verhoogt flexibiliteit en veiligheid.

php
<?php

namespace App\Dto;

use Symfony\Component\Validator\Constraints as Assert;

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

    #[Assert\NotBlank]
    #[Assert\Positive]
    public float $price;

    #[Assert\NotBlank]
    public string $category;

    public ?string $description = null;
}
php
<?php

namespace App\Dto;

class ProductOutput
{
    public int $id;
    public string $name;
    public float $price;
    public string $formattedPrice;
    public string $category;
    public string $createdAt;
}

De configuratie van de transformatie gebeurt in de resource-definitie:

php
#[ApiResource(
    operations: [
        new Post(
            input: CreateProductInput::class,
            output: ProductOutput::class,
            processor: CreateProductProcessor::class
        )
    ]
)]
class Product
{
    // ...
}

Filteren en Sorteren

API Platform biedt declaratieve filters voor veelvoorkomende query-patronen. De configuratie gebeurt direct op de entiteit.

php
<?php

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

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

Een voorbeeld van een API-request met filters:

bash
GET /api/products?name=laptop&price[gte]=500&order[price]=asc

Authenticatie en Autorisatie

De beveiligingsconfiguratie maakt gebruik van Symfony's Security-component. API Platform integreert Voters en expressions naadloos.

php
<?php

namespace App\Security\Voter;

use App\Entity\Product;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class ProductVoter extends Voter
{
    public const EDIT = 'PRODUCT_EDIT';
    public const DELETE = 'PRODUCT_DELETE';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [self::EDIT, self::DELETE])
            && $subject instanceof Product;
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();

        if (!$user instanceof UserInterface) {
            return false;
        }

        /** @var Product $product */
        $product = $subject;

        return match($attribute) {
            self::EDIT => $this->canEdit($product, $user),
            self::DELETE => $this->canDelete($product, $user),
            default => false,
        };
    }

    private function canEdit(Product $product, UserInterface $user): bool
    {
        return $product->getOwner() === $user || in_array('ROLE_ADMIN', $user->getRoles());
    }

    private function canDelete(Product $product, UserInterface $user): bool
    {
        return in_array('ROLE_ADMIN', $user->getRoles());
    }
}

API-Versionering

API Platform ondersteunt verschillende versioneringsstrategieën. URL-gebaseerde versionering is het meest gangbaar.

php
#[ApiResource(
    routePrefix: '/v1',
    operations: [
        new GetCollection(),
        new Get()
    ]
)]
class Product
{
    // ...
}

#[ApiResource(
    routePrefix: '/v2',
    operations: [
        new GetCollection(),
        new Get()
    ],
    normalizationContext: ['groups' => ['product:read:v2']]
)]
class ProductV2
{
    // ...
}

Veelgestelde Sollicitatievragen

Vraag: Wat is het verschil tussen State Provider en State Processor?

State Providers zijn verantwoordelijk voor leesoperaties (GET-requests) en leveren data uit willekeurige bronnen. State Processors verwerken schrijfoperaties (POST, PUT, PATCH, DELETE) en implementeren persistentielogica evenals neveneffecten zoals notificaties.

Vraag: Hoe implementeer je paginering in API Platform?

API Platform biedt automatische paginering. De configuratie kan globaal of per resource:

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

Vraag: Welke serialisatiegroepen worden aanbevolen?

Best practices adviseren aparte groepen voor lezen en schrijven:

php
#[ApiResource(
    normalizationContext: ['groups' => ['product:read']],
    denormalizationContext: ['groups' => ['product:write']]
)]

Vraag: Hoe test je API Platform-endpoints?

php
<?php

namespace App\Tests\Api;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Product;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;

class ProductTest extends ApiTestCase
{
    use RefreshDatabaseTrait;

    public function testGetCollection(): void
    {
        $response = static::createClient()->request('GET', '/api/products');

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains(['@type' => 'hydra:Collection']);
    }

    public function testCreateProduct(): void
    {
        $response = static::createClient()->request('POST', '/api/products', [
            'json' => [
                'name' => 'Test Product',
                'price' => '29.99'
            ],
            'headers' => [
                'Authorization' => 'Bearer ' . $this->getToken()
            ]
        ]);

        $this->assertResponseStatusCodeSame(201);
        $this->assertJsonContains(['name' => 'Test Product']);
    }
}

Klaar om je Symfony gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Conclusie

API Platform in 2026 biedt een volwassen architectuur voor het ontwikkelen van REST-API's met Symfony. State Providers en Processors maken flexibele datatoegangspatronen mogelijk, terwijl DTO's een schone scheiding tussen API en domeinlogica garanderen. De integratie van filters, beveiliging en automatische documentatie maakt API Platform de eerste keuze voor professionele API-ontwikkeling. De behandelde concepten en sollicitatievragen bereiden ontwikkelaars optimaal voor op technische gesprekken en geven best practices mee voor productierijpe implementaties.

Dagelijkse challenge

Zie jij de bug in Symfony?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 8 september 2026

Delen

Gerelateerde artikelen