Skip to content

Commit 10a009f

Browse files
adelplaceadelplace
authored andcommitted
alwaysIdentifier annotation option implementation
1 parent 1157397 commit 10a009f

File tree

10 files changed

+318
-4
lines changed

10 files changed

+318
-4
lines changed

features/bootstrap/FeatureContext.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
declare(strict_types=1);
1313

14+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\AlwaysIdentifierDummy;
1415
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Answer;
1516
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeItem;
1617
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeLabel;
@@ -838,4 +839,23 @@ public function thereAreDummyDateObjectsWithDummyDate(int $nb)
838839

839840
$this->manager->flush();
840841
}
842+
843+
/**
844+
* @Given there are AlwaysIdentifierDummies
845+
*/
846+
public function thereAreAlwaysIdentifierDummies()
847+
{
848+
$mainDummy = new AlwaysIdentifierDummy();
849+
$relatedDummy = new AlwaysIdentifierDummy();
850+
851+
$this->manager->persist($relatedDummy);
852+
853+
$mainDummy->setParent($relatedDummy);
854+
$mainDummy->setChildren([$relatedDummy]);
855+
$mainDummy->setRelated([$relatedDummy]);
856+
857+
$this->manager->persist($mainDummy);
858+
859+
$this->manager->flush();
860+
}
841861
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
Feature: allow resources in properties to always be serialized as identifiers
2+
In order to use a hypermedia API
3+
As a client software developer
4+
I need to be able to force to serialize only the identifier of the related resource
5+
6+
@createSchema
7+
@dropSchema
8+
Scenario: Create a set of Dummy with the alwaysIdentifier attributes and check the HAL formatted response
9+
Given there are AlwaysIdentifierDummies
10+
When I add "Accept" header equal to "application/hal+json"
11+
And I add "Content-Type" header equal to "application/hal+json"
12+
And I send a "GET" request to "/always_identifier_dummies/2"
13+
Then the JSON should be equal to:
14+
"""
15+
{
16+
"_links": {
17+
"self": {
18+
"href": "/always_identifier_dummies/2"
19+
},
20+
"children": [
21+
{
22+
"href": "/always_identifier_dummies/1"
23+
}
24+
],
25+
"related": [
26+
{
27+
"href": "/always_identifier_dummies/1"
28+
}
29+
],
30+
"parent": {
31+
"href": "/always_identifier_dummies/1"
32+
}
33+
},
34+
"_embedded": {
35+
"children": [
36+
"/always_identifier_dummies/1"
37+
],
38+
"related": [
39+
{
40+
"_links": {
41+
"self": {
42+
"href": "/always_identifier_dummies/1"
43+
}
44+
}
45+
}
46+
],
47+
"parent": "/always_identifier_dummies/1"
48+
}
49+
}
50+
"""
51+
52+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Feature: allow resources in properties to always be serialized as identifiers
2+
In order to use a hypermedia API
3+
As a client software developer
4+
I need to be able to force to serialize only the identifier of the related resource
5+
6+
@createSchema
7+
@dropSchema
8+
Scenario: Create a set of Dummy with the alwaysIdentifier attributes and check the JSON formatted response
9+
Given there are AlwaysIdentifierDummies
10+
When I add "Accept" header equal to "application/json"
11+
And I add "Content-Type" header equal to "application/json"
12+
And I send a "GET" request to "/always_identifier_dummies/2"
13+
Then the JSON should be equal to:
14+
"""
15+
{
16+
"children": [
17+
"/always_identifier_dummies/1"
18+
],
19+
"related": [
20+
{
21+
"children": [],
22+
"related": [],
23+
"parent": null
24+
}
25+
],
26+
"parent": "/always_identifier_dummies/1"
27+
}
28+
"""
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Feature: allow resources in properties to always be serialized as identifiers
2+
In order to use a hypermedia API
3+
As a client software developer
4+
I need to be able to force to serialize only the identifier of the related resource
5+
6+
@createSchema
7+
@dropSchema
8+
Scenario: Create a set of Dummy with the alwaysIdentifier attributes and check the JSON-LD formatted response
9+
Given there are AlwaysIdentifierDummies
10+
When I add "Content-Type" header equal to "application/ld+json"
11+
And I send a "GET" request to "/always_identifier_dummies/2"
12+
Then the JSON should be equal to:
13+
"""
14+
{
15+
"@context": "/contexts/AlwaysIdentifierDummy",
16+
"@id": "/always_identifier_dummies/2",
17+
"@type": "AlwaysIdentifierDummy",
18+
"children": [
19+
"/always_identifier_dummies/1"
20+
],
21+
"related": [
22+
{
23+
"@id": "/always_identifier_dummies/1",
24+
"@type": "AlwaysIdentifierDummy",
25+
"children": [],
26+
"related": [],
27+
"parent": null
28+
}
29+
],
30+
"parent": "/always_identifier_dummies/1"
31+
}
32+
"""

