State Providers in Detail
State Providers in Detail
~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Block 6 used state providers PRAGMATICALLY, WITHOUT explaining the concept – this chapter catches up FULLY: HOW does API Platform read data AT ALL, and WHY can that be SWAPPED OUT?
The default provider: invisible, but always there
EVERY #[ApiResource] operation WITHOUT an explicit provider attribute INTERNALLY uses a Doctrine-based DEFAULT provider – EXACTLY WHAT has been happening in the background since chapter 9, WITHOUT us ever writing a provider OURSELVES.
The ProviderInterface
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
final class ExampleProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): mixed
{
// For an item operation: return ONE object (or null).
// For a collection operation: return an iterable.
return null;
}
}JUST ONE method, provide() – the RETURN value depends on the operation type: Get expects ONE object (or null for 404), GetCollection expects an iterable.
Wrapping vs. replacing
| Strategy | Behavior |
|---|---|
| WRAPPING (chapters 48, 54) | Injects the DEFAULT provider/processor via the constructor, calls it AFTER its own logic – the DEFAULT flow stays INTACT |
| REPLACING (chapter 55) | Takes over data retrieval ENTIRELY on its own, NO call to the default mechanism |
OwnProjectsCollectionProvider from chapter 55 REPLACED the query ENTIRELY (findBy() instead of the default query) – ProjectOwnerProcessor from chapter 54, by contrast, only WRAPPED ADDITIONAL logic AROUND the default flow.
Which strategy when
- WRAPPING: for ADDITIONAL side effects (hashing, automatically setting fields, logging) – the BASIC flow stays Doctrine-based.
- REPLACING: when the data source is NOT the database (external API, cache, computed values) OR the query logic STRUCTURALLY differs (chapter 55).
Registering the provider
new Get(provider: ExampleProvider::class),EXACTLY the same syntax as processor: – provider can be set AT the resource level (applies to ALL read operations) OR AT the operation level (ONLY this one).
Tipp: Symfony's autowiring AUTOMATICALLY injects EVERYTHING the provider's constructor REQUIRES (repositories, Security, custom services) – NO manual services.yaml configuration needed, EXACTLY as with voters (chapter 52).