Completed
Pull Request — master (#997)
by Antoine
05:39 queued 02:16
created

IriConverter::getIdentifiersFromItem()   F

Complexity

Conditions 16
Paths 369

Size

Total Lines 80
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 80
rs 3.8237
c 0
b 0
f 0
cc 16
eloc 42
nc 369
nop 1

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