Skip to content

Commit 1c7c26d

Browse files
PierreThibaudeausoyukaalanpoulain
authored
feat: improves iri_only implementation (#3454)
* Issue-#3275 - feat: Improves iri_only implementation (#3454) * Add back default context * fix: minor fixes Co-authored-by: soyuka <[email protected]> Co-authored-by: Alan Poulain <[email protected]>
1 parent b322aff commit 1c7c26d

File tree

10 files changed

+298
-13
lines changed

10 files changed

+298
-13
lines changed

features/bootstrap/DoctrineContext.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\FourthLevel as FourthLevelDocument;
5555
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Greeting as GreetingDocument;
5656
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\InitializeInput as InitializeInputDocument;
57+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\IriOnlyDummy as IriOnlyDummyDocument;
5758
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\MaxDepthDummy as MaxDepthDummyDocument;
5859
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathDummy as NetworkPathDummyDocument;
5960
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathRelationDummy as NetworkPathRelationDummyDocument;
@@ -120,6 +121,7 @@
120121
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Greeting;
121122
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InitializeInput;
122123
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InternalUser;
124+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\IriOnlyDummy;
123125
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
124126
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy;
125127
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathRelationDummy;
@@ -1585,6 +1587,20 @@ public function thereAreDummyMercureObjects(int $nb)
15851587
$this->manager->flush();
15861588
}
15871589

1590+
/**
1591+
* @Given there are :nb iriOnlyDummies
1592+
*/
1593+
public function thereAreIriOnlyDummies(int $nb)
1594+
{
1595+
for ($i = 1; $i <= $nb; ++$i) {
1596+
$iriOnlyDummy = $this->buildIriOnlyDummy();
1597+
$iriOnlyDummy->setFoo('bar'.$nb);
1598+
$this->manager->persist($iriOnlyDummy);
1599+
}
1600+
1601+
$this->manager->flush();
1602+
}
1603+
15881604
/**
15891605
* @Given there are :nb absoluteUrlDummy objects with a related absoluteUrlRelationDummy
15901606
*/
@@ -1867,6 +1883,14 @@ private function buildGreeting()
18671883
return $this->isOrm() ? new Greeting() : new GreetingDocument();
18681884
}
18691885

1886+
/**
1887+
* @return IriOnlyDummy|IriOnlyDummyDocument
1888+
*/
1889+
private function buildIriOnlyDummy()
1890+
{
1891+
return $this->isOrm() ? new IriOnlyDummy() : new IriOnlyDummyDocument();
1892+
}
1893+
18701894
/**
18711895
* @return MaxDepthDummy|MaxDepthDummyDocument
18721896
*/

features/jsonld/iri_only.feature

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Feature: JSON-LD using iri_only parameter
2+
In order to improve Vulcain support
3+
As a Vulcain user and as a developer
4+
I should be able to only get an IRI list when I ask a resource.
5+
6+
Scenario: Retrieve Dummy's resource context with iri_only
7+
When I send a "GET" request to "/contexts/IriOnlyDummy"
8+
Then the response status code should be 200
9+
And the response should be in JSON
10+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
11+
And the JSON should be equal to:
12+
"""
13+
{
14+
"@context": {
15+
"@vocab": "http://example.com/docs.jsonld#",
16+
"hydra": "http://www.w3.org/ns/hydra/core#",
17+
"hydra:member": {
18+
"@type": "@id"
19+
}
20+
}
21+
}
22+
"""
23+
24+
@createSchema
25+
Scenario: Retrieve Dummies with iri_only and jsonld_embed_context
26+
Given there are 3 iriOnlyDummies
27+
When I send a "GET" request to "/iri_only_dummies"
28+
Then the response status code should be 200
29+
And the response should be in JSON
30+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
31+
And the JSON should be equal to:
32+
"""
33+
{
34+
"@context": {
35+
"@vocab": "http://example.com/docs.jsonld#",
36+
"hydra": "http://www.w3.org/ns/hydra/core#",
37+
"hydra:member": {
38+
"@type": "@id"
39+
}
40+
},
41+
"@id": "/iri_only_dummies",
42+
"@type": "hydra:Collection",
43+
"hydra:member": [
44+
"/iri_only_dummies/1",
45+
"/iri_only_dummies/2",
46+
"/iri_only_dummies/3"
47+
],
48+
"hydra:totalItems": 3
49+
}
50+
"""

