Skip to content

Commit f7f9cf0

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

File tree

1 file changed

+157
-2
lines changed

1 file changed

+157
-2
lines changed

core/state-processors.md

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ classes called **state processors**. State processors receive an instance of the
55
the `#[ApiResource]` attribute). This instance contains data submitted by the client during [the deserialization
66
process](serialization.md).
77

8-
A state processor using [Doctrine ORM](https://www.doctrine-project.org/projects/orm.html) is included with the library and
8+
With the Symfony variant, a state processor using [Doctrine ORM](https://www.doctrine-project.org/projects/orm.html) is included with the library and
99
is enabled by default. It is able to persist and delete objects that are also mapped as [Doctrine entities](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/basic-mapping.html).
1010
A [Doctrine MongoDB ODM](https://www.doctrine-project.org/projects/mongodb-odm.html) state processor is also included and can be enabled by following the [MongoDB documentation](mongodb.md).
1111

12+
With the Laravel variant, a state processor using [Eloquent ORM](https://laravel.com/docs/eloquent) is included with the library and
13+
is enabled by default. It is able to persist and delete objects that are also mapped as [Related Models](https://laravel.com/docs/eloquent-relationships#inserting-and-updating-related-models).
14+
A [Laravel MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/ state processor is also included and can be enabled by following the [MongoDB documentation](../core/mongodb.md).
15+
1216
However, you may want to:
1317

1418
* store data to other persistence layers (Elasticsearch, external web services...)
@@ -20,6 +24,7 @@ process the data for a given resource will be used.
2024

2125
## Creating a Custom State Processor
2226

27+
### Custom State Processor with Symfony
2328
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 processor easily:
2429

2530
```console
@@ -75,8 +80,64 @@ use App\State\BlogPostProcessor;
7580
class BlogPost {}
7681
```
7782

83+
### Custom State Processor with Laravel
84+
Using [Laravel Artisan Console](https://laravel.com/docs/artisan), you can generate a custom state processor easily with the following command:
85+
```console
86+
php artisan make:state-processor
87+
```
88+
89+
To create a state processor, you have to implement the [`ProcessorInterface`](https://github.com/api-platform/core/blob/main/src/State/ProcessorInterface.php).
90+
This interface defines a method `process`: to create, delete, update, or alter the given data in any ways.
91+
92+
Here is an implementation example:
93+
94+
```php
95+
<?php
96+
// api/app/State/BlogPostProcessor.php
97+
98+
namespace App\State;
99+
100+
use App\Models\BlogPost;
101+
use ApiPlatform\Metadata\Operation;
102+
use ApiPlatform\State\ProcessorInterface;
103+
104+
/**
105+
* @implements ProcessorInterface<BlogPost, BlogPost|void>
106+
*/
107+
final class BlogPostProcessor implements ProcessorInterface
108+
{
109+
/**
110+
* @return BlogPost|void
111+
*/
112+
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
113+
{
114+
// call your persistence layer to save $data
115+
return $data;
116+
}
117+
}
118+
```
119+
120+
The `process()` method must return the created or modified object, or nothing (that's why `void` is allowed) for `DELETE` operations.
121+
The `process()` method can also take an object as input, in the `$data` parameter, that isn't of the same type that its output (the returned object). See [the DTO documentation entry](dto.md) for more details.
122+
123+
We then configure our operation to use this processor:
124+
125+
```php
126+
<?php
127+
// api/app/Models/BlogPost.php
128+
129+
namespace App\Models;
130+
131+
use ApiPlatform\Metadata\Post;
132+
use App\State\BlogPostProcessor;
133+
134+
#[Post(processor: BlogPostProcessor::class)]
135+
class BlogPost {}
136+
```
137+
78138
## Hooking into the Built-In State Processors
79139

140+
#### Symfony State Provider mechanism
80141
If you want to execute custom business logic before or after persistence, this can be achieved by using [composition](https://en.wikipedia.org/wiki/Object_composition).
81142

82143
Here is an implementation example which uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send new users a welcome email after a REST `POST` or GraphQL `create` operation, in a project using the native Doctrine ORM state processor:
@@ -151,7 +212,101 @@ use App\State\UserProcessor;
151212
class User {}
152213
```
153214

154-
## Registering Services Without Autowiring
215+
#### Laravel State Provider mechanism
216+
If you want to execute custom business logic before or after persistence, this can be achieved by using [composition](https://en.wikipedia.org/wiki/Object_composition).
217+
218+
Here is an implementation example which uses [Laravel Mail](https://laravel.com/docs/mail) to send new users a welcome email after a REST `POST` or GraphQL `create` operation, in a project using the native Eloquent ORM state processor:
219+
220+
```php
221+
<?php
222+
// api/app/State/UserProcessor.php
223+
224+
namespace App\State;
225+
226+
use ApiPlatform\Metadata\DeleteOperationInterface;
227+
use ApiPlatform\Metadata\Operation;
228+
use ApiPlatform\State\ProcessorInterface;
229+
use App\Models\User;
230+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
231+
use Symfony\Component\Mailer\MailerInterface;
232+
233+
/**
234+
* @implements ProcessorInterface<User, User|void>
235+
*/
236+
final class UserProcessor implements ProcessorInterface
237+
{
238+
public function __construct(
239+
private ProcessorInterface $persistProcessor,
240+
private ProcessorInterface $removeProcessor,
241+
)
242+
{
243+
}
244+
245+
/**
246+
* @return User|void
247+
*/
248+
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
249+
{
250+
if ($operation instanceof DeleteOperationInterface) {
251+
return $this->removeProcessor->process($data, $operation, $uriVariables, $context);
252+
}
253+
254+
$result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
255+
$this->sendWelcomeEmail($data);
256+
257+
return $result;
258+
}
259+
260+
private function sendWelcomeEmail(User $user): void
261+
{
262+
// Your welcome email logic...
263+
// Mail::to($user->getEmail())->send(new WelcomeMail($user));
264+
}
265+
}
266+
```
267+
268+
Don't forget to tag the service with the `PersistProcessor` and the `RemoveProcessor` state classes.
269+
270+
```php
271+
<?php
272+
273+
namespace App\Providers;
274+
275+
use ApiPlatform\Laravel\Eloquent\State\PersistProcessor;
276+
use ApiPlatform\Laravel\Eloquent\State\RemoveProcessor;
277+
use App\State\UserProcessor;
278+
use Illuminate\Support\ServiceProvider;
279+
280+
class AppServiceProvider extends ServiceProvider
281+
{
282+
public function register(): void
283+
{
284+
$this->app->tag([UserProcessor::class], [PersistProcessor::class, RemoveProcessor::class,]);
285+
}
286+
287+
public function boot(): void
288+
{
289+
}
290+
}
291+
```
292+
If you're using Laravel MongoDB ODM instead of Eloquent ORM, make sure you're using the right services.
293+
294+
Finally, configure that you want to use this processor on the User resource:
295+
296+
```php
297+
<?php
298+
// api/app/Models/User.php
299+
300+
namespace App\Models;
301+
302+
use ApiPlatform\Metadata\ApiResource;
303+
use App\State\UserProcessor;
304+
305+
#[ApiResource(processor: UserProcessor::class)]
306+
class User {}
307+
```
308+
309+
## Registering Services Without Autowiring (only for the Symfony variant)
155310

156311
The previous examples work because service autowiring and autoconfiguration are enabled by default in Symfony and API Platform.
157312
If you disabled this feature, you need to register the services by yourself and add the `api_platform.state_processor` tag.

0 commit comments

Comments
 (0)