Completed
Pull Request — master (#997)
by Antoine
03:21
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 Symfony\Component\PropertyAccess\PropertyAccess;
24
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
25
use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
26
use Symfony\Component\Routing\RouterInterface;
27
use Psr\Cache\CacheException;
28
use Psr\Cache\CacheItemPoolInterface;
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
    /**
97
     * {@inheritdoc}
98
     */
99
    public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
100
    {
101
        try {
102
            return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, true), [], $referenceType);
103
        } catch (RoutingExceptionInterface $e) {
104
            throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e);
105
        }
106
    }
107
108
    /**
109
     * Find identifiers from an Item (Object).
110
     *
111
     * @param object $item
112
     *
113
     * @throws RuntimeException
114
     *
115
     * @return array
116
     */
117
    private function getIdentifiersFromItem($item): array
118
    {
119
        $identifiers = [];
120
        $resourceClass = $this->getObjectClass($item);
121
122
        $cacheKey = self::CACHE_KEY_PREFIX.md5(serialize([$resourceClass]));
123
124
        try {
125
            $cacheItem = $this->cacheItemPool->getItem($cacheKey);
126
127
            if ($cacheItem->isHit()) {
128
                foreach ($cacheItem->get() as $propertyName) {
129
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
130
131
                    if (is_object($identifiers[$propertyName])) {
132
                        $relatedCacheKey = self::CACHE_KEY_PREFIX.md5(serialize($this->getObjectClass($identifiers[$propertyName])));
133
134
                        $cacheItem = $this->cacheItemPool->getItem($relatedCacheKey);
135
136
                        if (!$cacheItem->isHit()) {
137
                            throw new CacheException();
138
                        }
139
140
                        $identifiers[$propertyName] = $cacheItem->get()[0];
141
                    }
142
                }
143
144
                return $identifiers;
145
            }
146
        } catch (CacheException $e) {
147
            // do nothing
148
        }
149
150
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
151
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
152
153
            $identifier = $propertyMetadata->isIdentifier();
154
            if (null === $identifier || false === $identifier) {
155
                continue;
156
            }
157
158
            $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
159
160
            if (!is_object($identifiers[$propertyName])) {
161
                continue;
162
            }
163
164
            $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
165
            $relatedItem = $identifiers[$propertyName];
166
167
            unset($identifiers[$propertyName]);
168
169
            foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
170
                $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
171
172
                if ($propertyMetadata->isIdentifier()) {
173
                    if (isset($identifiers[$propertyName])) {
174
                        throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
175
                    }
176
177
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
178
                }
179
            }
180
181
            if (!isset($identifiers[$propertyName])) {
182
                throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
183
            }
184
        }
185
186
        if (isset($cacheItem)) {
187
            try {
188
                $cacheItem->set(array_keys($identifiers));
189
                $this->cacheItemPool->save($cacheItem);
190
            } catch (CacheException $e) {
191
                // do nothing
192
            }
193
        }
194
195
        return $identifiers;
196
    }
197
198
    /**
199
     * Generate the identifier url.
200
     *
201
     * @param array $identifiers
202
     *
203
     * @return string[]
204
     */
205
    private function generateIdentifiersUrl(array $identifiers): array
206
    {
207
        if (1 === count($identifiers)) {
208
            return [rawurlencode(array_values($identifiers)[0])];
209
        }
210
211
        foreach ($identifiers as $name => $value) {
212
            $identifiers[$name] = sprintf('%s=%s', $name, $value);
213
        }
214
215
        return $identifiers;
216
    }
217
}
218