diff --git a/core/state-providers.md b/core/state-providers.md index b5de9769c54..bb5f04f6c08 100644 --- a/core/state-providers.md +++ b/core/state-providers.md @@ -1,11 +1,19 @@ # State Providers -To retrieve data exposed by the API, API Platform uses classes called **state providers**. A state provider using [Doctrine -ORM](https://www.doctrine-project.org/projects/orm.html) to retrieve data from a database, a state provider using +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, and a state provider using [Elasticsearch-PHP](https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html) -to retrieve data from an Elasticsearch cluster are included with the library. The first one is enabled by default. These -state providers natively support paged collections and filters. They can be used as-is and are perfectly suited to common uses. +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 using [Laravel MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/) to retrieve data from a document database. + +The ORM providers are enabled by default, based on your framework variant (Eloquent or Doctrine will be set up). + +Also, both Symfony and Laravel variant come with a state provider to retrieve data from an Elasticsearch cluster using the library [Elasticsearch-PHP](https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html). + +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 @@ -13,12 +21,17 @@ 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 an entity class called `App\Entity\BlogPost`. -Note, that if your entity is not Doctrine-related, you need to flag the identifier property by using +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: @@ -114,7 +127,7 @@ final class BlogPostProvider implements ProviderInterface } ``` -We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operations via the `ApiResource` attribute: +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 const DATA = [ + 'ab' => new BlogPost('ab'), + 'cd' => new BlogPost('cd'), + ]; + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): BlogPost|null + { + return self::DATA[$uriVariables['id']] ?? null; + } +} +``` + +For the example, we store the list of our blog posts in an associative array (the `BlogPostProvider::DATA` constant). + +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 +{ + private const DATA = [ + 'ab' => new BlogPost('ab'), + 'cd' => new BlogPost('cd'), + ]; + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|BlogPost|null + { + if ($operation instanceof CollectionOperationInterface) { + return self::DATA; + } + + return self::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 +app->tag([BookRepresentationProvider::class], ProviderInterface::class); + } + + public function boot(): void + { + } +} +``` + +After that, you can inject the `ProviderInterface` +```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 configure that you want to use this provider on the Book resource: + +```php +