src/Hydra/Serializer/CollectionNormalizer.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,10 @@ public function normalize($object, $format = null, array $context = [])
8686
}
8787

8888
$data['@type'] = 'hydra:Collection';
89-
9089
$data['hydra:member'] = [];
9190
$iriOnly = $context[self::IRI_ONLY] ?? $this->defaultContext[self::IRI_ONLY];
9291
foreach ($object as $obj) {
93-
$data['hydra:member'][] = $iriOnly ? ['@id' => $this->iriConverter->getIriFromItem($obj)] : $this->normalizer->normalize($obj, $format, $context);
92+
$data['hydra:member'][] = $iriOnly ? $this->iriConverter->getIriFromItem($obj) : $this->normalizer->normalize($obj, $format, $context);
9493
}
9594

9695
if ($object instanceof PaginatorInterface) {

src/JsonLd/ContextBuilder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ public function getResourceContext(string $resourceClass, int $referenceType = U
9595
return [];
9696
}
9797

98+
if ($resourceMetadata->getAttribute('normalization_context')['iri_only'] ?? false) {
99+
$context = $this->getBaseContext($referenceType);
100+
$context['hydra:member']['@type'] = '@id';
101+
102+
return $context;
103+
}
104+
98105
return $this->getResourceContextWithShortname($resourceClass, $referenceType, $shortName);
99106
}
100107

src/Serializer/SerializerContextBuilder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public function createFromRequest(Request $request, bool $normalization, array $
7272
}
7373

7474
$context['resource_class'] = $attributes['resource_class'];
75+
$context['iri_only'] = $resourceMetadata->getAttribute('normalization_context')['iri_only'] ?? false;
7576
$context['input'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'input', null, true);
7677
$context['output'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'output', null, true);
7778
$context['request_uri'] = $request->getRequestUri();
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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\Core\Tests\Fixtures\TestBundle\Document;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
18+
19+
/**
20+
* Dummy with iri_only.
21+
*
22+
* @author Pierre Thibaudeau <[email protected]>
23+
*
24+
* @ApiResource(
25+
* normalizationContext={
26+
* "iri_only"=true,
27+
* "jsonld_embed_context"=true
28+
* }
29+
* )
30+
* @ODM\Document
31+
*/
32+
class IriOnlyDummy
33+
{
34+
/**
35+
* @var int The id
36+
*
37+
* @ODM\Id(strategy="INCREMENT", type="integer")
38+
*/
39+
private $id;
40+
41+
/**
42+
* @var string
43+
*
44+
* @ODM\Field(type="string")
45+
*/
46+
private $foo;
47+
48+
public function getId(): int
49+
{
50+
return $this->id;
51+
}
52+
53+
public function getFoo(): string
54+
{
55+
return $this->foo;
56+
}
57+
58+
public function setFoo(string $foo): void
59+
{
60+
$this->foo = $foo;
61+
}
62+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
19+
/**
20+
* Dummy with iri_only.
21+
*
22+
* @author Pierre Thibaudeau <[email protected]>
23+
*
24+
* @ApiResource(
25+
* normalizationContext={
26+
* "iri_only"=true,
27+
* "jsonld_embed_context"=true
28+
* }
29+
* )
30+
* @ORM\Entity
31+
*/
32+
class IriOnlyDummy
33+
{
34+
/**
35+
* @var int The id
36+
*
37+
* @ORM\Column(type="integer")
38+
* @ORM\Id
39+
* @ORM\GeneratedValue(strategy="AUTO")
40+
*/
41+
private $id;
42+
43+
/**
44+
* @var string
45+
*
46+
* @ORM\Column(type="string")
47+
*/
48+
private $foo;
49+
50+
public function getId(): int
51+
{
52+
return $this->id;
53+
}
54+
55+
public function getFoo(): string
56+
{
57+
return $this->foo;
58+
}
59+
60+
public function setFoo(string $foo): void
61+
{
62+
$this->foo = $foo;
63+
}
64+
}

tests/Hydra/Serializer/CollectionNormalizerTest.php

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,12 @@ public function testNormalizeIriOnlyResourceCollection(): void
360360

361361
$delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
362362

363-
$normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal(), [CollectionNormalizer::IRI_ONLY => true]);
363+
$normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
364364
$normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
365365

