Skip to content

Commit cbf293e

Browse files
committed
wip: ajout d'un hook entityTransformer
1 parent d3b4b7b commit cbf293e

File tree

11 files changed

+196
-9
lines changed

11 files changed

+196
-9
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Feature: Use an entity transformer to return the correct ressource
2+
3+
@createSchema
4+
Scenario: Get collection
5+
Given there is a TransformedDummyEntity object for date '2025-01-01'
6+
When I send a "GET" request to "/transformed_dummy_ressources"
7+
Then the response status code should be 200
8+
And the response should be in JSON
9+
And the JSON node "hydra:totalItems" should be equal to 1
10+
11+
Scenario: Get item
12+
Given there is a TransformedDummyEntity object for date '2025-01-01'
13+
When I send a "GET" request to "/transformed_dummy_ressources/1"
14+
Then the response status code should be 200
15+
And the response should be in JSON
16+
And the JSON node "year" should exist
17+
And the JSON node year should be equal to "2025"
18+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace ApiPlatform\Doctrine\Common\State;
4+
5+
use ApiPlatform\Metadata\Operation;
6+
use Psr\Container\ContainerInterface;
7+
8+
/**
9+
* Maybe merge this and LinksHandlerLocatorTrait into a OptionsHooksLocatorTrait or something similar?
10+
*/
11+
trait EntityTransformerLocatorTrait
12+
{
13+
private ?ContainerInterface $transformEntityLocator;
14+
15+
protected function getEntityTransformer(Operation $operation): ?callable
16+
{
17+
if (!($options = $operation->getStateOptions()) || !$options instanceof Options) {
18+
return null;
19+
}
20+
21+
$transformEntity = $options->getTransformEntity();
22+
if (\is_callable($transformEntity)) {
23+
return $transformEntity;
24+
}
25+
26+
if ($this->transformEntityLocator && \is_string($transformEntity) && $this->transformEntityLocator->has($transformEntity)) {
27+
return [$this->transformEntityLocator->get($transformEntity), 'transformEntity'];
28+
}
29+
30+
return null;
31+
}
32+
}

src/Doctrine/Common/State/Options.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ class Options implements OptionsInterface
1919
{
2020
/**
2121
* @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
22+
* @param mixed $transformEntity experimental callable, typed mixed as we may want a service name in the future
2223
*/
2324
public function __construct(
2425
protected mixed $handleLinks = null,
26+
protected mixed $transformEntity = null,
2527
) {
2628
}
2729

@@ -37,4 +39,17 @@ public function withHandleLinks(mixed $handleLinks): self
3739

3840
return $self;
3941
}
42+
43+
public function getTransformEntity(): mixed
44+
{
45+
return $this->transformEntity;
46+
}
47+
48+
public function withTransformEntity(mixed $transformEntity): self
49+
{
50+
$self = clone $this;
51+
$self->transformEntity = $transformEntity;
52+
53+
return $self;
54+
}
4055
}

src/Doctrine/Odm/State/CollectionProvider.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Odm\State;
1515

