Completed
Pull Request — master (#904)
by Antoine
02:54
created

IriConverter::generateIdentifiersUrl()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
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\Bridge\Symfony\Routing;
15
16
use ApiPlatform\Core\Api\IriConverterInterface;
17
use ApiPlatform\Core\Api\OperationType;
18
use ApiPlatform\Core\Api\UrlGeneratorInterface;
19
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
20
use ApiPlatform\Core\Exception\InvalidArgumentException;
21
use ApiPlatform\Core\Exception\ItemNotFoundException;
22
use ApiPlatform\Core\Exception\RuntimeException;
23
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
24
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
25
use ApiPlatform\Core\Util\ClassInfoTrait;
26
use Symfony\Component\PropertyAccess\PropertyAccess;
27
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
28
use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
29
use Symfony\Component\Routing\RouterInterface;
30
31
/**
32
 * {@inheritdoc}
33
 *
34
 * @author Kévin Dunglas <[email protected]>
35
 */
36
final class IriConverter implements IriConverterInterface
37
{
38
    use ClassInfoTrait;
39
40
    private $propertyNameCollectionFactory;
41
    private $propertyMetadataFactory;
42
    private $itemDataProvider;
43
    private $routeNameResolver;
44
    private $router;
45
    private $propertyAccessor;
46
47
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ItemDataProviderInterface $itemDataProvider, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null)
48
    {
49
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
50
        $this->propertyMetadataFactory = $propertyMetadataFactory;
51
        $this->itemDataProvider = $itemDataProvider;
52
        $this->routeNameResolver = $routeNameResolver;
53
        $this->router = $router;
54
        $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function getItemFromIri(string $iri, array $context = [])
61
    {
62
        try {
63
            $parameters = $this->router->match($iri);
64
        } catch (RoutingExceptionInterface $e) {
65
            throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $e->getCode(), $e);
66
        }
67
68
        if (!isset($parameters['_api_resource_class'], $parameters['id'])) {
69
            throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri));
70
        }
71
72
        if ($item = $this->itemDataProvider->getItem($parameters['_api_resource_class'], $parameters['id'], null, $context)) {
73
            return $item;
74
        }
75
76
        throw new ItemNotFoundException(sprintf('Item not found for "%s".', $iri));
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
83
    {
84
        $resourceClass = $this->getObjectClass($item);
85
        $routeName = $this->routeNameResolver->getRouteName($resourceClass, false);
86
87
        $identifiers = $this->generateIdentifiersUrl($this->getIdentifiersFromItem($item));
88
89
        return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 View Code Duplication
    public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
96
    {
97
        try {
98
            return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::COLLECTION), [], $referenceType);
99
        } catch (RoutingExceptionInterface $e) {
100
            throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e);
101
        }
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107 View Code Duplication
    public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
    {
109
        try {
110
            return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::ITEM), $identifiers, $referenceType);
111
        } catch (RoutingExceptionInterface $e) {
112
            throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e);
113
        }
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 View Code Duplication
    public function getSubresourceIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
120
    {
121
        try {
122
            return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::SUBRESOURCE), $identifiers, $referenceType);
123
        } catch (RoutingExceptionInterface $e) {
124
            throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e);
125
        }
126
    }
127
128
    /**
129
     * Find identifiers from an Item (Object).
130
     *
131
     * @param object $item
132
     *
133
     * @throws RuntimeException
134
     *
135
     * @return array
136
     */
137
    private function getIdentifiersFromItem($item): array
138
    {
139
        $identifiers = [];
140
        $resourceClass = $this->getObjectClass($item);
141
142
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
143
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
144
145
            $identifier = $propertyMetadata->isIdentifier();
146
            if (null === $identifier || false === $identifier) {
147
                continue;
148
            }
149
150
            $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
151
152
            if (!is_object($identifiers[$propertyName])) {
153
                continue;
154
            }
155
156
            $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
157
            $relatedItem = $identifiers[$propertyName];
158
159
            unset($identifiers[$propertyName]);
160
161
            foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
162
                $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
163
164
                if ($propertyMetadata->isIdentifier()) {
165
                    if (isset($identifiers[$propertyName])) {
166
                        throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
167
                    }
168
169
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
170
                }
171
            }
172
173
            if (!isset($identifiers[$propertyName])) {
174
                throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
175
            }
176
        }
177
178
        return $identifiers;
179
    }
180
181
    /**
182
     * Generate the identifier url.
183
     *
184
     * @param array $identifiers
185
     *
186
     * @return string[]
187
     */
188
    private function generateIdentifiersUrl(array $identifiers): array
189
    {
190
        if (1 === count($identifiers)) {
191
            return [rawurlencode((string) array_values($identifiers)[0])];
192
        }
193
194
        foreach ($identifiers as $name => $value) {
195
            $identifiers[$name] = sprintf('%s=%s', $name, $value);
196
        }
197
198
        return $identifiers;
199
    }
200
}
201