Skip to content

Commit 4eb09f7

Browse files
committed
feat: use fromResourceTransformer in PersistProcessor
1 parent 2d665c4 commit 4eb09f7

20 files changed

+230
-51
lines changed

features/doctrine/transform_model.feature

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,49 @@ Feature: Use an entity or document transformer to return the correct ressource
22

33
@createSchema
44
@!mongodb
5-
Scenario: Get collection from entities
5+
Scenario: Get transformed collection from entities
66
Given there is a TransformedDummy for date '2025-01-01'
77
When I send a "GET" request to "/transformed_dummy_entity_ressources"
88
Then the response status code should be 200
99
And the response should be in JSON
1010
And the JSON node "hydra:totalItems" should be equal to 1
1111

1212
@!mongodb
13-
Scenario: Get item from entity
13+
Scenario: Get transform item from entity
1414
Given there is a TransformedDummy for date '2025-01-01'
1515
When I send a "GET" request to "/transformed_dummy_entity_ressources/1"
1616
Then the response status code should be 200
1717
And the response should be in JSON
1818
And the JSON node "year" should exist
1919
And the JSON node year should be equal to "2025"
2020

21+
@!mongodb
22+
Scenario: Post new entity from transformed resource
23+
Given I add "Content-type" header equal to "application/ld+json"
24+
When I send a "POST" request to "/transformed_dummy_entity_ressources" with body:
25+
"""
26+
{
27+
"year" : 2020
28+
}
29+
"""
30+
Then the response status code should be 201
31+
And the response should be in JSON
32+
And the JSON node "year" should be equal to "2020"
33+
34+
@!mongodb
35+
Scenario: Patch entity from transformed resource
36+
Given there is a TransformedDummy for date '2025-01-01'
37+
And I add "Content-type" header equal to "application/merge-patch+json"
38+
When I send a "PATCH" request to "/transformed_dummy_entity_ressources/1" with body:
39+
"""
40+
{
41+
"year" : 2020
42+
}
43+
"""
44+
Then the response status code should be 200
45+
And the response should be in JSON
46+
And the JSON node "year" should be equal to "2020"
47+
2148
@createSchema
2249
@mongodb
2350
Scenario: Get collection from documents

features/hydra/error.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Feature: Error handling
1616
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
1717
And the header "Link" should contain '<http://www.w3.org/ns/hydra/error>; rel="http://www.w3.org/ns/json-ld#error"'
1818
And the JSON node "type" should exist
19-
And the JSON node "title" should not exists
19+
And the JSON node "title" should not exist
2020
And the JSON node "hydra:title" should be equal to "An error occurred"
2121
And the JSON node "detail" should exist
2222
And the JSON node "description" should not exist

