Skip to content

Commit f7301c4

Browse files
authored
Merge pull request #1968 from soyuka/fix-embed
Fix embed
2 parents f9e73f3 + e501c35 commit f7301c4

File tree

10 files changed

+74
-31
lines changed

10 files changed

+74
-31
lines changed

features/main/operation.feature

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,24 @@ Feature: Operation support
1212
"""
1313
"This is a custom action for 42."
1414
"""
15+
16+
@createSchema
17+
@dropSchema
18+
Scenario: Select a resource and it's embedded data
19+
Given there are 1 embedded dummy objects
20+
When I send a "GET" request to "/embedded_dummies_groups/1"
21+
Then the response status code should be 200
22+
And the response should be in JSON
23+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
24+
And the JSON should be equal to:
25+
"""
26+
{
27+
"@context": "/contexts/EmbeddedDummy",
28+
"@id": "/embedded_dummies/1",
29+
"@type": "EmbeddedDummy",
30+
"name": "Dummy #1",
31+
"embeddedDummy": {
32+
"dummyName": "Dummy #1"
33+
}
34+
}
35+
"""

src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -228,46 +228,51 @@ private function addSelect(QueryBuilder $queryBuilder, string $entity, string $a
228228
$select = [];
229229
$entityManager = $queryBuilder->getEntityManager();
230230
$targetClassMetadata = $entityManager->getClassMetadata($entity);
231-
if ($targetClassMetadata->subClasses) {
231+
if (!empty($targetClassMetadata->subClasses)) {
232232
$queryBuilder->addSelect($associationAlias);
233-
} else {
234-
foreach ($this->propertyNameCollectionFactory->create($entity) as $property) {
235-
$propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions);
236233

237-
if (true === $propertyMetadata->isIdentifier()) {
238-
$select[] = $property;
239-
continue;
240-
}
234+
return;
235+
}
236+
237+
foreach ($this->propertyNameCollectionFactory->create($entity) as $property) {
238+
$propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions);
239+
240+
if (true === $propertyMetadata->isIdentifier()) {
241+
$select[] = $property;
242+
continue;
243+
}
241244

245+
// If it's an embedded property see below
246+
if (!array_key_exists($property, $targetClassMetadata->embeddedClasses)) {
242247
//the field test allows to add methods to a Resource which do not reflect real database fields
243248
if ($targetClassMetadata->hasField($property) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) {
244249
$select[] = $property;
245250
}
246251

247-
if (array_key_exists($property, $targetClassMetadata->embeddedClasses)) {
248-
foreach ($this->propertyNameCollectionFactory->create($targetClassMetadata->embeddedClasses[$property]['class']) as $embeddedProperty) {
249-
$propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions);
250-
$propertyName = "$property.$embeddedProperty";
251-
if ($targetClassMetadata->hasField($propertyName) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) {
252-
$select[] = $propertyName;
253-
}
254-
}
255-
}
252+
continue;
256253
}
257254

258-
$queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select)));
255+
// It's an embedded property, select relevent subfields
256+
foreach ($this->propertyNameCollectionFactory->create($targetClassMetadata->embeddedClasses[$property]['class']) as $embeddedProperty) {
257+
$propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions);
258+
$propertyName = "$property.$embeddedProperty";
259+
if ($targetClassMetadata->hasField($propertyName) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) {
260+
$select[] = $propertyName;
261+
}
262+
}
259263
}
264+
265+
$queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select)));
260266
}
261267

262268
/**
263-
* Gets serializer context.
269+
* Gets the serializer context.
264270
*
265271
* @param string $contextType normalization_context or denormalization_context
266272
* @param array $options represents the operation name so that groups are the one of the specific operation
267273
*/
268274
private function getNormalizationContext(string $resourceClass, string $contextType, array $options): array
269275
{
270-
$request = null;
271276
if (null !== $this->requestStack && null !== $this->serializerContextBuilder && null !== $request = $this->requestStack->getCurrentRequest()) {
272277
return $this->serializerContextBuilder->createFromRequest($request, 'normalization_context' === $contextType);
273278
}

src/Bridge/Doctrine/Orm/Filter/DateFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
171171
} catch (\Exception $e) {
172172
// Silently ignore this filter if it can not be transformed to a \DateTime
173173
$this->logger->notice('Invalid filter ignored', [
174-
'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)),
175-
]);
174+
'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)),
175+
]);
176176

177177
return;
178178
}