366366
$actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
367367
'collection_operation_name' => 'get',
368-
'operation_type' => OperationType::COLLECTION,
368+
'iri_only' => true,
369369
'resource_class' => Foo::class,
370370
]);
371371

@@ -374,8 +374,67 @@ public function testNormalizeIriOnlyResourceCollection(): void
374374
'@id' => '/foos',
375375
'@type' => 'hydra:Collection',
376376
'hydra:member' => [
377-
['@id' => '/foos/1'],
378-
['@id' => '/foos/3'],
377+
'/foos/1',
378+
'/foos/3',
379+
],
380+
'hydra:totalItems' => 2,
381+
], $actual);
382+
}
383+
384+
public function testNormalizeIriOnlyEmbedContextResourceCollection(): void
385+
{
386+
$fooOne = new Foo();
387+
$fooOne->id = 1;
388+
$fooOne->bar = 'baz';
389+
390+
$fooThree = new Foo();
391+
$fooThree->id = 3;
392+
$fooThree->bar = 'bzz';
393+
394+
$data = [$fooOne, $fooThree];
395+
396+
$contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
397+
$contextBuilderProphecy->getResourceContext(Foo::class)->willReturn([
398+
'@vocab' => 'http://localhost:8080/docs.jsonld#',
399+
'hydra' => 'http://www.w3.org/ns/hydra/core#',
400+
'hydra:member' => [
401+
'@type' => '@id',
402+
],
403+
]);
404+
405+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
406+
$resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);
407+
408+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
409+
$iriConverterProphecy->getIriFromResourceClass(Foo::class)->willReturn('/foos');
410+
$iriConverterProphecy->getIriFromItem($fooOne)->willReturn('/foos/1');
411+
$iriConverterProphecy->getIriFromItem($fooThree)->willReturn('/foos/3');
412+
413+
$delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
414+
415+
$normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
416+
$normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
417+
418+
$actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
419+
'collection_operation_name' => 'get',
420+
'iri_only' => true,
421+
'jsonld_embed_context' => true,
422+
'resource_class' => Foo::class,
423+
]);
424+
425+
$this->assertSame([
426+
'@context' => [
427+
'@vocab' => 'http://localhost:8080/docs.jsonld#',
428+
'hydra' => 'http://www.w3.org/ns/hydra/core#',
429+
'hydra:member' => [
430+
'@type' => '@id',
431+
],
432+
],
433+
'@id' => '/foos',
434+
'@type' => 'hydra:Collection',
435+
'hydra:member' => [
436+
'/foos/1',
437+
'/foos/3',
379438
],
380439
'hydra:totalItems' => 2,
381440
], $actual);

tests/JsonLd/ContextBuilderTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ public function testResourceContext()
7070
$this->assertEquals($expected, $contextBuilder->getResourceContext($this->entityClass));
7171
}
7272

73+
public function testIriOnlyResourceContext()
74+
{
75+
$this->resourceMetadataFactoryProphecy->create($this->entityClass)->willReturn(new ResourceMetadata('DummyEntity', null, null, null, null, ['normalization_context' => ['iri_only' => true]]));
76+
$this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA']));
77+
$this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA')->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'Dummy property A', true, true, true, true, false, false));
78+
79+
$contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal());
80+
81+
$expected = [
82+
'@vocab' => '#',
83+
'hydra' => 'http://www.w3.org/ns/hydra/core#',
84+
'hydra:member' => [
85+
'@type' => '@id',
86+
],
87+
];
88+
89+
$this->assertEquals($expected, $contextBuilder->getResourceContext($this->entityClass));
90+
}
91+
7392
public function testResourceContextWithJsonldContext()
7493
{
7594
$this->resourceMetadataFactoryProphecy->create($this->entityClass)->willReturn(new ResourceMetadata('DummyEntity'));

0 commit comments

Comments
 (0)