Passed
Push — master ( 8bd912...d93388 )
by Alan
06:58 queued 02:20
created

src/JsonLd/ContextBuilder.php (1 issue)

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
The call to Symfony\Component\Serial...rInterface::normalize() has too many arguments starting with $resourceClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
            $convertedName = $this->nameConverter ? $this->nameConverter->/** @scrutinizer ignore-call */ normalize($propertyName, $resourceClass, self::FORMAT) : $propertyName;

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.

Loading history...
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