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\JsonLd; |
||
15 | |||
16 | use ApiPlatform\Core\Api\UrlGeneratorInterface; |
||
17 | use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; |
||
18 | use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; |
||
19 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
||
20 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; |
||
21 | use ApiPlatform\Core\Util\ClassInfoTrait; |
||
22 | use Symfony\Component\Serializer\NameConverter\NameConverterInterface; |
||
23 | |||
24 | /** |
||
25 | * {@inheritdoc} |
||
26 | * |
||
27 | * @author Kévin Dunglas <[email protected]> |
||
28 | */ |
||
29 | final class ContextBuilder implements AnonymousContextBuilderInterface |
||
30 | { |
||
31 | use ClassInfoTrait; |
||
32 | |||
33 | public const FORMAT = 'jsonld'; |
||
34 | |||
35 | private $resourceNameCollectionFactory; |
||
36 | private $resourceMetadataFactory; |
||
37 | private $propertyNameCollectionFactory; |
||
38 | private $propertyMetadataFactory; |
||
39 | private $urlGenerator; |
||
40 | |||
41 | /** |
||
42 | * @var NameConverterInterface|null |
||
43 | */ |
||
44 | private $nameConverter; |
||
45 | |||
46 | public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, UrlGeneratorInterface $urlGenerator, NameConverterInterface $nameConverter = null) |
||
47 | { |
||
48 | $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; |
||
49 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||
50 | $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; |
||
51 | $this->propertyMetadataFactory = $propertyMetadataFactory; |
||
52 | $this->urlGenerator = $urlGenerator; |
||
53 | $this->nameConverter = $nameConverter; |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * {@inheritdoc} |
||
58 | */ |
||
59 | public function getBaseContext(int $referenceType = UrlGeneratorInterface::ABS_URL): array |
||
60 | { |
||
61 | return [ |
||
62 | '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#', |
||
63 | 'hydra' => self::HYDRA_NS, |
||
64 | ]; |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * {@inheritdoc} |
||
69 | */ |
||
70 | public function getEntrypointContext(int $referenceType = UrlGeneratorInterface::ABS_PATH): array |
||
71 | { |
||
72 | $context = $this->getBaseContext($referenceType); |
||
73 | |||
74 | foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { |
||
75 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
||
76 | |||
77 | $resourceName = lcfirst($resourceMetadata->getShortName()); |
||
78 | |||
79 | $context[$resourceName] = [ |
||
80 | '@id' => 'Entrypoint/'.$resourceName, |
||
81 | '@type' => '@id', |
||
82 | ]; |
||
83 | } |
||
84 | |||
85 | return $context; |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * {@inheritdoc} |
||
90 | */ |
||
91 | public function getResourceContext(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): array |
||
92 | { |
||
93 | $metadata = $this->resourceMetadataFactory->create($resourceClass); |
||
94 | if (null === $shortName = $metadata->getShortName()) { |
||
95 | return []; |
||
96 | } |
||
97 | |||
98 | return $this->getResourceContextWithShortname($resourceClass, $referenceType, $shortName); |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * {@inheritdoc} |
||
103 | */ |
||
104 | public function getResourceContextUri(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string |
||
105 | { |
||
106 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
||
107 | |||
108 | return $this->urlGenerator->generate('api_jsonld_context', ['shortName' => $resourceMetadata->getShortName()], $referenceType); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * {@inheritdoc} |
||
113 | */ |
||
114 | public function getAnonymousResourceContext($object, array $context = [], int $referenceType = UrlGeneratorInterface::ABS_PATH): array |
||
115 | { |
||
116 | $outputClass = $this->getObjectClass($object); |
||
117 | $shortName = (new \ReflectionClass($outputClass))->getShortName(); |
||
118 | |||
119 | $jsonLdContext = [ |
||
120 | '@context' => $this->getResourceContextWithShortname( |
||
121 | $outputClass, |
||
122 | $referenceType, |
||
123 | $shortName |
||
124 | ), |
||
125 | '@type' => $shortName, |
||
126 | '@id' => $context['iri'] ?? '_:'.(\function_exists('spl_object_id') ? spl_object_id($object) : spl_object_hash($object)), |
||
127 | ]; |
||
128 | |||
129 | // here the object can be different from the resource given by the $context['api_resource'] value |
||
130 | if (isset($context['api_resource'])) { |
||
131 | $jsonLdContext['@type'] = $this->resourceMetadataFactory->create($this->getObjectClass($context['api_resource']))->getShortName(); |
||
132 | } |
||
133 | |||
134 | return $jsonLdContext; |
||
135 | } |
||
136 | |||
137 | private function getResourceContextWithShortname(string $resourceClass, int $referenceType, string $shortName): array |
||
138 | { |
||
139 | $context = $this->getBaseContext($referenceType); |
||
140 | |||
141 | foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { |
||
142 | $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); |
||
143 | |||
144 | if ($propertyMetadata->isIdentifier() && true !== $propertyMetadata->isWritable()) { |
||
145 | continue; |
||
146 | } |
||
147 | |||
148 | $convertedName = $this->nameConverter ? $this->nameConverter->normalize($propertyName, $resourceClass, self::FORMAT) : $propertyName; |
||
0 ignored issues
–
show
|
|||
149 | $jsonldContext = $propertyMetadata->getAttributes()['jsonld_context'] ?? []; |
||
150 | |||
151 | if (!$id = $propertyMetadata->getIri()) { |
||
152 | $id = sprintf('%s/%s', $shortName, $convertedName); |
||
153 | } |
||
154 | |||
155 | if (false === $propertyMetadata->isReadableLink()) { |
||
156 | $jsonldContext += [ |
||
157 | '@id' => $id, |
||
158 | '@type' => '@id', |
||
159 | ]; |
||
160 | } |
||
161 | |||
162 | if (empty($jsonldContext)) { |
||
163 | $context[$convertedName] = $id; |
||
164 | } else { |
||
165 | $context[$convertedName] = $jsonldContext + [ |
||
166 | '@id' => $id, |
||
167 | ]; |
||
168 | } |
||
169 | } |
||
170 | |||
171 | return $context; |
||
172 | } |
||
173 | } |
||
174 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.