16+
use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait;
1617
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
1718
use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
1819
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface;
@@ -33,6 +34,7 @@ final class CollectionProvider implements ProviderInterface
3334
{
3435
use LinksHandlerLocatorTrait;
3536
use LinksHandlerTrait;
37+
use EntityTransformerLocatorTrait;
3638
use StateOptionsTrait;
3739

3840
/**
@@ -42,6 +44,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource
4244
{
4345
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4446
$this->handleLinksLocator = $handleLinksLocator;
47+
$this->transformEntityLocator = $handleLinksLocator;
4548
$this->managerRegistry = $managerRegistry;
4649
}
4750

@@ -69,13 +72,19 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
6972
$extension->applyToCollection($aggregationBuilder, $documentClass, $operation, $context);
7073

7174
if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) {
72-
return $extension->getResult($aggregationBuilder, $documentClass, $operation, $context);
75+
$result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context);
76+
break;
7377
}
7478
}
7579

7680
$attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? [];
7781
$executeOptions = $attribute['execute_options'] ?? [];
7882

79-
return $aggregationBuilder->hydrate($documentClass)->getAggregation($executeOptions)->getIterator();
83+
$result = $result ?? $aggregationBuilder->hydrate($documentClass)->getAggregation($executeOptions)->getIterator();
84+
85+
return match($transformer = $this->getEntityTransformer($operation)){
86+
null => $result,
87+
default => array_map($transformer, iterator_to_array($result))
88+
};
8089
}
8190
}

src/Doctrine/Odm/State/ItemProvider.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Odm\State;
1515

16+
use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait;
1617
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
1718
use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
1819
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface;
@@ -37,6 +38,7 @@ final class ItemProvider implements ProviderInterface
3738
use LinksHandlerLocatorTrait;
3839
use LinksHandlerTrait;
3940
use StateOptionsTrait;
41+
use EntityTransformerLocatorTrait;
4042

4143
/**
4244
* @param AggregationItemExtensionInterface[] $itemExtensions
@@ -45,6 +47,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource
4547
{
4648
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4749
$this->handleLinksLocator = $handleLinksLocator;
50+
$this->transformEntityLocator = $handleLinksLocator;
4851
$this->managerRegistry = $managerRegistry;
4952
}
5053

@@ -77,12 +80,18 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7780
$extension->applyToItem($aggregationBuilder, $documentClass, $uriVariables, $operation, $context);
7881

7982
if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) {
80-
return $extension->getResult($aggregationBuilder, $documentClass, $operation, $context);
83+
$result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context);
84+
break;
8185
}
8286
}
8387

8488
$executeOptions = $operation->getExtraProperties()['doctrine_mongodb']['execute_options'] ?? [];
8589

86-
return $aggregationBuilder->hydrate($documentClass)->getAggregation($executeOptions)->getIterator()->current() ?: null;
90+
$result = $result ?? ($aggregationBuilder->hydrate($documentClass)->getAggregation($executeOptions)->getIterator()->current() ?: null);
91+
92+
return match($transformer = $this->getEntityTransformer($operation)){
93+
null => $result,
94+
default => ($result !== null)? $transformer($result) : null
95+
};
8796
}
8897
}

src/Doctrine/Orm/State/CollectionProvider.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Orm\State;
1515

16+
use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait;
1617
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
1718
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
1819
use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
@@ -37,6 +38,7 @@ final class CollectionProvider implements ProviderInterface
3738
use LinksHandlerLocatorTrait;
3839
use LinksHandlerTrait;
3940
use StateOptionsTrait;
41+
use EntityTransformerLocatorTrait;
4042

4143
/**
4244
* @param QueryCollectionExtensionInterface[] $collectionExtensions
@@ -45,6 +47,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource
4547
{
4648
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4749
$this->handleLinksLocator = $handleLinksLocator;
50+
$this->transformEntityLocator = $handleLinksLocator;
4851
$this->managerRegistry = $managerRegistry;
4952
}
5053

@@ -73,10 +76,16 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7376
$extension->applyToCollection($queryBuilder, $queryNameGenerator, $entityClass, $operation, $context);
7477

7578
if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($entityClass, $operation, $context)) {
76-
return $extension->getResult($queryBuilder, $entityClass, $operation, $context);
79+
$result = $extension->getResult($queryBuilder, $entityClass, $operation, $context);
80+
break;
7781
}
7882
}
7983

80-
return $queryBuilder->getQuery()->getResult();
84+
$result = $result ?? $queryBuilder->getQuery()->getResult();
85+
86+
return match($transformer = $this->getEntityTransformer($operation)){
87+
null => $result,
88+
default => array_map($transformer, iterator_to_array($result))
89+
};
8190
}
8291
}

src/Doctrine/Orm/State/ItemProvider.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Orm\State;
1515

16+
use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait;
1617
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
1718
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
1819
use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface;
@@ -37,6 +38,7 @@ final class ItemProvider implements ProviderInterface
3738
use LinksHandlerLocatorTrait;
3839
use LinksHandlerTrait;
3940
use StateOptionsTrait;
41+
use EntityTransformerLocatorTrait;
4042

4143
/**
4244
* @param QueryItemExtensionInterface[] $itemExtensions
@@ -45,6 +47,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource
4547
{
4648
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4749
$this->handleLinksLocator = $handleLinksLocator;
50+
$this->transformEntityLocator = $handleLinksLocator;
4851
$this->managerRegistry = $managerRegistry;
4952
}
5053

@@ -82,10 +85,16 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
8285
$extension->applyToItem($queryBuilder, $queryNameGenerator, $entityClass, $uriVariables, $operation, $context);
8386

8487
if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($entityClass, $operation, $context)) {
85-
return $extension->getResult($queryBuilder, $entityClass, $operation, $context);
88+
$result = $extension->getResult($queryBuilder, $entityClass, $operation, $context);
89+
break;
8690
}
8791
}
8892

89-
return $queryBuilder->getQuery()->getOneOrNullResult();
93+
$result = $result ?? $queryBuilder->getQuery()->getOneOrNullResult();
94+
95+
return match($transformer = $this->getEntityTransformer($operation)){
96+
null => $result,
97+
default=> $transformer($result)
98+
};
9099
}
91100
}

src/Doctrine/Orm/State/Options.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ class Options extends CommonOptions implements OptionsInterface
2020
{
2121
/**
2222
* @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future
23+
* @param string|callable $transformEntity experimental callable, typed mixed as we may want a service name in the future
2324
*
2425
* @see LinksHandlerInterface
2526
*/
2627
public function __construct(
2728
protected ?string $entityClass = null,
2829
mixed $handleLinks = null,
30+
mixed $transformEntity = null
2931
) {
30-
parent::__construct(handleLinks: $handleLinks);
32+
parent::__construct(handleLinks: $handleLinks, transformEntity: $transformEntity);
3133
}
3234

3335
public function getEntityClass(): ?string

tests/Behat/DoctrineContext.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SymfonyUuidDummy;
200200
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon;
201201
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel;
202+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity;
202203
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TreeDummy;
203204
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UrlEncodedId;
204205
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\User;
@@ -2325,6 +2326,15 @@ public function thereAreIssue6039Users(): void
23252326
$this->manager->flush();
23262327
}
23272328

