Skip to content

Commit 8b768ea

Browse files
authored
fix: state interfaces (#1533)
1 parent 315e3d1 commit 8b768ea

File tree

4 files changed

+99
-97
lines changed

4 files changed

+99
-97
lines changed

core/dto.md

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ final class UserResetPasswordDto
2828
namespace App\Model;
2929

3030
use App\Dto\UserResetPasswordDto;
31+
use App\State\UserResetPasswordProcessor;
3132

32-
#[ApiResource(input: UserResetPasswordDto::class)]
33+
#[ApiResource(input: UserResetPasswordDto::class, processor: UserResetPasswordProcessor::class)]
3334
final class User {}
3435
```
3536

@@ -46,19 +47,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
4647

4748
final class UserResetPasswordProcessor implements ProcessorInterface
4849
{
49-
public function process($data, array $identifiers = [], ?string $operationName = null, array $context = [])
50+
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
5051
{
5152
if ('[email protected]' === $data->email) {
5253
return $data;
5354
}
5455

5556
throw new NotFoundHttpException();
5657
}
57-
58-
public function supports($data, array $identifiers = [], ?string $operationName = null, array $context = []): bool
59-
{
60-
return $data instanceof UserResetPasswordDto;
61-
}
6258
}
6359
```
6460

@@ -78,15 +74,13 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
7874

7975
final class BookRepresentationProvider implements ProviderInterface
8076
{
81-
public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = [])
77+
/**
78+
* {@inheritDoc}
79+
*/
80+
public function provide(Operation $operation, array $uriVariables = [], array $context = [])
8281
{
8382
return new AnotherRepresentation();
8483
}
85-
86-
public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool
87-
{
88-
return Book::class === $resourceClass;
89-
}
9084
}
9185
```
9286

core/identifiers.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ namespace App\Entity;
1818

1919
use ApiPlatform\Metadata\ApiProperty;
2020
use ApiPlatform\Metadata\ApiResource;
21+
use App\State\PersonProvider;
2122
use App\Uuid;
2223

23-
#[ApiResource]
24+
#[ApiResource(provider: PersonProvider::class)]
2425
final class Person
2526
{
2627
/**
@@ -39,12 +40,18 @@ properties:
3940
App\Entity\Person:
4041
code:
4142
identifier: true
43+
resource:
44+
App\Entity\Person:
45+
provider: App\State\PersonProvider
4246
```
4347
4448
```xml
4549
<properties xmlns="https://api-platform.com/schema/metadata/properties">
46-
<property resource="App\EntityPerson" name="code" identifier="true"/>
50+
<property resource="App\Entity\Person" name="code" identifier="true"/>
4751
</properties>
52+
<resources xmlns="https://api-platform.com/schema/metadata/resources">
53+
<resource class="App\Entity\Person" provider="App\State\PersonProvider" />
54+
</resources>
4855
```
4956

5057
[/codeSelector]
@@ -65,23 +72,15 @@ use App\Uuid;
6572
final class PersonProvider implements ProviderInterface
6673
{
6774
/**
68-
* {@inheritdoc}
75+
* {@inheritDoc}
6976
*/
70-
public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = [])
77+
public function provide(Operation $operation, array $uriVariables = [], array $context = [])
7178
{
7279
// Our identifier is:
7380
// $uriVariables['code']
7481
// although it's a string, it's not an instance of Uuid and we wanted to retrieve the timestamp of our time-based uuid:
7582
// $uriVariable['code']->getTimestamp()
7683
}
77-
78-
/**
79-
* {@inheritdoc}
80-
*/
81-
public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool
82-
{
83-
return $resourceClass === Person::class;
84-
}
8584
}
8685
```
8786

core/state-processors.md

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ However, you may want to:
1313

1414
* store data to other persistence layers (Elasticsearch, external web services...)
1515
* not publicly expose the internal model mapped with the database through the API
16-
* use a separate model for [read operations](data-providers.md) and for updates by implementing patterns such as [CQRS](https://martinfowler.com/bliki/CQRS.html)
16+
* use a separate model for [read operations](state-providers.md) and for updates by implementing patterns such as [CQRS](https://martinfowler.com/bliki/CQRS.html)
1717

1818
Custom state processors can be used to do so. A project can include as many state processors as needed. The first able to
1919
process the data for a given resource will be used.
@@ -27,10 +27,7 @@ bin/console make:state-processor
2727
```
2828