src/Annotation/ApiProperty.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,9 @@ final class ApiProperty
6767
* @var array
6868
*/
6969
public $attributes = [];
70+
71+
/**
72+
* @var bool
73+
*/
74+
public $alwaysIdentifier;
7075
}

src/Metadata/Property/Factory/AnnotationPropertyMetadataFactory.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,14 @@ private function createMetadata(ApiProperty $annotation, PropertyMetadata $paren
117117
$annotation->identifier,
118118
$annotation->iri,
119119
null,
120-
$annotation->attributes
120+
$annotation->attributes,
121+
null,
122+
$annotation->alwaysIdentifier
121123
);
122124
}
123125

124126
$propertyMetadata = $parentPropertyMetadata;
125-
foreach ([['get', 'description'], ['is', 'readable'], ['is', 'writable'], ['is', 'readableLink'], ['is', 'writableLink'], ['is', 'required'], ['get', 'iri'], ['is', 'identifier'], ['get', 'attributes']] as $property) {
127+
foreach ([['get', 'description'], ['is', 'readable'], ['is', 'writable'], ['is', 'readableLink'], ['is', 'writableLink'], ['is', 'required'], ['get', 'iri'], ['is', 'identifier'], ['get', 'attributes'], ['is', 'alwaysIdentifier']] as $property) {
126128
if (null !== $value = $annotation->{$property[1]}) {
127129
$propertyMetadata = $this->createWith($propertyMetadata, $property, $value);
128130
}

src/Metadata/Property/PropertyMetadata.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ final class PropertyMetadata
3434
private $childInherited;
3535
private $attributes;
3636
private $subresource;
37+
private $alwaysIdentifier;
3738

38-
public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null)
39+
public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null, bool $alwaysIdentifier = null)
3940
{
4041
$this->type = $type;
4142
$this->description = $description;
@@ -49,6 +50,7 @@ public function __construct(Type $type = null, string $description = null, bool
4950
$this->childInherited = $childInherited;
5051
$this->attributes = $attributes;
5152
$this->subresource = $subresource;
53+
$this->alwaysIdentifier = $alwaysIdentifier;
5254
}
5355

5456
/**
@@ -381,4 +383,25 @@ public function withSubresource(SubresourceMetadata $subresource = null): self
381383

382384
return $metadata;
383385
}
386+
387+
/**
388+
* Returns a new instance with the given alwaysIdentifier flag.
389+
*/
390+
public function withAlwaysIdentifier(bool $alwaysIdentifier): self
391+
{
392+
$metadata = clone $this;
393+
$metadata->alwaysIdentifier = $alwaysIdentifier;
394+
395+
return $metadata;
396+
}
397+
398+
/**
399+
* Is the property should be serialized as identifier ?
400+
*
401+
* @return bool|null
402+
*/
403+
public function isAlwaysIdentifier()
404+
{
405+
return $this->alwaysIdentifier;
406+
}
384407
}

src/Serializer/AbstractItemNormalizer.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
3939
{
4040
use ContextTrait;
4141

42+
const ALWAYS_IDENTIFIER = 'always_identifier';
43+
4244
protected $propertyNameCollectionFactory;
4345
protected $propertyMetadataFactory;
4446
protected $iriConverter;
@@ -97,6 +99,10 @@ public function normalize($object, $format = null, array $context = [])
9799
$context['resources'][$resource] = $resource;
98100
}
99101

