Completed
Pull Request — master (#927)
by Kévin
02:57
created

IriConverter::getIdentifiersFromItem()   C

Complexity

Conditions 10
Paths 12

Size

Total Lines 48
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 48
rs 5.3454
c 0
b 0
f 0
cc 10
eloc 27
nc 12
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
namespace ApiPlatform\Core\Bridge\Symfony\Routing;
13
14
use ApiPlatform\Core\Api\IriConverterInterface;
15
use ApiPlatform\Core\Api\UrlGeneratorInterface;
16
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
17
use ApiPlatform\Core\Exception\InvalidArgumentException;
18
use ApiPlatform\Core\Exception\ItemNotFoundException;
19
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
20
use ApiPlatform\Core\Exception\RuntimeException;
21
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
22
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
23
use ApiPlatform\Core\Util\ClassInfoTrait;
24
use Symfony\Component\PropertyAccess\PropertyAccess;
25
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
26
use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
27
use Symfony\Component\Routing\RouterInterface;
28
29
/**
30
 * {@inheritdoc}
31
 *
32
 * @author Kévin Dunglas <[email protected]>
33
 */
34
final class IriConverter implements IriConverterInterface
35
{
36
    use ClassInfoTrait;
37
38
    private $propertyNameCollectionFactory;
39
    private $propertyMetadataFactory;
40
    private $itemDataProvider;
41
    private $routeNameResolver;
42
    private $router;
43
    private $propertyAccessor;
44
45
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ItemDataProviderInterface $itemDataProvider, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null)
46
    {
47
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
48
        $this->propertyMetadataFactory = $propertyMetadataFactory;
49
        $this->itemDataProvider = $itemDataProvider;
50
        $this->routeNameResolver = $routeNameResolver;
51
        $this->router = $router;
52
        $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function getItemFromIri(string $iri, array $context = [])
59
    {
60
        try {
61
            $parameters = $this->router->match($iri);
62
        } catch (RoutingExceptionInterface $e) {
63
            throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $e->getCode(), $e);
64
        }
65
66
        if (!isset($parameters['_api_resource_class'], $parameters['id'])) {
67
            throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri));
68
        }
69
70
        if ($item = $this->itemDataProvider->getItem($parameters['_api_resource_class'], $parameters['id'], null, $context)) {
71
            return $item;
72
        }
73
74
        throw new ItemNotFoundException(sprintf('Item not found for "%s".', $iri));
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
81
    {
82
        $resourceClass = $this->getObjectClass($item);
83
        $routeName = $this->routeNameResolver->getRouteName($resourceClass, false);
84
85
        $identifiers = $this->generateIdentifiersUrl($this->getIdentifiersFromItem($item));
86
87
        return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
94
    {
95
        try {
96
            return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, true), [], $referenceType);
97
        } catch (RoutingExceptionInterface $e) {
98
            throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e);
99
        }
100
    }
101
102
    /**
103
     * Find identifiers from an Item (Object).
104
     *
105
     * @param object $item
106
     *
107
     * @throws RuntimeException
108
     *
109
     * @return array
110
     */
111
    private function getIdentifiersFromItem($item): array
112
    {
113
        $identifiers = [];
114
        $resourceClass = $this->getObjectClass($item);
115
116
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
117
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
118
119
            $identifier = $propertyMetadata->isIdentifier();
120
            if (null === $identifier || false === $identifier) {
121
                continue;
122
            }
123
124
            $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
125
126
            if (!is_object($identifiers[$propertyName])) {
127
                continue;
128
            }
129
130
            $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
131
            $relatedItem = $identifiers[$propertyName];
132
            unset($identifiers[$propertyName]);
133
134
            foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
135
                try {
136
                    $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
137
                } catch (ResourceClassNotFoundException $e) {
138
                    $identifiers[$propertyName] = (string) $relatedItem;
139
140
                    continue;
141
                }
142
143
                if ($propertyMetadata->isIdentifier()) {
144
                    if (isset($identifiers[$propertyName])) {
145
                        throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
146
                    }
147
148
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
149
                }
150
            }
151
152
            if (!isset($identifiers[$propertyName])) {
153
                throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
154
            }
155
        }
156
157
        return $identifiers;
158
    }
159
160
    /**
161
     * Generate the identifier url.
162
     *
163
     * @param array $identifiers
164
     *
165
     * @return string[]
166
     */
167
    private function generateIdentifiersUrl(array $identifiers): array
168
    {
169
        if (1 === count($identifiers)) {
170
            return [rawurlencode(array_values($identifiers)[0])];
171
        }
172
173
        foreach ($identifiers as $name => $value) {
174
            $identifiers[$name] = sprintf('%s=%s', $name, $value);
175
        }
176
177
        return $identifiers;
178
    }
179
}
180