2929
To create a state processor, you have to implement the [`ProcessorInterface`](https://github.com/api-platform/core/blob/main/src/State/ProcessorInterface.php).
30-
This interface defines only two methods:
31-
32-
* `process`: to create, delete, update, or process the given data in any ways
33-
* `supports`: to check whether the given data is supported by this state processor
30+
This interface defines a method `process`: to create, delete, update, or alter the given data in any ways.
3431

3532
Here is an implementation example:
3633

@@ -47,36 +44,40 @@ class BlogPostProcessor implements ProcessorInterface
4744
/**
4845
* {@inheritDoc}
4946
*/
50-
public function process($data, array $identifiers = [], ?string $operationName = null, array $context = [])
47+
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
5148
{
5249
// call your persistence layer to save $data
5350
return $data;
5451
}
55-
56-
/**
57-
* {@inheritDoc}
58-
*/
59-
public function supports($data, array $identifiers = [], ?string $operationName = null, array $context = []): bool
60-
{
61-
return $data instanceof BlogPost && '_api_/blog_posts_post' === $operationName;
62-
}
6352
}
6453
```
6554

66-
You can find the operation name information either with the `debug:router` command (the route name and the operation name are
67-
the same), or by using the `debug:api` command.
55+
We then configure our operation to use this processor:
56+
57+
```php
58+
<?php
59+
60+
namespace App\Entity;
61+
62+
use ApiPlatform\Metadata\Post;
63+
use App\State\BlogPostProcessor;
64+
65+
#[Post(processor: BlogPostProcessor::class)]
66+
class BlogPost {}
67+
```
68+
6869
If service autowiring and autoconfiguration are enabled (they are by default), you are done!
6970

7071
Otherwise, if you use a custom dependency injection configuration, you need to register the corresponding service and add the
71-
`api_platform.state_processor` tag. The `priority` attribute can be used to order processors.
72+
`api_platform.state_processor` tag.
7273

7374
```yaml
7475
# api/config/services.yaml
7576
services:
7677
# ...
7778
App\State\BlogPostProcessor: ~
7879
# Uncomment only if autoconfiguration is disabled
79-
#tags: [ 'api_platform.state_processor', priority: 2 ]
80+
#tags: [ 'api_platform.state_processor' ]
8081
```
8182

8283
## Decorating the Built-In State Processors
@@ -107,22 +108,13 @@ final class UserProcessor implements ProcessorInterface
107108
$this->mailer = $mailer;
108109
}
109110

110-
public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = [])
111+
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
111112
{
112113
$result = $this->decorated->process($data, $uriVariables, $operationName, $context);
113-
114-
if ($data instanceof User && '_api_/blog_posts_post' === $operationName) {
115-
$this->sendWelcomeEmail($data);
116-
}
117-
114+
$this->sendWelcomeEmail($data);
118115
return $result;
119116
}
120117

121-
public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool
122-
{
123-
return $this->decorated->supports($data, $uriVariables, $operationName, $context);
124-
}
125-
126118
private function sendWelcomeEmail(User $user)
127119
{
128120
// Your welcome email logic...
@@ -144,3 +136,17 @@ services:
144136
#arguments: ['@App\State\UserProcessor.inner']
145137
#tags: [ 'api_platform.state_processor' ]
146138
```
139+
140+
And configure that you want to use this processor on the User resource:
141+
142+
```php
143+
<?php
144+
145+
namespace App\Entity;
146+
147+
use ApiPlatform\Metadata\ApiResource;
148+
use App\State\UserProcessor;
149+
150+
#[ApiResource(processor: UserProcessor::class)]
151+
class User {}
152+
```

core/state-providers.md

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ you can use the following command to generate a custom state provider easily:
2626
bin/console make:state-provider
2727
```
2828

29-
Let's start with a State Provider for the URI: `/blog_posts/{id}`, which operation name is `_api_/blog_posts/{id}_get`.
30-
You can find this information either with the `debug:router` command (the route name and the operation name are the same),
31-
or by using the `debug:api` command.
29+
Let's start with a State Provider for the URI: `/blog_posts/{id}`.
3230

3331
First, your `BlogPostProvider` has to implement the
3432
[`StateProviderInterface`](https://github.com/api-platform/core/blob/main/src/State/StateProviderInterface.php):
@@ -46,27 +44,45 @@ final class BlogPostProvider implements ProviderInterface
4644
/**
4745
* {@inheritDoc}
4846
*/
49-
public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = [])
47+
public function provide(Operation $operation, array $uriVariables = [], array $context = [])
5048
{
5149
return new BlogPost($uriVariables['id']);
5250
}
53-
54-
/**
55-
* {@inheritDoc}
56-
*/
57-
public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool
58-
{
59-
return BlogPost::class === $resourceClass && '_api_/blog_posts/{id}_get' === $operationName;
60-
}
6151
}
6252
```
6353