src/Doctrine/Common/State/Options.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
class Options implements OptionsInterface
1919
{
2020
/**
21-
* @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
22-
* @param mixed $toResourceTransformer experimental callable, typed mixed as we may want a service name in the future
21+
* @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
22+
* @param mixed $toResourceTransformer experimental callable, typed mixed as we may want a service name in the future
2323
* @param mixed $fromResourceTransformer experimental callable, typed mixed as we may want a service name in the future
2424
*/
2525
public function __construct(

src/Doctrine/Common/State/PersistProcessor.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@
1919
use ApiPlatform\State\ProcessorInterface;
2020
use Doctrine\Persistence\ManagerRegistry;
2121
use Doctrine\Persistence\ObjectManager as DoctrineObjectManager;
22+
use Psr\Container\ContainerInterface;
2223

2324
final class PersistProcessor implements ProcessorInterface
2425
{
2526
use ClassInfoTrait;
2627
use LinksHandlerTrait;
2728
use ResourceTransformerLocatorTrait;
2829

29-
public function __construct(private readonly ManagerRegistry $managerRegistry)
30+
public function __construct(private readonly ManagerRegistry $managerRegistry,
31+
?ContainerInterface $handleLinksLocator = null,
32+
?ContainerInterface $resourceTransformerLocator = null)
3033
{
34+
$this->resourceTransformerLocator = $resourceTransformerLocator;
3135
}
3236

3337
/**
@@ -39,6 +43,12 @@ public function __construct(private readonly ManagerRegistry $managerRegistry)
3943
*/
4044
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
4145
{
46+
// if a transformer is defined, start with that
47+
$data = match ($transformer = $this->getFromResourceTransformer($operation)) {
48+
null => $data,
49+
default => $transformer($data, $this->getManager($operation, $this->managerRegistry)),
50+
};
51+
4252
if (
4353
!\is_object($data)
4454
|| !$manager = $this->managerRegistry->getManagerForClass($class = $this->getObjectClass($data))
@@ -105,7 +115,10 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
105115
$manager->flush();
106116
$manager->refresh($data);
107117

108-
return $data;
118+
return match ($transformer = $this->getToResourceTransformer($operation)) {
119+
null => $data,
120+
default => $transformer($data, $operation, $uriVariables, $context),
121+
};
109122
}
110123

111124
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common\State;
15+
16+
use Doctrine\Persistence\ObjectManager;
17+
18+
interface ResourceTransformerInterface
19+
{
20+
/**
21+
* @param object $entityOrDocument the doctrine entity or document to make a resource from
22+
*
23+
* @return object the resulting ApiResource
24+
*/
25+
public function toResource(object $entityOrDocument): object;
26+
27+
/**
28+
* @param object $resource the resource you want to persist
29+
* @param ObjectManager $objectManager the object manager to handle this kind of resources
30+
*
31+
* @return object the existing or new entity or document
32+
*/
33+
public function fromResource(object $resource, ObjectManager $objectManager): object;
34+
}

src/Doctrine/Common/State/ResourceTransformerLocatorTrait.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
namespace ApiPlatform\Doctrine\Common\State;
1515

1616
use ApiPlatform\Metadata\Operation;
17+
use Doctrine\Persistence\ManagerRegistry;
18+
use Doctrine\Persistence\ObjectManager;
1719
use Psr\Container\ContainerInterface;
1820

1921
/**
@@ -58,4 +60,20 @@ protected function getFromResourceTransformer(Operation $operation): ?callable
5860

5961
return null;
6062
}
63+
64+
protected function getManager(Operation $operation, ManagerRegistry $managerRegistry): ObjectManager
65+
{
66+
$options = $operation->getStateOptions();
67+
$entityClass = match (true) {
68+
$options instanceof \ApiPlatform\Doctrine\Orm\State\Options => $options->getEntityClass(),
69+
$options instanceof \ApiPlatform\Doctrine\Odm\State\Options => $options->getDocumentClass(),
70+
default => null,
71+
};
72+
73+
if ($entityClass) {
74+
return $managerRegistry->getManagerForClass($entityClass);
75+
}
76+
77+
throw new \RuntimeException('This should not be called on an operation without StateOptions');
78+
}
6179
}

src/Doctrine/Odm/State/CollectionProvider.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace ApiPlatform\Doctrine\Odm\State;
1515

1616
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
17-
use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait;
17+
use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait;
1818
use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
1919
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface;
2020
use ApiPlatform\Metadata\Exception\RuntimeException;
@@ -34,17 +34,20 @@ final class CollectionProvider implements ProviderInterface
3434
{
3535
use LinksHandlerLocatorTrait;
3636
use LinksHandlerTrait;
37-
use ModelTransformerLocatorTrait;
37+
use ResourceTransformerLocatorTrait;
3838
use StateOptionsTrait;
3939

4040
/**
4141
* @param AggregationCollectionExtensionInterface[] $collectionExtensions
4242
*/
43-
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ?ContainerInterface $handleLinksLocator = null)
44-
{
43+
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry,
44+
private readonly iterable $collectionExtensions = [],
45+
?ContainerInterface $handleLinksLocator = null,
46+
?ContainerInterface $resourceTransformerLocator = null,
47+
) {
4548
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4649
$this->handleLinksLocator = $handleLinksLocator;
47-
$this->transformEntityLocator = $handleLinksLocator;
50+
$this->resourceTransformerLocator = $resourceTransformerLocator;
4851
$this->managerRegistry = $managerRegistry;
4952
}
5053

@@ -82,7 +85,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
8285

8386
$result = $result ?? $aggregationBuilder->hydrate($documentClass)->getAggregation($executeOptions)->getIterator();
8487

85-
return match ($transformer = $this->getEntityTransformer($operation)) {
88+
return match ($transformer = $this->getToResourceTransformer($operation)) {
8689
null => $result,
8790
default => array_map($transformer, iterator_to_array($result)),
8891
};

src/Doctrine/Odm/State/ItemProvider.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace ApiPlatform\Doctrine\Odm\State;
1515

1616
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
17-
use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait;
17+
use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait;
1818
use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
1919
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface;
2020
use ApiPlatform\Metadata\Exception\RuntimeException;
@@ -37,17 +37,20 @@ final class ItemProvider implements ProviderInterface
3737
{
3838
use LinksHandlerLocatorTrait;
3939
use LinksHandlerTrait;
40+
use ResourceTransformerLocatorTrait;
4041
use StateOptionsTrait;
41-
use ModelTransformerLocatorTrait;
4242

4343
/**
4444
* @param AggregationItemExtensionInterface[] $itemExtensions
4545
*/
46-
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ?ContainerInterface $handleLinksLocator = null)
46+
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry,
47+
private readonly iterable $itemExtensions = [],
48+
?ContainerInterface $handleLinksLocator = null,
49+
?ContainerInterface $resourceTransformerLocator = null)
4750
{
4851
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4952
$this->handleLinksLocator = $handleLinksLocator;
50-
$this->transformEntityLocator = $handleLinksLocator;
53+
$this->resourceTransformerLocator = $resourceTransformerLocator;
5154
$this->managerRegistry = $managerRegistry;
5255
}
5356

@@ -89,7 +92,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
8992

9093
$result = $result ?? ($aggregationBuilder->hydrate($documentClass)->getAggregation($executeOptions)->getIterator()->current() ?: null);
9194

92-
return match ($transformer = $this->getEntityTransformer($operation)) {
95+
return match ($transformer = $this->getToResourceTransformer($operation)) {
9396
null => $result,
9497
default => (null !== $result) ? $transformer($result) : null,
9598
};

src/Doctrine/Odm/State/Options.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
class Options extends CommonOptions implements OptionsInterface
2020
{
2121
/**
22-
* @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
22+
* @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
2323
* @param mixed $transformFromDocument experimental callable, typed mixed as we may want a service name in the future
2424
*
2525
* @see LinksHandlerInterface
2626
*/
2727
public function __construct(
2828
protected ?string $documentClass = null,
29-
mixed $handleLinks = null,
30-
mixed $transformFromDocument = null,
29+
mixed $handleLinks = null,
30+
mixed $transformFromDocument = null,
3131
) {
3232
parent::__construct(handleLinks: $handleLinks, toResourceTransformer: $transformFromDocument);
3333
}

src/Doctrine/Orm/State/CollectionProvider.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace ApiPlatform\Doctrine\Orm\State;
1515

1616
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
17-
use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait;
17+
use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait;
1818
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
1919
use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
2020
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
@@ -37,17 +37,21 @@ final class CollectionProvider implements ProviderInterface
3737
{
3838
use LinksHandlerLocatorTrait;
3939
use LinksHandlerTrait;
40-
use ModelTransformerLocatorTrait;
40+
use ResourceTransformerLocatorTrait;
4141
use StateOptionsTrait;
4242

4343
/**
4444
* @param QueryCollectionExtensionInterface[] $collectionExtensions
4545
*/
46-
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ?ContainerInterface $handleLinksLocator = null)
46+
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
47+
ManagerRegistry $managerRegistry,
48+
private readonly iterable $collectionExtensions = [],
49+
?ContainerInterface $handleLinksLocator = null,
50+
?ContainerInterface $resourceTransformerLocator = null)
4751
{
4852
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4953
$this->handleLinksLocator = $handleLinksLocator;
50-
$this->transformEntityLocator = $handleLinksLocator;
54+
$this->resourceTransformerLocator = $resourceTransformerLocator;
5155
$this->managerRegistry = $managerRegistry;
5256
}
5357

@@ -83,7 +87,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
8387

8488
$result = $result ?? $queryBuilder->getQuery()->getResult();
8589

86-
return match ($transformer = $this->getEntityTransformer($operation)) {
90+
return match ($transformer = $this->getToResourceTransformer($operation)) {
8791
null => $result,
8892
default => array_map($transformer, iterator_to_array($result)),
8993
};

0 commit comments

Comments
 (0)