src/GraphQl/Resolver/Factory/CollectionResolverFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public function __construct(CollectionDataProviderInterface $collectionDataProvi
6363
public function __invoke(string $resourceClass = null, string $rootClass = null, string $operationName = null): callable
6464
{
6565
return function ($source, $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass) {
66+
if (null === $resourceClass) {
67+
return null;
68+
}
69+
6670
if ($this->requestStack && null !== $request = $this->requestStack->getCurrentRequest()) {
6771
$request->attributes->set(
6872
'_graphql_collections_args',
@@ -75,7 +79,7 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
7579
$dataProviderContext['attributes'] = $this->fieldsToAttributes($info);
7680
$dataProviderContext['filters'] = $this->getNormalizedFilters($args);
7781

78-
if (isset($source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_KEY])) {
82+
if (isset($rootClass, $source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_KEY])) {
7983
$rootResolvedFields = $this->identifiersExtractor->getIdentifiersFromItem(unserialize($source[ItemNormalizer::ITEM_KEY]));
8084
$subresource = $this->getSubresource($rootClass, $rootResolvedFields, array_keys($rootResolvedFields), $rootProperty, $resourceClass, true, $dataProviderContext);
8185
$collection = $subresource ?? [];

src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,15 @@ public function __construct(IriConverterInterface $iriConverter, DataPersisterIn
6464
public function __invoke(string $resourceClass = null, string $rootClass = null, string $operationName = null): callable
6565
{
6666
return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $operationName) {
67+
if (null === $resourceClass) {
68+
return null;
69+
}
70+
6771
$data = ['clientMutationId' => $args['input']['clientMutationId'] ?? null];
6872
$item = null;
6973

7074
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
71-
$normalizationContext = $resourceMetadata->getGraphqlAttribute($operationName, 'normalization_context', [], true);
75+
$normalizationContext = $resourceMetadata->getGraphqlAttribute($operationName ?? '', 'normalization_context', [], true);
7276
$normalizationContext['attributes'] = $info->getFieldSelection(PHP_INT_MAX);
7377

7478
if (isset($args['input']['id'])) {

src/GraphQl/Resolver/ResourceAccessCheckerTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
trait ResourceAccessCheckerTrait
3838
{
3939
/**
40-
* @param object $object
40+
* @param mixed $object
4141
*
4242
* @throws Error
4343
*/
@@ -47,7 +47,7 @@ public function canAccess(ResourceAccessCheckerInterface $resourceAccessChecker
4747
return;
4848
}
4949

50-
$isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'access_control', null, true);
50+
$isGranted = $resourceMetadata->getGraphqlAttribute($operationName ?? '', 'access_control', null, true);
5151
if (null === $isGranted || $resourceAccessChecker->isGranted($resourceClass, $isGranted, ['object' => $object])) {
5252
return;
5353
}

src/GraphQl/Type/Definition/IterableType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public function parseLiteral($valueNode)
7777

7878
/**
7979
* @param StringValueNode|BooleanValueNode|IntValueNode|FloatValueNode|ObjectValueNode|ListValueNode $valueNode
80+
*
81+
* @return mixed
8082
*/
8183
private function parseIterableLiteral($valueNode)
8284
{

tests/Bridge/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ public function testApplyToCollection()
9797
$relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
9898

9999
foreach ($relatedNameCollection as $property) {
100-
if ('id' !== $property) {
101-
$relatedClassMetadataProphecy->hasField($property)->willReturn(!\in_array($property, ['notindatabase', 'embeddedDummy'], true))->shouldBeCalled();
100+
if ('id' !== $property && 'embeddedDummy' !== $property) {
101+
$relatedClassMetadataProphecy->hasField($property)->willReturn(!\in_array($property, ['notindatabase'], true))->shouldBeCalled();
102102
}
103103
}
104104
$relatedClassMetadataProphecy->hasField('embeddedDummy.name')->willReturn(true)->shouldBeCalled();
@@ -183,8 +183,8 @@ public function testApplyToItem()
183183
$relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
184184

185185
foreach ($relatedNameCollection as $property) {
186-
if ('id' !== $property) {
187-
$relatedClassMetadataProphecy->hasField($property)->willReturn(!\in_array($property, ['notindatabase', 'embeddedDummy'], true))->shouldBeCalled();
186+
if ('id' !== $property && 'embeddedDummy' !== $property) {
187+
$relatedClassMetadataProphecy->hasField($property)->willReturn(!\in_array($property, ['notindatabase'], true))->shouldBeCalled();
188188
}
189189
}
190190
$relatedClassMetadataProphecy->hasField('embeddedDummy.name')->willReturn(true)->shouldBeCalled();

tests/Fixtures/TestBundle/Entity/EmbeddableDummy.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class EmbeddableDummy
3030
* @var string The dummy name
3131
*
3232
* @ORM\Column(nullable=true)
33+
* @Groups({"embed"})
3334
*/
3435
private $dummyName;
3536

tests/Fixtures/TestBundle/Entity/EmbeddedDummy.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
1717
use Doctrine\ORM\Mapping as ORM;
18+
use Symfony\Component\Serializer\Annotation\Groups;
1819
use Symfony\Component\Validator\Constraints as Assert;
1920

2021
/**
2122
* Embedded Dummy.
2223
*
2324
* @author Jordan Samouh <[email protected]>
2425
*
25-
* @ApiResource(attributes={"filters"={"my_dummy.search", "my_dummy.order", "my_dummy.date", "my_dummy.range", "my_dummy.boolean", "my_dummy.numeric"}})
26+
* @ApiResource(
27+
* attributes={"filters"={"my_dummy.search", "my_dummy.order", "my_dummy.date", "my_dummy.range", "my_dummy.boolean", "my_dummy.numeric"}},
28+
* itemOperations={"get", "put", "delete", "groups"={"method"="GET", "path"="/embedded_dummies_groups/{id}", "normalization_context"={"groups"={"embed"}}}}
29+
* )
2630
* @ORM\Entity
2731
*/
2832
class EmbeddedDummy
@@ -40,6 +44,7 @@ class EmbeddedDummy
4044
* @var string The dummy name
4145
*
4246
* @ORM\Column(nullable=true)
47+
* @Groups({"embed"})
4348
*/
4449
private $name;
4550

@@ -55,6 +60,7 @@ class EmbeddedDummy
5560
* @var EmbeddableDummy
5661
*
5762
* @ORM\Embedded(class="EmbeddableDummy")
63+
* @Groups({"embed"})
5864
*/
5965
public $embeddedDummy;
6066

0 commit comments

Comments
 (0)