64-
In the `supports` method, we declare that this State Provider only works for the given operation. As this operation expects a
65-
BlogPost we return an instance of the BlogPost in the `provide` method.
54+
As this operation expects a BlogPost we return an instance of the BlogPost in the `provide` method.
6655
The `uriVariables` parameter is an array with the values of the URI variables.
6756

57+
To use this provider we need to configure the provider on the operation:
58+
59+
```php
60+
<?php
61+
62+
namespace App\Entity;
63+
64+
use ApiPlatform\Metadata\Get;
65+
use App\State\BlogPostProvider;
66+
67+
#[Get(provider: BlogPostProvider::class)]
68+
class BlogPost {}
69+
```
70+
71+
If you use the default configuration, the corresponding service will be automatically registered thanks to
72+
[autowiring](https://symfony.com/doc/current/service_container/autowiring.html).
73+
To declare the service explicitly, you can use the following snippet:
74+
75+
```yaml
76+
# api/config/services.yaml
77+
services:
78+
# ...
79+
App\State\BlogPostProvider: ~
80+
# Uncomment only if autoconfiguration is disabled
81+
#tags: [ 'api_platform.state_provider' ]
82+
```
83+
6884
Now let's say that we also want to handle the `/blog_posts` URI which returns a collection. We can change the Provider into
69-
supporting a wider range of operations. Then we can provide a collection of blog posts when the operation is a `GetCollection`:
85+
supporting a wider range of operations. Then we can provide a collection of blog posts when the operation is a `CollectionOperationInterface`:
7086

7187
```php
7288
<?php
@@ -75,47 +91,34 @@ namespace App\State;
7591

7692
use App\Entity\BlogPost;
7793
use ApiPlatform\State\ProviderInterface;
78-
use ApiPlatform\Metadata\GetCollection;
94+
use ApiPlatform\Metadata\CollectionOperationInterface;
7995

8096
final class BlogPostProvider implements ProviderInterface
8197
{
8298
/**
83-
* Provides data.
84-
*
85-
* @return object|array|null
99+
* {@inheritDoc}
86100
*/
87-
public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = [])
101+
public function provide(Operation $operation, array $uriVariables = [], array $context = [])
88102
{
89-
if ($context['operation'] instanceof GetCollection) {
103+
if ($operation instanceof CollectionOperationInterface) {
90104
return [new BlogPost(), new BlogPost()];
91105
}
92106

93107
return new BlogPost($uriVariables['id']);
94108
}
95-
96-
/**
97-
* Whether this state provider supports the class/identifier tuple.
98-
*/
99-
public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool
100-
{
101-
return $data instanceof BlogPost;
102-
}
103109
}
104110
```
105111

106-
If you use the default configuration, the corresponding service will be automatically registered thanks to
107-
[autowiring](https://symfony.com/doc/current/service_container/autowiring.html).
108-
To declare the service explicitly, or to set a custom priority, you can use the following snippet:
112+
We then need to configure this same provider on the BlogPost `GetCollection` operation, or for every operations via the `ApiResource` attribute:
109113

110-
```yaml
111-
# api/config/services.yaml
112-
services:
113-
# ...
114-
App\State\BlogPostProvider: ~
115-
# Uncomment only if autoconfiguration is disabled
116-
#tags: [ 'api_platform.state_provider', priority: 2 ]
117-
```
114+
```php
115+
<?php
116+
117+
namespace App\Entity;
118118

119-
Tagging the service with the tag `api_platform.state_provider` will enable API Platform Core to automatically
120-
register and use this state provider. The optional attribute `priority` allows you to define the order in which the
121-
data providers are called.
119+
use ApiPlatform\Metadata\ApiResource;
120+
use App\State\BlogPostProvider;
121+
122+
#[ApiResource(provider: BlogPostProvider::class)]
123+
class BlogPost {}
124+
```

0 commit comments

Comments
 (0)