Skip to content

Commit 4c29bf8

Browse files
authored
refactor(metadata): provide a trait in addition to the attribute (#6558)
Co-authored-by: Kévin Dunglas <[email protected]> - provides an IsApiResource trait that can be used in place of the #[ApiResource] attribute - enables #[ApiProperty(property: 'foo')] on a class
1 parent db1241c commit 4c29bf8

17 files changed

+520
-210
lines changed

src/Metadata/ApiProperty.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,27 @@ public function __construct(
202202
private $iris = null,
203203
private ?bool $genId = null,
204204
private ?string $uriTemplate = null,
205+
private ?string $property = null,
205206
private array $extraProperties = [],
206207
) {
207208
if (\is_string($types)) {
208209
$this->types = (array) $types;
209210
}
210211
}
211212

213+
public function getProperty(): ?string
214+
{
215+
return $this->property;
216+
}
217+
218+
public function withProperty(string $property): self
219+
{
220+
$self = clone $this;
221+
$self->property = $property;
222+
223+
return $self;
224+
}
225+
212226
public function getDescription(): ?string
213227
{
214228
return $this->description;

src/Metadata/ApiResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*
2323
* The API Resource attribute declares the behaviors attached to a Resource inside API Platform.
2424
* This class is immutable, and if you set a value yourself, API Platform will not override the value.
25-
* The API Resource helps sharing options with operations.
25+
* The API Resource helps to share options with operations.
2626
*
2727
* Read more about how metadata works [here](/docs/in-depth/metadata).
2828
*

src/Metadata/Extractor/XmlPropertyExtractor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ protected function extractPath(string $path): void
7373
'iris' => $this->buildArrayValue($property, 'iri'),
7474
'genId' => $this->phpize($property, 'genId', 'bool'),
7575
'uriTemplate' => $this->phpize($property, 'uriTemplate', 'string'),
76+
'property' => $this->phpize($property, 'property', 'string'),
7677
];
7778
}
7879
}

src/Metadata/Extractor/YamlPropertyExtractor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private function buildProperties(array $resourcesYaml): void
9494
'schema' => $this->buildAttribute($propertyValues, 'schema'),
9595
'genId' => $this->phpize($propertyValues, 'genId', 'bool'),
9696
'uriTemplate' => $this->phpize($propertyValues, 'uriTemplate', 'string'),
97+
'property' => $this->phpize($propertyValues, 'property', 'string'),
9798
];
9899
}
99100
}

src/Metadata/Extractor/schema/properties.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<xsd:attribute name="securityPostDenormalize" type="xsd:string"/>
4545
<xsd:attribute name="initializable" type="xsd:boolean"/>
4646
<xsd:attribute name="genId" type="xsd:boolean"/>
47+
<xsd:attribute name="property" type="xsd:string"/>
4748
<xsd:attribute name="uriTemplate" type="xsd:string"/>
4849
</xsd:complexType>
4950

src/Metadata/IsApiResource.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Metadata;
15+
16+
/**
17+
* @author Kévin Dunglas <[email protected]>
18+
*/
19+
trait IsApiResource
20+
{
21+
public static function apiResource(): ApiResource
22+
{
23+
return new ApiResource();
24+
}
25+
}

src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ public function create(string $resourceClass, string $property, array $options =
8989
}
9090
}
9191

92+
$attributes = $reflectionClass->getAttributes(ApiProperty::class);
93+
foreach ($attributes as $attribute) {
94+
$instance = $attribute->newInstance();
95+
if ($instance->getProperty() === $property) {
96+
return $this->createMetadata($instance, $parentPropertyMetadata);
97+
}
98+
}
99+
92100
return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
93101
}
94102

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Property\PropertyNameCollection;
18+
19+
/**
20+
* Gathers "virtual" properties created using the ApiProperty attribute at class level.
21+
*/
22+
final class ClassLevelAttributePropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
23+
{
24+
public function __construct(
25+
private readonly ?PropertyNameCollectionFactoryInterface $decorated = null,
26+
) {
27+
}
28+
29+
public function create(string $resourceClass, array $options = []): PropertyNameCollection
30+
{
31+
$parentPropertyNameCollection = $this->decorated?->create($resourceClass, $options);
32+
if (!class_exists($resourceClass)) {
33+
return $parentPropertyNameCollection ?? new PropertyNameCollection();
34+
}
35+
36+
$properties = $parentPropertyNameCollection ? iterator_to_array($parentPropertyNameCollection) : [];
37+
38+
$refl = new \ReflectionClass($resourceClass);
39+
$attributes = $refl->getAttributes(ApiProperty::class);
40+
foreach ($attributes as $attribute) {
41+
$instance = $attribute->newInstance();
42+
if ($property = $instance->getProperty()) {
43+
$properties[] = $property;
44+
}
45+
}
46+
47+
return new PropertyNameCollection($properties);
48+
}
49+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\IsApiResource;
18+
use ApiPlatform\Metadata\Property\PropertyNameCollection;
19+
20+
/**
21+
* Handles property defined with the {@see IsApiResource} concern.
22+
*
23+
* @author Kévin Dunglas <[email protected]>
24+
*/
25+
final class ConcernsPropertyNameCollectionMetadataFactory implements PropertyNameCollectionFactoryInterface
26+
{
27+
public function __construct(
28+
private readonly ?PropertyNameCollectionFactoryInterface $decorated = null,
29+
) {
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*
35+
* @param class-string $resourceClass
36+
*/
37+
public function create(string $resourceClass, array $options = []): PropertyNameCollection
38+
{
39+
$propertyNameCollection = $this->decorated?->create($resourceClass, $options);
40+
if (!method_exists($resourceClass, 'apiResource')) {
41+
return $propertyNameCollection ?? new PropertyNameCollection();
42+
}
43+
44+
$refl = new \ReflectionClass($resourceClass);
45+
$method = $refl->getMethod('apiResource');
46+
if (!$method->isPublic() || !$method->isStatic()) {
47+
return $propertyNameCollection ?? new PropertyNameCollection();
48+
}
49+
50+
$metadataCollection = $method->invoke(null);
51+
if (!\is_array($metadataCollection)) {
52+
$metadataCollection = [$metadataCollection];
53+
}
54+
55+
$properties = $propertyNameCollection ? array_flip(iterator_to_array($propertyNameCollection)) : [];
56+
57+
foreach ($metadataCollection as $apiProperty) {
58+
if (!$apiProperty instanceof ApiProperty) {
59+
continue;
60+
}
61+
62+
if (null !== $propertyName = $apiProperty->getProperty()) {
63+
$properties[$propertyName] = true;
64+
}
65+
}
66+
67+
return new PropertyNameCollection(array_keys($properties));
68+
}
69+
}

0 commit comments

Comments
 (0)