Skip to content

Commit d2b657e

Browse files
meyerbaptistedunglas
authored andcommitted
Fallback attributes in the @ApiResource annotation (#1788)
* Fallback attributes in the @ApiResource annotation * Refactor to work with the PHP(Storm) Annotation plugin
1 parent 3ff4ddf commit d2b657e

File tree

5 files changed

+269
-22
lines changed

5 files changed

+269
-22
lines changed

src/Annotation/ApiResource.php

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,44 @@
1313

1414
namespace ApiPlatform\Core\Annotation;
1515

16+
use ApiPlatform\Core\Exception\InvalidArgumentException;
17+
use Doctrine\Common\Util\Inflector;
18+
1619
/**
1720
* ApiResource annotation.
1821
*
1922
* @author Kévin Dunglas <[email protected]>
2023
*
2124
* @Annotation
2225
* @Target({"CLASS"})
26+
* @Attributes(
27+
* @Attribute("accessControl", type="string"),
28+
* @Attribute("accessControlMessage", type="string"),
29+
* @Attribute("attributes", type="array"),
30+
* @Attribute("collectionOperations", type="array"),
31+
* @Attribute("denormalizationContext", type="array"),
32+
* @Attribute("description", type="string"),
33+
* @Attribute("fetchPartial", type="bool"),
34+
* @Attribute("forceEager", type="bool"),
35+
* @Attribute("filters", type="string[]"),
36+
* @Attribute("graphql", type="array"),
37+
* @Attribute("iri", type="string"),
38+
* @Attribute("itemOperations", type="array"),
39+
* @Attribute("maximumItemsPerPage", type="int"),
40+
* @Attribute("normalizationContext", type="array"),
41+
* @Attribute("order", type="array"),
42+
* @Attribute("paginationClientEnabled", type="bool"),
43+
* @Attribute("paginationClientItemsPerPage", type="bool"),
44+
* @Attribute("paginationClientPartial", type="bool"),
45+
* @Attribute("paginationEnabled", type="bool"),
46+
* @Attribute("paginationFetchJoinCollection", type="bool"),
47+
* @Attribute("paginationItemsPerPage", type="int"),
48+
* @Attribute("paginationPartial", type="bool"),
49+
* @Attribute("routePrefix", type="string"),
50+
* @Attribute("shortName", type="string"),
51+
* @Attribute("subresourceOperations", type="array"),
52+
* @Attribute("validationGroups", type="mixed")
53+
* )
2354
*/
2455
final class ApiResource
2556
{
@@ -62,4 +93,155 @@ final class ApiResource
6293
* @var array
6394
*/
6495
public $attributes = [];
96+
97+
/**
98+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
99+
*
100+
* @var string
101+
*/
102+
private $accessControl;
103+
104+
/**
105+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
106+
*
107+
* @var string
108+
*/
109+
private $accessControlMessage;
110+
111+
/**
112+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
113+
*
114+
* @var array
115+
*/
116+
private $denormalizationContext;
117+
118+
/**
119+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
120+
*
121+
* @var bool
122+
*/
123+
private $fetchPartial;
124+
125+
/**
126+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
127+
*
128+
* @var bool
129+
*/
130+
private $forceEager;
131+
132+
/**
133+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
134+
*
135+
* @var string[]
136+
*/
137+
private $filters;
138+
139+
/**
140+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
141+
*
142+
* @var int
143+
*/
144+
private $maximumItemsPerPage;
145+
146+
/**
147+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
148+
*
149+
* @var array
150+
*/
151+
private $normalizationContext;
152+
153+
/**
154+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
155+
*
156+
* @var array
157+
*/
158+
private $order;
159+
160+
/**
161+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
162+
*
163+
* @var bool
164+
*/
165+
private $paginationClientEnabled;
166+
167+
/**
168+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
169+
*
170+
* @var bool
171+
*/
172+
private $paginationClientItemsPerPage;
173+
174+
/**
175+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
176+
*
177+
* @var bool
178+
*/
179+
private $paginationClientPartial;
180+
181+
/**
182+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
183+
*
184+
* @var bool
185+
*/
186+
private $paginationEnabled;
187+
188+
/**
189+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
190+
*
191+
* @var bool
192+
*/
193+
private $paginationFetchJoinCollection;
194+
195+
/**
196+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
197+
*
198+
* @var int
199+
*/
200+
private $paginationItemsPerPage;
201+
202+
/**
203+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
204+
*
205+
* @var int
206+
*/
207+
private $paginationPartial;
208+
209+
/**
210+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
211+
*
212+
* @var string
213+
*/
214+
private $routePrefix;
215+
216+
/**
217+
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
218+
*
219+
* @var mixed
220+
*/
221+
private $validationGroups;
222+
223+
/**
224+
* @throws InvalidArgumentException
225+
*/
226+
public function __construct(array $values = [])
227+
{
228+
if (isset($values['attributes'])) {
229+
$this->attributes = $values['attributes'];
230+
unset($values['attributes']);
231+
}
232+
233+
foreach ($values as $key => $value) {
234+
if (!property_exists($this, $key)) {
235+
throw new InvalidArgumentException(sprintf('Unknown property "%s" on annotation "%s".', $key, self::class));
236+
}
237+
238+
$property = new \ReflectionProperty($this, $key);
239+
240+
if ($property->isPublic()) {
241+
$this->$key = $value;
242+
} else {
243+
$this->attributes += [Inflector::tableize($key) => $value];
244+
}
245+
}
246+
}
65247
}

tests/Annotation/ApiResourceTest.php

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Tests\Annotation;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Tests\Fixtures\AnnotatedClass;
1718
use Doctrine\Common\Annotations\AnnotationReader;
1819
use PHPUnit\Framework\TestCase;
1920

@@ -22,23 +23,65 @@
2223
*/
2324
class ApiResourceTest extends TestCase
2425
{
25-
public function testAssignation()
26+
public function testConstruct()
2627
{
27-
$resource = new ApiResource();
28-
$resource->shortName = 'shortName';
29-
$resource->description = 'description';
30-
$resource->iri = 'http://example.com/res';
31-
$resource->itemOperations = ['foo' => ['bar']];
32-
$resource->collectionOperations = ['bar' => ['foo']];
33-
$resource->graphql = ['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]];
34-
$resource->attributes = ['foo' => 'bar'];
28+
$resource = new ApiResource([
29+
'accessControl' => 'has_role("ROLE_FOO")',
30+
'accessControlMessage' => 'You are not foo.',
31+
'attributes' => ['foo' => 'bar', 'validation_groups' => ['baz', 'qux']],
32+
'collectionOperations' => ['bar' => ['foo']],
33+
'denormalizationContext' => ['groups' => ['foo']],
34+
'description' => 'description',
35+
'fetchPartial' => true,
36+
'forceEager' => false,
37+
'filters' => ['foo', 'bar'],
38+
'graphql' => ['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]],
39+
'iri' => 'http://example.com/res',
40+
'itemOperations' => ['foo' => ['bar']],
41+
'maximumItemsPerPage' => 42,
42+
'normalizationContext' => ['groups' => ['bar']],
43+
'order' => ['foo', 'bar' => 'ASC'],
44+
'paginationClientEnabled' => true,
45+
'paginationClientItemsPerPage' => true,
46+
'paginationClientPartial' => true,
47+
'paginationEnabled' => true,
48+
'paginationFetchJoinCollection' => true,
49+
'paginationItemsPerPage' => 42,
50+
'paginationPartial' => true,
51+
'routePrefix' => '/foo',
52+
'shortName' => 'shortName',
53+
'subresourceOperations' => [],
54+
'validationGroups' => ['foo', 'bar'],
55+
]);
3556

3657
$this->assertSame('shortName', $resource->shortName);
3758
$this->assertSame('description', $resource->description);
3859
$this->assertSame('http://example.com/res', $resource->iri);
60+
$this->assertSame(['foo' => ['bar']], $resource->itemOperations);
3961
$this->assertSame(['bar' => ['foo']], $resource->collectionOperations);
62+
$this->assertSame([], $resource->subresourceOperations);
4063
$this->assertSame(['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], $resource->graphql);
41-
$this->assertSame(['foo' => 'bar'], $resource->attributes);
64+
$this->assertEquals([
65+
'access_control' => 'has_role("ROLE_FOO")',
66+
'access_control_message' => 'You are not foo.',
67+
'denormalization_context' => ['groups' => ['foo']],
68+
'fetch_partial' => true,
69+
'foo' => 'bar',
70+
'force_eager' => false,
71+
'filters' => ['foo', 'bar'],
72+
'maximum_items_per_page' => 42,
73+
'normalization_context' => ['groups' => ['bar']],
74+
'order' => ['foo', 'bar' => 'ASC'],
75+
'pagination_client_enabled' => true,
76+
'pagination_client_items_per_page' => true,
77+
'pagination_client_partial' => true,
78+
'pagination_enabled' => true,
79+
'pagination_fetch_join_collection' => true,
80+
'pagination_items_per_page' => 42,
81+
'pagination_partial' => true,
82+
'route_prefix' => '/foo',
83+
'validation_groups' => ['baz', 'qux'],
84+
], $resource->attributes);
4285
}
4386

4487
public function testApiResourceAnnotation()
@@ -51,6 +94,24 @@ public function testApiResourceAnnotation()
5194
$this->assertSame('http://example.com/res', $resource->iri);
5295
$this->assertSame(['bar' => ['foo']], $resource->collectionOperations);
5396
$this->assertSame(['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], $resource->graphql);
54-
$this->assertSame(['foo' => 'bar', 'route_prefix' => '/whatever'], $resource->attributes);
97+
$this->assertEquals([
98+
'foo' => 'bar',
99+
'route_prefix' => '/whatever',
100+
'access_control' => "has_role('ROLE_FOO')",
101+
'access_control_message' => 'You are not foo.',
102+
], $resource->attributes);
103+
}
104+
105+
/**
106+
* @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
107+
* @expectedExceptionMessage Unknown property "invalidAttribute" on annotation "ApiPlatform\Core\Annotation\ApiResource".
108+
*/
109+
public function testConstructWithInvalidAttribute()
110+
{
111+
new ApiResource([
112+
'shortName' => 'shortName',
113+
'routePrefix' => '/foo',
114+
'invalidAttribute' => 'exception',
115+
]);
55116
}
56117
}

tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function testProcess()
5858

5959
$reader->getClassAnnotations(Argument::type(\ReflectionClass::class))->will(function ($args) {
6060
if (Dummy::class === $args[0]->name) {
61-
return [new ApiFilter(['value' => SearchFilter::class, 'strategy' => 'exact', 'properties' => ['description', 'relatedDummy.name', 'name']]), new ApiResource(), new ApiFilter(['value' => GroupFilter::class, 'arguments' => ['parameterName' => 'foobar']])];
61+
return [new ApiFilter(['value' => SearchFilter::class, 'strategy' => 'exact', 'properties' => ['description', 'relatedDummy.name', 'name']]), new ApiResource([]), new ApiFilter(['value' => GroupFilter::class, 'arguments' => ['parameterName' => 'foobar']])];
6262
}
6363

6464
return [];

tests/Annotation/AnnotatedClass.php renamed to tests/Fixtures/AnnotatedClass.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
declare(strict_types=1);
1313

14-
namespace ApiPlatform\Core\Tests\Annotation;
14+
namespace ApiPlatform\Core\Tests\Fixtures;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
1717

@@ -24,6 +24,9 @@
2424
* collectionOperations={"bar"={"foo"}},
2525
* graphql={"query"={"normalization_context"={"groups"={"foo", "bar"}}}},
2626
* attributes={"foo"="bar", "route_prefix"="/whatever"},
27+
* routePrefix="/foo",
28+
* accessControl="has_role('ROLE_FOO')",
29+
* accessControlMessage="You are not foo."
2730
* )
2831
*
2932
* @author Marcus Speight <[email protected]>

tests/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,16 @@ public function testCreate(ProphecyInterface $reader, ProphecyInterface $decorat
4949

5050
public function getCreateDependencies()
5151
{
52-
$annotation = new ApiResource();
53-
$annotation->shortName = 'shortName';
54-
$annotation->description = 'description';
55-
$annotation->iri = 'http://example.com';
56-
$annotation->itemOperations = ['foo' => ['bar' => true]];
57-
$annotation->collectionOperations = ['baz' => ['tab' => false]];
58-
$annotation->subresourceOperations = ['sub' => ['bus' => false]];
59-
$annotation->attributes = ['a' => 1, 'route_prefix' => '/foobar'];
60-
$annotation->graphql = ['foo' => 'bar'];
52+
$annotation = new ApiResource([
53+
'shortName' => 'shortName',
54+
'description' => 'description',
55+
'iri' => 'http://example.com',
56+
'itemOperations' => ['foo' => ['bar' => true]],
57+
'collectionOperations' => ['baz' => ['tab' => false]],
58+
'subresourceOperations' => ['sub' => ['bus' => false]],
59+
'attributes' => ['a' => 1, 'route_prefix' => '/foobar'],
60+
'graphql' => ['foo' => 'bar'],
61+
]);
6162

6263
$reader = $this->prophesize(Reader::class);
6364
$reader->getClassAnnotation(Argument::type(\ReflectionClass::class), ApiResource::class)->willReturn($annotation)->shouldBeCalled();

0 commit comments

Comments
 (0)