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

IriConverter::getIdentifiersFromItem()   D

Complexity

Conditions 9
Paths 10

Size

Total Lines 43
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 4.909
c 0
b 0
f 0
cc 9
eloc 23
nc 10
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 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...
108
    {
109
        try {
110
            return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::SUBRESOURCE), $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
     * Find identifiers from an Item (Object).
118
     *
119
     * @param object $item
120
     *
121
     * @throws RuntimeException
122
     *
123
     * @return array
124
     */
125
    private function getIdentifiersFromItem($item): array
126
    {
127
        $identifiers = [];
128
        $resourceClass = $this->getObjectClass($item);
129
130
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
131
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
132
133
            $identifier = $propertyMetadata->isIdentifier();
134
            if (null === $identifier || false === $identifier) {
135
                continue;
136
            }
137
138
            $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
139
140
            if (!is_object($identifiers[$propertyName])) {
141
                continue;
142
            }
143
144
            $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
145
            $relatedItem = $identifiers[$propertyName];
146
147
            unset($identifiers[$propertyName]);
148
149
            foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
150
                $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
151
152
                if ($propertyMetadata->isIdentifier()) {
153
                    if (isset($identifiers[$propertyName])) {
154
                        throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
155
                    }
156
157
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
158
                }
159
            }
160
161
            if (!isset($identifiers[$propertyName])) {
162
                throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
163
            }
164
        }
165
166
        return $identifiers;
167
    }
168
169
    /**
170
     * Generate the identifier url.
171
     *
172
     * @param array $identifiers
173
     *
174
     * @return string[]
175
     */
176
    private function generateIdentifiersUrl(array $identifiers): array
177
    {
178
        if (1 === count($identifiers)) {
179
            return [rawurlencode((string) array_values($identifiers)[0])];
180
        }
181
182
        foreach ($identifiers as $name => $value) {
183
            $identifiers[$name] = sprintf('%s=%s', $name, $value);
184
        }
185
186
        return $identifiers;
187
    }
188
}
189