Skip to content

Commit 9d5a6b1

Browse files
committed
restore property metadata?
1 parent 7ae769c commit 9d5a6b1

File tree

7 files changed

+540
-3
lines changed

7 files changed

+540
-3
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Cache\CachedTrait;
17+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
18+
use Psr\Cache\CacheItemPoolInterface;
19+
20+
/**
21+
* Caches property metadata.
22+
*
23+
* @author Teoh Han Hui <[email protected]>
24+
*/
25+
final class CachedPropertyMetadataFactory implements PropertyMetadataFactoryInterface
26+
{
27+
use CachedTrait;
28+
29+
public const CACHE_KEY_PREFIX = 'property_metadata_';
30+
31+
private $decorated;
32+
33+
public function __construct(CacheItemPoolInterface $cacheItemPool, PropertyMetadataFactoryInterface $decorated)
34+
{
35+
$this->cacheItemPool = $cacheItemPool;
36+
$this->decorated = $decorated;
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
43+
{
44+
$cacheKey = self::CACHE_KEY_PREFIX.md5(serialize([$resourceClass, $property, $options]));
45+
46+
return $this->getCached($cacheKey, function () use ($resourceClass, $property, $options) {
47+
return $this->decorated->create($resourceClass, $property, $options);
48+
});
49+
}
50+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Exception\PropertyNotFoundException;
17+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
18+
19+
/**
20+
* Populates defaults values of the ressource properties using the default PHP values of properties.
21+
*/
22+
final class DefaultPropertyMetadataFactory implements PropertyMetadataFactoryInterface
23+
{
24+
private $decorated;
25+
26+
public function __construct(PropertyMetadataFactoryInterface $decorated = null)
27+
{
28+
$this->decorated = $decorated;
29+
}
30+
31+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
32+
{
33+
if (null === $this->decorated) {
34+
$propertyMetadata = new PropertyMetadata();
35+
} else {
36+
try {
37+
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
38+
} catch (PropertyNotFoundException $propertyNotFoundException) {
39+
$propertyMetadata = new PropertyMetadata();
40+
}
41+
}
42+
43+
try {
44+
$reflectionClass = new \ReflectionClass($resourceClass);
45+
} catch (\ReflectionException $reflectionException) {
46+
return $propertyMetadata;
47+
}
48+
49+
$defaultProperties = $reflectionClass->getDefaultProperties();
50+
51+
if (!\array_key_exists($property, $defaultProperties) || null === ($defaultProperty = $defaultProperties[$property])) {
52+
return $propertyMetadata;
53+
}
54+
55+
return $propertyMetadata->withDefault($defaultProperty);
56+
}
57+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Exception\PropertyNotFoundException;
17+
use ApiPlatform\Core\Metadata\Extractor\ExtractorInterface;
18+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
19+
use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
20+
use Symfony\Component\PropertyInfo\Type;
21+
22+
/**
23+
* Creates properties's metadata using an extractor.
24+
*
25+
* @author Kévin Dunglas <[email protected]>
26+
*/
27+
final class ExtractorPropertyMetadataFactory implements PropertyMetadataFactoryInterface
28+
{
29+
private $extractor;
30+
private $decorated;
31+
32+
public function __construct(ExtractorInterface $extractor, PropertyMetadataFactoryInterface $decorated = null)
33+
{
34+
$this->extractor = $extractor;
35+
$this->decorated = $decorated;
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
42+
{
43+
$parentPropertyMetadata = null;
44+
if ($this->decorated) {
45+
try {
46+
$parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
47+
} catch (PropertyNotFoundException $propertyNotFoundException) {
48+
// Ignore not found exception from decorated factories
49+
}
50+
}
51+
52+
$isInterface = interface_exists($resourceClass);
53+
54+
if (
55+
!property_exists($resourceClass, $property) && !$isInterface ||
56+
null === ($propertyMetadata = $this->extractor->getResources()[$resourceClass]['properties'][$property] ?? null)
57+
) {
58+
return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
59+
}
60+
61+
if ($parentPropertyMetadata) {
62+
return $this->update($parentPropertyMetadata, $propertyMetadata);
63+
}
64+
65+
return ($metadata = new PropertyMetadata(
66+
null,
67+
$propertyMetadata['description'],
68+
$propertyMetadata['readable'],
69+
$propertyMetadata['writable'],
70+
$propertyMetadata['readableLink'],
71+
$propertyMetadata['writableLink'],
72+
$propertyMetadata['required'],
73+
$propertyMetadata['identifier'],
74+
$propertyMetadata['iri'],
75+
null,
76+
$propertyMetadata['attributes']
77+
))->withSubresource($this->createSubresourceMetadata($propertyMetadata['subresource'], $metadata));
78+
}
79+
80+
/**
81+
* Returns the metadata from the decorated factory if available or throws an exception.
82+
*
83+
* @throws PropertyNotFoundException
84+
*/
85+
private function handleNotFound(?PropertyMetadata $parentPropertyMetadata, string $resourceClass, string $property): PropertyMetadata
86+
{
87+
if ($parentPropertyMetadata) {
88+
return $parentPropertyMetadata;
89+
}
90+
91+
throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
92+
}
93+
94+
/**
95+
* Creates a new instance of metadata if the property is not already set.
96+
*/
97+
private function update(PropertyMetadata $propertyMetadata, array $metadata): PropertyMetadata
98+
{
99+
$metadataAccessors = [
100+
'description' => 'get',
101+
'readable' => 'is',
102+
'writable' => 'is',
103+
'writableLink' => 'is',
104+
'readableLink' => 'is',
105+
'required' => 'is',
106+
'identifier' => 'is',
107+
'iri' => 'get',
108+
'attributes' => 'get',
109+
];
110+
111+
foreach ($metadataAccessors as $metadataKey => $accessorPrefix) {
112+
if (null === $metadata[$metadataKey]) {
113+
continue;
114+
}
115+
116+
$propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]);
117+
}
118+
119+
if ($propertyMetadata->hasSubresource()) {
120+
return $propertyMetadata;
121+
}
122+
123+
return $propertyMetadata->withSubresource($this->createSubresourceMetadata($metadata['subresource'], $propertyMetadata));
124+
}
125+
126+
/**
127+
* Creates a SubresourceMetadata.
128+
*
129+
* @param bool|array|null $subresource the subresource metadata coming from XML or YAML
130+
* @param PropertyMetadata $propertyMetadata the current property metadata
131+
*/
132+
private function createSubresourceMetadata($subresource, PropertyMetadata $propertyMetadata): ?SubresourceMetadata
133+
{
134+
if (!$subresource) {
135+
return null;
136+
}
137+
138+
$type = $propertyMetadata->getType();
139+
$maxDepth = \is_array($subresource) ? $subresource['maxDepth'] ?? null : null;
140+
141+
if (null !== $type) {
142+
$isCollection = $type->isCollection();
143+
if (
144+
$isCollection &&
145+
$collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()
146+
) {
147+
$resourceClass = $collectionValueType->getClassName();
148+
} else {
149+
$resourceClass = $type->getClassName();
150+
}
151+
} elseif (\is_array($subresource) && isset($subresource['resourceClass'])) {
152+
$resourceClass = $subresource['resourceClass'];
153+
$isCollection = $subresource['collection'] ?? true;
154+
} else {
155+
return null;
156+
}
157+
158+
return new SubresourceMetadata($resourceClass, $isCollection, $maxDepth);
159+
}
160+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Exception\PropertyNotFoundException;
17+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
18+
19+
/**
20+
* Creates a property metadata value object.
21+
*
22+
* @author Kévin Dunglas <[email protected]>
23+
*/
24+
interface PropertyMetadataFactoryInterface
25+
{
26+
/**
27+
* Creates a property metadata.
28+
*
29+
* @throws PropertyNotFoundException
30+
*/
31+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata;
32+
}

0 commit comments

Comments
 (0)