Skip to content

Commit 00496e6

Browse files
committed
[WIP] Add Laravel support to state providers documentation
This commit enhances the state providers documentation by including support for Laravel alongside Symfony. It details how to create custom state providers, configure them, and integrate with Laravel-specific ORM like Eloquent and MongoDB, ensuring parity with Symfony's state provider features.
1 parent adae1bb commit 00496e6

File tree

1 file changed

+216
-11
lines changed

1 file changed

+216
-11
lines changed

core/state-providers.md

Lines changed: 216 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
# State Providers
22

3-
To retrieve data exposed by the API, API Platform uses classes called **state providers**. A state provider using [Doctrine
4-
ORM](https://www.doctrine-project.org/projects/orm.html) to retrieve data from a database, a state provider using
3+
To retrieve data exposed by the API, API Platform uses classes called **state providers**.
4+
5+
With the Symfony variant, a state provider using [Doctrine
6+
ORM](https://www.doctrine-project.org/projects/orm.html) is ready to retrieve data from a database and a state provider using
57
[Doctrine MongoDB ODM](https://www.doctrine-project.org/projects/mongodb-odm.html) to retrieve data from a document
6-
database, and a state provider using [Elasticsearch-PHP](https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html)
7-
to retrieve data from an Elasticsearch cluster are included with the library. The first one is enabled by default. These
8-
state providers natively support paged collections and filters. They can be used as-is and are perfectly suited to common uses.
8+
database.
9+
10+
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.
11+
12+
The ORM providers are enabled by default, based on your framework variant (Eloquent or Doctrine will be set up).
13+
14+
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).
15+
16+
These state providers natively support paged collections and filters. They can be used as-is and are perfectly suited to common uses.
917

1018
However, you sometimes want to retrieve data from other sources such as another persistence layer or a webservice.
1119
Custom state providers can be used to do so. A project can include as many state providers as needed. The first able to
1220
retrieve data for a given resource will be used.
1321

1422
To do so you need to implement the `ApiPlatform\State\ProviderInterface`.
1523

16-
In the following examples we will create custom state providers for an entity class called `App\Entity\BlogPost`.
17-
Note, that if your entity is not Doctrine-related, you need to flag the identifier property by using
24+
In the following examples we will create custom state providers for Symfony entities and Laravel models:
25+
- For Symfony we will create an entity class called `App\Entity\BlogPost`.
26+
- For Laravel, we will create a model class called `App\Models\BlogPost`.
27+
28+
Note, that if your entity is not Doctrine-related or Eloquent-related, you need to flag the identifier property by using
1829
`#[ApiProperty(identifier: true)` for things to work properly (see also [Entity Identifier Case](serialization.md#entity-identifier-case)).
1930

2031
## Creating a Custom State Provider
2132

33+
### Custom State Provider with Symfony
34+
2235
If the [Symfony MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle) is installed in your project,
2336
you can use the following command to generate a custom state provider easily:
2437

@@ -114,7 +127,7 @@ final class BlogPostProvider implements ProviderInterface
114127
}
115128
```
116129

117-
We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operations via the `ApiResource` attribute:
130+
We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operation via the `ApiResource` attribute:
118131

119132
```php
120133
<?php
@@ -129,11 +142,124 @@ use App\State\BlogPostProvider;
129142
class BlogPost {}
130143
```
131144

145+
#### Custom State Provider with Laravel
146+
147+
Using [Laravel Artisan Console](https://laravel.com/docs/artisan), you can generate a custom state provider easily with the following command:
148+
149+
```console
150+
php artisan make:state-provider
151+
```
152+
153+
Let's start with a State Provider for the URI: `/blog_posts/{id}`.
154+
155+
First, your `BlogPostProvider` has to implement the
156+
[`ProviderInterface`](https://github.com/api-platform/core/blob/main/src/State/ProviderInterface.php):
157+
158+
```php
159+
<?php
160+
// api/src/State/BlogPostProvider.php
161+
162+
namespace App\State;
163+
164+
use App\Models\BlogPost;
165+
use ApiPlatform\Metadata\Operation;
166+
use ApiPlatform\State\ProviderInterface;
167+
168+
/**
169+
* @implements ProviderInterface<BlogPost|null>
170+
*/
171+
final class BlogPostProvider implements ProviderInterface
172+
{
173+
private const DATA = [
174+
'ab' => new BlogPost('ab'),
175+
'cd' => new BlogPost('cd'),
176+
];
177+
178+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): BlogPost|null
179+
{
180+
return self::DATA[$uriVariables['id']] ?? null;
181+
}
182+
}
183+
```
184+
185+
For the example, we store the list of our blog posts in an associative array (the `BlogPostProvider::DATA` constant).
186+
187+
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`.
188+
189+
The `$uriVariables` parameter contains an array with the values of the URI variables.
190+
191+
To use this provider we need to configure the provider on the operation:
192+
193+
```php
194+
<?php
195+
// api/src/Models/BlogPost.php
196+
197+
namespace App\Models;
198+
199+
use ApiPlatform\Metadata\Get;
200+
use App\State\BlogPostProvider;
201+
202+
#[Get(provider: BlogPostProvider::class)]
203+
class BlogPost {}
204+
```
205+
206+
Now let's say that we also want to handle the `/blog_posts` URI which returns a collection. We can change the Provider into
207+
supporting a wider range of operations. Then we can provide a collection of blog posts when the operation is a `CollectionOperationInterface`:
208+
209+
```php
210+
<?php
211+
// api/src/State/BlogPostProvider.php
212+
213+
namespace App\State;
214+
215+
use App\Models\BlogPost;
216+
use ApiPlatform\Metadata\Operation;
217+
use ApiPlatform\State\ProviderInterface;
218+
use ApiPlatform\Metadata\CollectionOperationInterface;
219+
220+
/**
221+
* @implements ProviderInterface<BlogPost[]|BlogPost|null>
222+
*/
223+
final class BlogPostProvider implements ProviderInterface
224+
{
225+
private const DATA = [
226+
'ab' => new BlogPost('ab'),
227+
'cd' => new BlogPost('cd'),
228+
];
229+
230+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|BlogPost|null
231+
{
232+
if ($operation instanceof CollectionOperationInterface) {
233+
return self::DATA;
234+
}
235+
236+
return self::DATA[$uriVariables['id']] ?? null;
237+
}
238+
}
239+
```
240+
241+
We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operation via the `ApiResource` attribute:
242+
243+
```php
244+
<?php
245+
// api/src/Models/BlogPost.php
246+
247+
namespace App\Models;
248+
249+
use ApiPlatform\Metadata\ApiResource;
250+
use App\State\BlogPostProvider;
251+
252+
#[ApiResource(provider: BlogPostProvider::class)]
253+
class BlogPost {}
254+
```
255+
132256
## Hooking into the Built-In State Provider
133257

134258
If you want to execute custom business logic before or after retrieving data, this can be achieved by [decorating](https://symfony.com/doc/current/service_container/service_decoration.html) the built-in state providers or using [composition](https://en.wikipedia.org/wiki/Object_composition).
135259

136-
The next example uses a [DTO](https://api-platform.com/docs/core/dto/#using-data-transfer-objects-dtos) to change the presentation for data originally retrieved by the default state provider.
260+
The next examples (one for Symfony and one for Laravel) uses a [DTO](https://api-platform.com/docs/core/dto/#using-data-transfer-objects-dtos) to change the presentation for data originally retrieved by the default state provider.
261+
262+
### Symfony State Provider mechanism
137263

138264
```php
139265
<?php
@@ -142,7 +268,7 @@ The next example uses a [DTO](https://api-platform.com/docs/core/dto/#using-data
142268
namespace App\State;
143269

144270
use App\Dto\AnotherRepresentation;
145-
use App\Model\Book;
271+
use App\Entity\Book;
146272
use ApiPlatform\Metadata\Operation;
147273
use ApiPlatform\State\ProviderInterface;
148274
use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -187,7 +313,86 @@ use App\State\BookRepresentationProvider;
187313
class Book {}
188314
```
189315

190-
## Registering Services Without Autowiring
316+
#### Laravel State Provider mechanism
317+
318+
First, don't forget to tag the service with the `ProviderInterface`
319+
320+
```php
321+
<?php
322+
323+
namespace App\Providers;
324+
325+
use ApiPlatform\State\ProviderInterface;
326+
use App\State\BookStateProvider;
327+
use Illuminate\Support\ServiceProvider;
328+
329+
class AppServiceProvider extends ServiceProvider
330+
{
331+
public function register(): void
332+
{
333+
$this->app->tag([BookRepresentationProvider::class], ProviderInterface::class);
334+
}
335+
336+
public function boot(): void
337+
{
338+
}
339+
}
340+
```
341+
342+
After that, you can inject the `ProviderInterface`
343+
```php
344+
<?php
345+
// api/src/State/BlogPostProvider.php
346+
347+
namespace App\State;
348+
349+
use App\Dto\AnotherRepresentation;
350+
use App\Models\Book;
351+
use ApiPlatform\Metadata\Operation;
352+
use ApiPlatform\State\ProviderInterface;
353+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
354+
355+
/**
356+
* @implements ProviderInterface<AnotherRepresentation>
357+
*/
358+
final class BookRepresentationProvider implements ProviderInterface
359+
{
360+
public function __construct(
361+
private ProviderInterface $itemProvider,
362+
)
363+
{
364+
}
365+
366+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): AnotherRepresentation
367+
{
368+
$book = $this->itemProvider->provide($operation, $uriVariables, $context);
369+
370+
return new AnotherRepresentation(
371+
// Add DTO constructor params here.
372+
// $book->getTitle(),
373+
);
374+
}
375+
}
376+
```
377+
378+
And configure that you want to use this provider on the Book resource:
379+
380+
```php
381+
<?php
382+
// api/app/Models/Book.php
383+
384+
namespace App\Models;
385+
386+
use ApiPlatform\Metadata\Get;
387+
use App\Dto\AnotherRepresentation;
388+
use App\State\BookRepresentationProvider;
389+
390+
#[Get(output: AnotherRepresentation::class, provider: BookRepresentationProvider::class)]
391+
class Book {}
392+
```
393+
394+
395+
## Registering Services Without Autowiring (only for the Symfony variant)
191396

192397
The services in the previous examples are automatically registered because
193398
[autowiring](https://symfony.com/doc/current/service_container/autowiring.html)

0 commit comments

Comments
 (0)