2329+
/**
2330+
* @Given there is a TransformedDummyEntity object for date :date
2331+
*/
2332+
public function thereIsATransformedDummyEntity(string $date): void
2333+
{
2334+
$this->manager->persist(new TransformedDummyEntity(new \DateTimeImmutable($date)));
2335+
$this->manager->flush();
2336+
}
2337+
23282338
private function isOrm(): bool
23292339
{
23302340
return null !== $this->schemaTool;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
4+
5+
use ApiPlatform\Doctrine\Orm\State\Options;
6+
use ApiPlatform\Metadata\ApiResource;
7+
use ApiPlatform\Metadata\Get;
8+
use ApiPlatform\Metadata\GetCollection;
9+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity;
10+
11+
#[ApiResource(
12+
operations :[
13+
new GetCollection(),
14+
new Get(),
15+
],
16+
stateOptions: new Options(
17+
entityClass: TransformedDummyEntity::class,
18+
transformEntity: [self::class, 'transformEntity'],
19+
)
20+
)]
21+
class TransformedDummyRessource
22+
{
23+
public ?int $id = null;
24+
25+
public ?int $year = null;
26+
27+
public static function transformEntity(TransformedDummyEntity $entity): self
28+
{
29+
$resource = new self();
30+
$resource->id = $entity->getId();
31+
$resource->year = (int) $entity->getDate()->format('Y');
32+
33+
return $resource;
34+
}
35+
36+
}

0 commit comments

Comments
 (0)