Symfony Security in 2026: Voters, Firewalls en Technische Sollicitatievragen
Uitgebreide handleiding over Symfony Security: firewalls configureren, custom access token handlers, voters voor fijnmazige autorisatie, access decision strategies, debugging met Twig en veelgestelde technische interviewvragen.

Het Security-component van Symfony behoort tot de meest uitgebreide en tegelijkertijd meest ondervraagde onderdelen tijdens technische sollicitatiegesprekken voor PHP-backendposities in 2026. Authenticatie, autorisatie, firewalls en voters vormen een gelaagde architectuur waarvan de beheersing het verschil markeert tussen medior en senior profielen. Met Symfony 7.4 LTS heeft het beveiligingssysteem er krachtige debugging-mogelijkheden bij gekregen, terwijl de modulaire opzet die het framework kenmerkt volledig behouden blijft.
Dit artikel ontleedt de volledige werking van Symfony Security: van firewallconfiguratie en tokenverwerking tot het ontwerpen van voters, het coordineren van access decision strategies en het hardenen van de beveiligingslaag. Elk concept wordt onderbouwd met productieklare codevoorbeelden en gekoppeld aan veelgestelde technische interviewvragen.
Symfony 7.4 LTS, uitgebracht in november 2025 met ondersteuning tot november 2029, consolideert de belangrijkste verbeteringen aan het Security-component sinds versie 6.0: uniforme authenticators, het native IsGranted-attribuut, debugging van toegangsbeslissingen in de Profiler en Twig, en verbeterde voter-ondersteuning. Deze versie geldt als referentie voor productieprojecten en technische interviews in 2026.
Hoe Symfony Firewalls authenticatie aansturen
Het beveiligingssysteem van Symfony steunt op een architectuur van firewalls die worden geconfigureerd in het bestand security.yaml. Elke firewall definieert een beveiligingsperimeter voor een verzameling routes, met eigen authenticatieregels en toegangscontrole. De volgorde van declaratie is bepalend: Symfony doorloopt de firewalls sequentieel en past de eerste toe waarvan het patroon overeenkomt met de URL van de binnenkomende request.
# config/packages/security.yaml
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api
stateless: true
access_token:
token_handler: App\Security\ApiTokenHandler
main:
lazy: true
form_login:
login_path: app_login
check_path: app_login
enable_csrf: true
logout:
path: app_logout
remember_me:
secret: '%kernel.secret%'
login_throttling:
max_attempts: 5
interval: '15 minutes'Drie firewalls bestaan naast elkaar in deze configuratie. De dev-firewall schakelt beveiliging volledig uit voor de routes van de Symfony Profiler, de Web Debug Toolbar en statische assets, waardoor elke interferentie in de ontwikkelomgeving wordt voorkomen. De api-firewall beschermt API-endpoints met stateless token-authenticatie. De main-firewall regelt klassieke sessiegebaseerde authenticatie via een formulierlogin.
Het lazy: true-attribuut verdient bijzondere aandacht. Deze instelling zorgt ervoor dat Symfony de authenticatie pas uitvoert wanneer de applicatie daadwerkelijk toegang tot gebruikersgegevens nodig heeft. Op publieke pagina's waar geen autorisatiecontrole plaatsvindt, wordt de sessie niet onnodig geladen. Dit optimaliseert de performance van pagina's die voor iedereen toegankelijk zijn.
De login_throttling-configuratie vormt een eerste verdedigingsmechanisme tegen brute-force-aanvallen. Met maximaal vijf pogingen binnen vijftien minuten worden geautomatiseerde loginpogingen effectief geblokkeerd zonder legitieme gebruikers buitensporig te hinderen.
Stateless versus stateful firewalls en wanneer welke te gebruiken
Het onderscheid tussen stateless en stateful authenticatie is fundamenteel voor het ontwerpen van moderne webapplicaties en komt regelmatig terug in technische interviews.
In het stateful paradigma (de main-firewall) slaat Symfony het authenticatietoken op in de PHP-sessie. Bij elke volgende request wordt de beveiligingscontext opgebouwd vanuit de sessie, zonder opnieuw te authenticeren. Dit model is bijzonder geschikt voor traditionele webapplicaties met een browserinterface, waar gebruikers inloggen via een formulier en vervolgens meerdere pagina's bezoeken binnen dezelfde sessie.
In het stateless paradigma (de api-firewall) draagt elke request zijn eigen credentials mee, doorgaans in de vorm van een Bearer-token, API-sleutel of JWT. Er wordt geen sessie aangemaakt op de server. Dit model past op natuurlijke wijze bij gedistribueerde architecturen, microservices en mobiele clients, maar vereist dat elke request volledig zelfstandig is vanuit authenticatieperspectief.
De keuze tussen beide paradigma's is geen kwestie van technische voorkeur maar van architecturale vereisten. Een monolithische applicatie met webinterface heeft baat bij stateful authenticatie vanwege de eenvoud van sessiebeheer. Een API die wordt geconsumeerd door uiteenlopende clients vereist stateless authenticatie voor horizontale schaalbaarheid. Bij stateful systemen is gedeelde sessie-opslag (Redis, database) noodzakelijk bij meerdere applicatieservers, terwijl stateless services probleemloos over meerdere instanties kunnen worden verdeeld.
Een custom access token handler bouwen
Voor API-authenticatie biedt Symfony de AccessTokenHandlerInterface, geintroduceerd in Symfony 6.2 als vervanging van de oudere Guard Authenticators. Deze interface maakt het mogelijk om eigen tokenvalidatielogica te implementeren, ongeacht of het gaat om database-tokens, JWTs of externe authenticatieservices.
namespace App\Security;
use App\Repository\ApiTokenRepository;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class ApiTokenHandler implements AccessTokenHandlerInterface
{
public function __construct(
private readonly ApiTokenRepository $repository,
) {}
public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
{
$token = $this->repository->findOneByValue($accessToken);
if (!$token || !$token->isValid()) {
throw new BadCredentialsException('Invalid or expired token.');
}
return new UserBadge($token->getUser()->getUserIdentifier());
}
}De interface schrijft slechts een methode voor: getUserBadgeFrom. Deze methode ontvangt het ruwe token dat is geextraheerd uit de request (standaard via de Authorization: Bearer-header) en retourneert een UserBadge met de identifier van de bijbehorende gebruiker. Het #[\SensitiveParameter]-attribuut markeert het token als gevoelig, waardoor het niet verschijnt in stack traces en logbestanden. Dit is een beveiligingsmaatregel die in PHP 8.2 werd geintroduceerd en bij productiesystemen standaard dient te worden toegepast.
Het ontwerppatroon is bewust minimaal gehouden: de validatie van het token (bestaan, verloopdatum, intrekking) blijft in het repository of een gespecialiseerde service. De handler vertaalt uitsluitend een token naar een gebruikersidentiteit. Deze scheiding van verantwoordelijkheden vergemakkelijkt het schrijven van unittests en respecteert het single responsibility principle.
Voters: fijnmazige autorisatielogica
Waar firewalls de authenticatie afhandelen, vormen voters het centrale autorisatiemechanisme in Symfony. In tegenstelling tot statische rollen in access_control maken voters dynamische toegangsbeslissingen mogelijk op basis van de context: de huidige gebruiker, het doelobject en de gevraagde actie. Elke voter beantwoordt een precieze vraag: "Mag deze gebruiker deze actie uitvoeren op dit object?"
namespace App\Security\Voter;
use App\Entity\Post;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class PostVoter extends Voter
{
public const EDIT = 'POST_EDIT';
public const DELETE = 'POST_DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE])
&& $subject instanceof Post;
}
protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token,
?Vote $vote = null,
): bool {
$user = $token->getUser();
if (!$user instanceof User) {
$vote?->addReason('User is not authenticated.');
return false;
}
/** @var Post $post */
$post = $subject;
return match ($attribute) {
self::EDIT => $this->canEdit($post, $user, $vote),
self::DELETE => $this->canDelete($post, $user, $vote),
default => false,
};
}
private function canEdit(Post $post, User $user, ?Vote $vote): bool
{
if ($post->getAuthor() === $user) {
return true;
}
$vote?->addReason('Only the author can edit this post.');
return false;
}
private function canDelete(Post $post, User $user, ?Vote $vote): bool
{
if (in_array('ROLE_ADMIN', $user->getRoles())) {
return true;
}
if ($post->getAuthor() === $user && !$post->isPublished()) {
return true;
}
$vote?->addReason('Only admins or authors of unpublished posts can delete.');
return false;
}
}Verschillende elementen in deze voter verdienen nadere analyse. De supports()-methode filtert welke autorisatievragen relevant zijn voor deze voter: alleen de attributen POST_EDIT en POST_DELETE in combinatie met een Post-instantie activeren de stemlogica. De voteOnAttribute()-methode voert de daadwerkelijke beslissing uit via een match-expressie die delegeert naar gespecialiseerde privemethoden.
De optionele Vote-parameter, geintroduceerd in Symfony 7.1, maakt het mogelijk om tekstuele redenen aan elke beslissing te koppelen. Deze redenen verschijnen in de Symfony Profiler en kunnen in Twig-templates worden weergegeven voor debugging. In productieomgevingen voeden ze auditlogs.
De logica van canDelete() illustreert een veelvoorkomend patroon: de combinatie van rolverificatie en eigendomscontrole. Een administrator mag elk artikel verwijderen, maar een auteur mag uitsluitend eigen ongepubliceerde artikelen verwijderen. Dit type contextuele regel is onmogelijk uit te drukken met eenvoudige rollen in access_control.
Klaar om je Symfony gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Het IsGranted-attribuut in controllers
Symfony biedt het #[IsGranted]-attribuut om autorisatiecontroles declaratief te definieren op controller- of methodeniveau. Dit houdt de controllercode schoon en maakt beveiligingsregels direct zichtbaar bij de routedefinitie.
namespace App\Controller;
use App\Entity\Post;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class PostController extends AbstractController
{
#[Route('/posts/{id}/edit', name: 'post_edit')]
#[IsGranted('POST_EDIT', subject: 'post', message: 'You cannot edit this post.')]
public function edit(Post $post): Response
{
// The voter already validated access.
// Only the post author reaches this point.
return $this->render('post/edit.html.twig', ['post' => $post]);
}
#[Route('/admin/posts', name: 'admin_posts')]
#[IsGranted('ROLE_ADMIN')]
public function adminIndex(): Response
{
return $this->render('admin/posts.html.twig');
}
}De subject: 'post'-parameter koppelt het attribuut aan het controllerargument met dezelfde naam. Symfony resolvet de entiteit automatisch via de ParamConverter en geeft deze door aan de voter als onderwerp van de controle. De message-parameter past het foutbericht aan bij een 403-respons, wat nuttig is voor debugging en auditlogs.
Deze declaratieve stijl biedt een belangrijk voordeel op het gebied van leesbaarheid: de toegangsregels zijn zichtbaar op dezelfde plek als de methodesignatuur, zonder dat de body van de functie hoeft te worden geinspecteerd. Tijdens technische interviews toont het vermogen om het volledige pad te beschrijven -- van het attribuut via de voter naar de AccessDecisionManager -- een diepgaand begrip van het Security-component.
Access decision strategies en de coordinatie van voters
Wanneer meerdere voters een stem uitbrengen over dezelfde autorisatievraag, bepaalt de AccessDecisionManager aan de hand van een strategie hoe de stemmen worden gecombineerd tot een eindoordeel. Symfony biedt vier strategieen:
| Strategie | Verleent toegang wanneer | Geschikt voor |
|---|---|---|
| affirmative (standaard) | Minstens een voter verleent toegang | Algemeen gebruik, permissief |
| consensus | De meerderheid van voters verleent toegang | Comiteachtige beslissingen |
| unanimous | Alle voters verlenen toegang | Hoge-beveiligingsoperaties |
| priority | De eerste niet-onthoudende voter beslist | Volgordegevoelige evaluatie |
Voor applicaties met strenge beveiligingsvereisten kan de unanimous-strategie worden geconfigureerd:
# config/packages/security.yaml
security:
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: falseMet allow_if_all_abstain: false wordt toegang geweigerd wanneer geen enkele voter een expliciete stem uitbrengt. Dit voorkomt dat ontbrekende autorisatielogica onbedoeld toegang verleent. In de praktijk volstaat de standaard affirmative-strategie voor de meeste applicaties. De unanimous-strategie is noodzakelijk in contexten waar beveiliging de hoogste prioriteit heeft: financiele applicaties, medische gegevens of kritieke infrastructuur. Hierbij moeten alle voters -- bijvoorbeeld een IP-verificatievoter, een rolvoter en een eigendomsvoter -- unaniem akkoord gaan voordat toegang wordt verleend.
Voter-debugging met Twig-functies in Symfony 7.4
Symfony 7.4 verrijkt de debugging van het beveiligingssysteem door de redenen achter toegangsbeslissingen direct beschikbaar te maken in Twig-templates. De Vote-parameter die aan voters wordt meegegeven, komt hier volledig tot zijn recht: de redenen die via $vote->addReason() zijn gedeclareerd, verschijnen in de Profiler en kunnen conditioneel in templates worden weergegeven.
{# templates/post/show.html.twig #}
{% set decision = access_decision('POST_EDIT', post) %}
{% if decision.isGranted %}
<a href="{{ path('post_edit', {id: post.id}) }}">Edit</a>
{% endif %}
{# In dev: inspect why access was denied #}
{% if app.debug and not decision.isGranted %}
{% for vote in decision.votes %}
{# vote.reasons contains explanations from voters #}
{% endfor %}
{% endif %}De functie access_decision() retourneert een AccessDecision-object met informatie over alle uitgebrachte stemmen en de redenen die voters hebben toegevoegd. In ontwikkelomgevingen kan dit worden gebruikt om te achterhalen waarom een bepaalde actie werd geweigerd, wat het debuggingproces aanzienlijk versnelt.
Dit mechanisme adresseert een terugkerend probleem in Symfony-projecten: het identificeren van de oorzaak wanneer een gebruiker onverwacht geen toegang krijgt. Voor Symfony 7.4 vereiste dit handmatige inspectie van de Profiler of het tijdelijk toevoegen van logregels in voters.
Veelgestelde technische interviewvragen over Symfony Security
Beveiliging in Symfony vormt een vast onderdeel van technische sollicitatiegesprekken voor PHP-backendposities. De volgende vragen dekken de onderwerpen die interviewers regelmatig evalueren, van mediorniveau tot seniorposities.
Wat is het verschil tussen authenticatie en autorisatie?
Authenticatie beantwoordt de vraag "Wie bent u?" en verifieert de identiteit van een gebruiker via credentials zoals login en wachtwoord, API-tokens of certificaten. Autorisatie beantwoordt de vraag "Wat mag u doen?" en controleert de rechten van de geauthenticeerde gebruiker via rollen, voters en beveiligingsexpressies. In Symfony regelt de firewall de authenticatie, terwijl de AccessDecisionManager samen met de voters de autorisatie afhandelt.
Wanneer verdient een voter de voorkeur boven een rolcontrole?
Voters zijn de juiste keuze wanneer de toegangsbeslissing afhangt van de context: het doelobject (een Post, een Order), de staat van dat object (gepubliceerd, gearchiveerd) of de relatie tussen de gebruiker en het object (auteur, beheerder). Regels in access_control binnen security.yaml zijn beperkt tot statische controles op rollen en URL-patronen. Zodra bedrijfslogica een rol speelt in de toegangsbeslissing, is een voter het aangewezen mechanisme.
Hoe verbetert CacheableVoterInterface de performance?
De CacheableVoterInterface bevat een methode supportsAttribute() die Symfony in staat stelt om bij het bootstrappen van de applicatie te bepalen welke voters relevant zijn voor welke attributen. Hierdoor hoeft Symfony niet bij elke autorisatiecontrole alle geregistreerde voters te raadplegen, maar alleen die voters die het betreffende attribuut ondersteunen. Voor applicaties met tientallen voters levert dit een meetbare performanceverbetering op, vooral bij pagina's met meerdere isGranted()-aanroepen.
Hoe worden voters geisoleerd getest?
Voters zijn zuivere PHP-klassen zonder framework-afhankelijkheden (mits geen services worden geinjecteerd), waardoor ze met standaard PHPUnit-tests kunnen worden gevalideerd. Elke test instantieert de voter, bouwt een UsernamePasswordToken op die een gebruiker met specifieke rollen simuleert, creert het subject in de gewenste staat en verifieert dat vote() de verwachte waarde retourneert.
namespace App\Tests\Security\Voter;
use App\Entity\Post;
use App\Entity\User;
use App\Security\Voter\PostVoter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
class PostVoterTest extends TestCase
{
private PostVoter $voter;
protected function setUp(): void
{
$this->voter = new PostVoter();
}
public function testAuthorCanEdit(): void
{
$user = new User();
$post = (new Post())->setAuthor($user);
$token = new UsernamePasswordToken($user, 'main', $user->getRoles());
$result = $this->voter->vote($token, $post, [PostVoter::EDIT]);
$this->assertSame(VoterInterface::ACCESS_GRANTED, $result);
}
public function testNonAuthorCannotEdit(): void
{
$author = new User();
$otherUser = new User();
$post = (new Post())->setAuthor($author);
$token = new UsernamePasswordToken($otherUser, 'main', $otherUser->getRoles());
$result = $this->voter->vote($token, $post, [PostVoter::EDIT]);
$this->assertSame(VoterInterface::ACCESS_DENIED, $result);
}
}Elke test isoleert een specifieke bedrijfsregel. De testdekking dient alle ondersteunde attributen te omvatten, inclusief randgevallen zoals niet-geauthenticeerde gebruikers en niet-ondersteunde attributen. Voor complexere voters die services vereisen (abonnementsverificatie, quotacontroles), is het mocken van afhankelijkheden via createMock() de standaardaanpak.
Wat gebeurt er wanneer security: false op een firewall wordt ingesteld?
Het instellen van security: false schakelt het volledige Security-component uit voor de routes die onder die firewall vallen. Er wordt geen token aangemaakt, er is geen gebruikersobject beschikbaar en geen enkele voter wordt geraadpleegd. Dit is uitsluitend bedoeld voor ontwikkelroutes zoals de Symfony Profiler.
Het gebruik van security: false op een firewall die API-routes afvangt, verwijdert alle authenticatie en autorisatie. Logging, auditing en rate limiting op basis van de geauthenticeerde gebruiker functioneren dan niet meer. Gebruik in plaats daarvan PUBLIC_ACCESS in access_control om ongeauthenticeerde toegang toe te staan terwijl de firewall actief blijft.
Symfony Security hardenen voorbij de standaardinstellingen
Naast voters en firewalls biedt Symfony aanvullende mechanismen om de beveiligingslaag te verharden. De UserCheckerInterface maakt het mogelijk om extra validaties uit te voeren tijdens het authenticatieproces, zowel voor als na de verificatie van credentials.
namespace App\Security;
use App\Entity\User;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserEnabledChecker implements UserCheckerInterface
{
public function checkPreAuth(UserInterface $user): void
{
if (!$user instanceof User) {
return;
}
if ($user->isBanned()) {
throw new CustomUserMessageAccountStatusException(
'This account has been suspended.'
);
}
}
public function checkPostAuth(UserInterface $user): void
{
if (!$user instanceof User) {
return;
}
if (!$user->isEmailVerified()) {
throw new CustomUserMessageAccountStatusException(
'Please verify your email address before logging in.'
);
}
}
}De UserChecker verdeelt de validaties over twee fases. De checkPreAuth()-methode wordt uitgevoerd voordat het wachtwoord wordt gecontroleerd: bij een geblokkeerd account is het zinloos om credentials te verifieren. De checkPostAuth()-methode wordt aangeroepen na succesvolle authenticatie: het wachtwoord is correct, maar het account is mogelijk nog niet volledig geactiveerd. Deze fasering optimaliseert het authenticatieproces en levert specifieke foutmeldingen per situatie.
Aanvullende hardeningsmaatregelen die in productiesystemen worden verwacht:
- Rate limiting: het ingebouwde RateLimiter-component beperkt loginpogingen per IP-adres of gebruikersnaam en beschermt tegen brute-force-aanvallen
- CSRF-bescherming: activeer CSRF-validatie op alle formulieren, inclusief het loginformulier, via de
enable_csrf-instelling in de firewallconfiguratie - Wachtwoord-hashing: gebruik de
auto-hasher die automatisch het veiligste beschikbare algoritme selecteert (bcrypt of Argon2id, afhankelijk van de PHP-installatie) - Security headers: configureer HTTP-headers zoals
Content-Security-Policy,X-Frame-OptionsenStrict-Transport-Securityvia een event listener opkernel.response
Symfony dispatcht events bij elke stap van het beveiligingsproces: AuthenticationSuccessEvent, LoginSuccessEvent, LogoutEvent, SwitchUserEvent en AccessDeniedEvent. Deze events maken het mogelijk om logging, notificaties of aanvullende controles toe te voegen zonder bestaande authenticators of voters te wijzigen. In productieomgevingen levert het luisteren naar AccessDeniedEvent waardevolle input voor monitoringsystemen en detectie van ongeautoriseerde toegangspogingen.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Conclusie
Het Security-component van Symfony biedt een architectuur die zowel rigoureus als uitbreidbaar is, geschikt voor scenario's van klassieke formulierauthenticatie tot multicriteria autorisatiesystemen in gedistribueerde omgevingen. De beheersing ervan geldt als een onmiskenbaar teken van senioriteit tijdens technische sollicitatiegesprekken.
De belangrijkste punten samengevat:
- Firewalls: de volgorde van declaratie bepaalt de matching; gebruik
security: falseuitsluitend voor ontwikkelroutes, nooit in productie - Stateless versus stateful: de keuze hangt af van de architectuur (monoliet versus microservices) en niet van een technische voorkeur
- Access Token Handler: de
AccessTokenHandlerInterfaceunificeert tokenbeheer met een enkele methode, wat testen en onderhoud vereenvoudigt - Voters: verkies voters voor elke contextuele autorisatielogica; structureer elke voter met privemethoden per actie
- Vote-parameter: benut
addReason()voor debugging en auditbaarheid van toegangsbeslissingen - IsGranted: pas toegangscontrole declaratief toe op controllerniveau voor maximale leesbaarheid
- Access decision strategies: kies
unanimousvoor beveiligingsgevoelige contexten waar defense in depth vereist is - Testing: dek elke combinatie van gebruiker, actie en object af in de unittests van voters
- Hardening: combineer
UserChecker, rate limiting, CSRF, security headers en geheimrotatie voor een diepgaande verdediging
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Symfony-sollicitatievragen: Top 25 in 2026
De 25 meest gestelde Symfony-sollicitatievragen. Architectuur, Doctrine ORM, services, beveiliging, formulieren en tests met gedetailleerde antwoorden en codevoorbeelden.

Doctrine ORM: Relaties beheersen in Symfony
Volledige gids voor Doctrine ORM-relaties in Symfony. OneToMany, ManyToMany, laadstrategieën en performance-optimalisatie met praktische voorbeelden.

Symfony 7: API Platform en Best Practices
Volledige gids voor het bouwen van professionele REST API's met Symfony 7 en API Platform 4. State Providers, Processors, validatie en serialisatie uitgelegd met praktische voorbeelden.