102+
if ($context[self::ALWAYS_IDENTIFIER] ?? false) {
103+
return $context['iri'] ?? $this->iriConverter->getIriFromItem($object);
104+
}
105+
100106
return parent::normalize($object, $format, $context);
101107
}
102108

@@ -397,6 +403,8 @@ protected function getAttributeValue($object, $attribute, $format = null, array
397403
{
398404
$propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context));
399405

406+
$context[self::ALWAYS_IDENTIFIER] = $propertyMetadata->isAlwaysIdentifier();
407+
400408
try {
401409
$attributeValue = $this->propertyAccessor->getValue($object, $attribute);
402410
} catch (NoSuchPropertyException $e) {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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\ApiProperty;
17+
use ApiPlatform\Core\Annotation\ApiResource;
18+
use Doctrine\Common\Collections\ArrayCollection;
19+
use Doctrine\ORM\Mapping as ORM;
20+
use Symfony\Component\Serializer\Annotation\Groups;
21+
22+
/**
23+
* AlwaysIdentifierDummy.
24+
*
25+
* @author Alexandre Delplace <[email protected]>
26+
*
27+
* @ApiResource(attributes={
28+
* "normalization_context"={"groups"={"alwaysIdentifierGroup"}}
29+
* })
30+
* @ORM\Entity
31+
*/
32+
class AlwaysIdentifierDummy
33+
{
34+
/**
35+
* @var int id
36+
*
37+
* @ORM\Column(type="integer")
38+
* @ORM\Id
39+
* @ORM\GeneratedValue(strategy="AUTO")
40+
*/
41+
private $id;
42+
43+
/**
44+
* @var ArrayCollection children dummies
45+
*
46+
* @ORM\ManyToMany(targetEntity="AlwaysIdentifierDummy")
47+
* @ApiProperty(alwaysIdentifier=true)
48+
* @Groups({"alwaysIdentifierGroup"})
49+
*/
50+
private $children;
51+
52+
/**
53+
* @var ArrayCollection related dummies
54+
*
55+
* @ORM\ManyToMany(targetEntity="AlwaysIdentifierDummy")
56+
* @Groups({"alwaysIdentifierGroup"})
57+
* @ORM\JoinTable(name="alwaysidentifierdummies_related")
58+
*/
59+
private $related;
60+
61+
/**
62+
* @var AlwaysIdentifierDummy parent dummy
63+
*
64+
* @ORM\ManyToOne(targetEntity="AlwaysIdentifierDummy")
65+
* @ApiProperty(alwaysIdentifier=true)
66+
* @Groups({"alwaysIdentifierGroup"})
67+
*/
68+
private $parent;
69+
70+
public function __construct()
71+
{
72+
$this->related = new ArrayCollection();
73+
$this->children = new ArrayCollection();
74+
}
75+
76+
/**
77+
* @return int
78+
*/
79+
public function getId()
80+
{
81+
return $this->id;
82+
}
83+
84+
/**
85+
* @param int $id
86+
*/
87+
public function setId($id)
88+
{
89+
$this->id = $id;
90+
}
91+
92+
/**
93+
* @return ArrayCollection
94+
*/
95+
public function getChildren()
96+
{
97+
return $this->children;
98+
}
99+
100+
/**
101+
* @param ArrayCollection $children
102+
*/
103+
public function setChildren($children)
104+
{
105+
$this->children = $children;
106+
}
107+
108+
/**
109+
* @return ArrayCollection
110+
*/
111+
public function getRelated()
112+
{
113+
return $this->related;
114+
}
115+
116+
/**
117+
* @param ArrayCollection $related
118+
*/
119+
public function setRelated($related)
120+
{
121+
$this->related = $related;
122+
}
123+
124+
/**
125+
* @return AlwaysIdentifierDummy
126+
*/
127+
public function getParent()
128+
{
129+
return $this->parent;
130+
}
131+
132+
/**
133+
* @param AlwaysIdentifierDummy $parent
134+
*/
135+
public function setParent($parent)
136+
{
137+
$this->parent = $parent;
138+
}
139+
}

0 commit comments

Comments
 (0)