# State Providers To retrieve data exposed by the API, API Platform uses classes called **state providers**. With the Symfony variant, a state provider using [Doctrine ORM](https://www.doctrine-project.org/projects/orm.html) is ready to retrieve data from a database and a state provider using [Doctrine MongoDB ODM](https://www.doctrine-project.org/projects/mongodb-odm.html) to retrieve data from a document database. With the Laravel variant, a state provider using [Eloquent ORM](https://laravel.com/docs/eloquent) to retrieve data from a relational database and a state provider. The ORM providers are enabled by default, based on your framework variant (Eloquent or Doctrine will be set up). These state providers natively support paged collections and filters. They can be used as-is and are perfectly suited to common uses. However, you sometimes want to retrieve data from other sources such as another persistence layer or a webservice. Custom state providers can be used to do so. A project can include as many state providers as needed. The first able to retrieve data for a given resource will be used. To do so you need to implement the `ApiPlatform\State\ProviderInterface`. In the following examples we will create custom state providers for Symfony entities and Laravel models: - For Symfony we will create an entity class called `App\Entity\BlogPost`. - For Laravel, we will create a model class called `App\Models\BlogPost`. Note, that if your entity is not Doctrine-related or Eloquent-related, you need to flag the identifier property by using `#[ApiProperty(identifier: true)` for things to work properly (see also [Entity Identifier Case](serialization.md#entity-identifier-case)). ## Creating a Custom State Provider ### Custom State Provider with Symfony If the [Symfony MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle) is installed in your project, you can use the following command to generate a custom state provider easily: ```console bin/console make:state-provider ``` Let's start with a State Provider for the URI: `/blog_posts/{id}`. First, your `BlogPostProvider` has to implement the [`ProviderInterface`](https://github.com/api-platform/core/blob/main/src/State/ProviderInterface.php): ```php */ final class BlogPostProvider implements ProviderInterface { private array $data; public function __construct() { $this->data = [ 'ab' => new BlogPost('ab'), 'cd' => new BlogPost('cd'), ]; } public function provide(Operation $operation, array $uriVariables = [], array $context = []): BlogPost|null { return $this->data[$uriVariables['id']] ?? null; } } ``` For the example, we store the list of our blog posts in an associative array `$data`. As this operation expects a `BlogPost`, the `provide` methods return the instance of the `BlogPost` corresponding to the ID passed in the URL. If the ID doesn't exist in the associative array, `provide()` returns `null`. API Platform will automatically generate a 404 response if the provider returns `null`. The `$uriVariables` parameter contains an array with the values of the URI variables. To use this provider we need to configure the provider on the operation: ```php */ final class BlogPostProvider implements ProviderInterface { // … public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|BlogPost|null { if ($operation instanceof CollectionOperationInterface) { return $this->data; } return $this->data[$uriVariables['id']] ?? null; } } ``` We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operation via the `ApiResource` attribute: ```php */ final class BlogPostProvider implements ProviderInterface { private array $data; public function __construct() { $this->data = [ 'ab' => new BlogPost('ab'), 'cd' => new BlogPost('cd'), ]; } public function provide(Operation $operation, array $uriVariables = [], array $context = []): BlogPost|null { return $this->data[$uriVariables['id']] ?? null; } } ``` For the example, we store the list of our blog posts in an associative array `$data`. As this operation expects a `BlogPost`, the `provide` methods return the instance of the `BlogPost` corresponding to the ID passed in the URL. If the ID doesn't exist in the associative array, `provide()` returns `null`. API Platform will automatically generate a 404 response if the provider returns `null`. The `$uriVariables` parameter contains an array with the values of the URI variables. To use this provider we need to configure the provider on the operation: ```php */ final class BlogPostProvider implements ProviderInterface { // … public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|BlogPost|null { if ($operation instanceof CollectionOperationInterface) { return $this->data; } return $this->data[$uriVariables['id']] ?? null; } } ``` We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operation via the `ApiResource` attribute: ```php */ final class BookRepresentationProvider implements ProviderInterface { public function __construct( #[Autowire(service: 'api_platform.doctrine.orm.state.item_provider')] private ProviderInterface $itemProvider, ) { } public function provide(Operation $operation, array $uriVariables = [], array $context = []): AnotherRepresentation { $book = $this->itemProvider->provide($operation, $uriVariables, $context); return new AnotherRepresentation( // Add DTO constructor params here. // $book->getTitle(), ); } } ``` And configure that you want to use this provider on the Book resource: ```php */ final class BookRepresentationProvider implements ProviderInterface { public function __construct( private ProviderInterface $itemProvider, ) { } public function provide(Operation $operation, array $uriVariables = [], array $context = []): AnotherRepresentation { $book = $this->itemProvider->provide($operation, $uriVariables, $context); return new AnotherRepresentation( // Add DTO constructor params here. // $book->getTitle(), ); } } ``` And we bind the [ItemProvider](https://github.com/api-platform/core/blob/main/src/Laravel/Eloquent/State/ItemProvider.php) in our Service Provider ```php app->singleton(BookRepresentationProvider::class, function (Application $app) { return new BookRepresentationProvider( $app->make(ItemProvider::class), ); }); } //... } ``` Finally, configure that you want to use this provider on the Book resource: ```php */ class ProductRepository extends EntityRepository { public function findAvailable(): QueryBuilder { return $this->createQueryBuilder('o') ->andWhere('o.available = :available') ->setParameter('available', true); } } ``` The providers apply identifier resolution (for item operations), pagination, and filters on top of the returned builder. A custom root alias is supported — the link handler reads the builder's root alias automatically. If the method does not exist on the repository, a `RuntimeException` is thrown: `The repository method "ProductRepository::findAvailable" does not exist.` If the method returns a value that is not the expected builder type, a `RuntimeException` is thrown: `The repository method "findAvailable" must return a QueryBuilder instance.` > [!NOTE] Because the filter applies at the item level too, a `Get` operation using a > `repositoryMethod` that filters rows will return a 404 response for any item excluded by that > filter. ### GraphQL `repositoryMethod` works identically for GraphQL queries. Use it on the `ApiResource` or on specific GraphQL operations: ```php $entity, 'fieldAlias' => $scalar]` instead of plain entities. To map the scalar back onto the entity, combine `repositoryMethod` with a `processor` on the operation. A processor only runs on a read operation when `write: true` is set on that operation. Without this flag the processor stage is skipped and the raw array rows reach normalization, which produces errors such as "Cannot return null for non-nullable field". Set `write: true` explicitly to enable the processor. **REST example:** ```php */ class CartRepository extends EntityRepository { public function getCartsWithTotalQuantity(): QueryBuilder { return $this->createQueryBuilder('o') ->leftJoin('o.items', 'items') ->addSelect('COALESCE(SUM(items.quantity), 0) AS totalQuantity') ->addGroupBy('o.id'); } } ``` ```php totalQuantity = $row['totalQuantity'] ?? 0; $row = $cart; } return $data; } } ``` **GraphQL example:** The same `process` method works for GraphQL. Declare it on the `QueryCollection` operation alongside `write: true`: ```php totalQuantity = $row['totalQuantity'] ?? 0; $row = $cart; } return $data; } } ``` With `paginationEnabled: false` the GraphQL query returns a plain list: ```graphql { carts { totalQuantity } } ``` With pagination enabled (the default), it returns a Relay connection: ```graphql { carts { edges { node { totalQuantity } } } } ``` ## Registering Services Without Autowiring (only for the Symfony variant) The services in the previous examples are automatically registered because [autowiring](https://symfony.com/doc/current/service_container/autowiring.html) and autoconfiguration are enabled by default in API Platform. To declare the service explicitly, you can use the following snippet: ```yaml # api/config/services.yaml services: # ... App\State\BlogPostProvider: tags: ["api_platform.state_provider"] # api/config/services.yaml services: # ... App\State\BookRepresentationProvider: arguments: $itemProvider: "@api_platform.doctrine.orm.state.item_provider" tags: ["api_platform.state_provider"] ```