Completed
Pull Request — master (#997)
by Antoine
03:23
created

CachedIriConverter   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 144
Duplicated Lines 37.5 %

Coupling/Cohesion

Components 2
Dependencies 13

Importance

Changes 0
Metric Value
wmc 21
lcom 2
cbo 13
dl 54
loc 144
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 10 10 2
A getItemFromIri() 0 4 1
A getIriFromItem() 9 9 1
F getIdentifiersFromItem() 35 85 16
A getIriFromResourceClass() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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\Exception\CacheException;
17
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
19
use ApiPlatform\Core\Util\ClassInfoTrait;
20
use Psr\Cache\CacheException as CacheExceptionInterface;
21
use Psr\Cache\CacheItemPoolInterface;
22
use Symfony\Component\PropertyAccess\PropertyAccess;
23
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
24
use Symfony\Component\Routing\RouterInterface;
25
26
/**
27
 * {@inheritdoc}
28
 *
29
 * @author Antoine Bluchet <[email protected]>
30
 */
31
final class CachedIriConverter implements IriConverterInterface
32
{
33
    use ClassInfoTrait;
34
    use IriConverterTrait;
35
36
    const CACHE_KEY_PREFIX = 'iri_converter';
37
38
    private $propertyNameCollectionFactory;
39
    private $propertyMetadataFactory;
40
    private $routeNameResolver;
41
    private $router;
42
    private $propertyAccessor;
43
    private $cacheItemPool;
44
    private $decorated;
45
46 View Code Duplication
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null, CacheItemPoolInterface $cacheItemPool, IriConverterInterface $decorated)
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...
47
    {
48
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
49
        $this->propertyMetadataFactory = $propertyMetadataFactory;
50
        $this->routeNameResolver = $routeNameResolver;
51
        $this->router = $router;
52
        $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
53
        $this->cacheItemPool = $cacheItemPool;
54
        $this->decorated = $decorated;
55
    }
56
57
    public function getItemFromIri(string $iri, array $context = [])
58
    {
59
        return $this->decorated->getItemFromIri($iri, $context);
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 View Code Duplication
    public function getIriFromItem($item, 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...
66
    {
67
        $resourceClass = $this->getObjectClass($item);
68
        $routeName = $this->routeNameResolver->getRouteName($resourceClass, false);
69
70
        $identifiers = $this->generateIdentifiersUrl($this->getIdentifiersFromItem($item));
71
72
        return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
73
    }
74
75
    /**
76
     * Find identifiers from an Item (Object).
77
     *
78
     * @param object $item
79
     *
80
     * @throws RuntimeException
81
     *
82
     * @return array
83
     */
84
    private function getIdentifiersFromItem($item): array
85
    {
86
        $identifiers = [];
87
        $resourceClass = $this->getObjectClass($item);
88
89
        $cacheKey = self::CACHE_KEY_PREFIX.md5($resourceClass);
90
91
        try {
92
            $cacheItem = $this->cacheItemPool->getItem($cacheKey);
93
94
            if ($cacheItem->isHit()) {
95
                foreach ($cacheItem->get() as $propertyName) {
96
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
97
98
                    if (is_object($identifiers[$propertyName])) {
99
                        $relatedCacheKey = self::CACHE_KEY_PREFIX.md5($this->getObjectClass($identifiers[$propertyName]));
100
101
                        $cacheItem = $this->cacheItemPool->getItem($relatedCacheKey);
102
103
                        if (!$cacheItem->isHit()) {
104
                            throw new CacheException('No relation cache item founded, we need more cache to continue.');
105
                        }
106
107
                        $relatedItem = $identifiers[$propertyName];
108
109
                        unset($identifiers[$propertyName]);
110
111
                        // Because composite identifiers aren't allowed in this case, we can use the first identifier only
112
                        $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $cacheItem->get()[0]);
113
                    }
114
                }
115
116
                return $identifiers;
117
            }
118
        } catch (CacheExceptionInterface $e) {
119
            // do nothing
120
        }
121
122 View Code Duplication
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
123
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
124
125
            $identifier = $propertyMetadata->isIdentifier();
126
            if (null === $identifier || false === $identifier) {
127
                continue;
128
            }
129
130
            $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
131
132
            if (!is_object($identifiers[$propertyName])) {
133
                continue;
134
            }
135
136
            $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
137
            $relatedItem = $identifiers[$propertyName];
138
139
            unset($identifiers[$propertyName]);
140
141
            foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
142
                $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
143
144
                if ($propertyMetadata->isIdentifier()) {
145
                    if (isset($identifiers[$propertyName])) {
146
                        throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
147
                    }
148
149
                    $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
150
                }
151
            }
152
153
            if (!isset($identifiers[$propertyName])) {
154
                throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass));
155
            }
156
        }
157
158
        if (isset($cacheItem)) {
159
            try {
160
                $cacheItem->set(array_keys($identifiers));
161
                $this->cacheItemPool->save($cacheItem);
162
            } catch (CacheExceptionInterface $e) {
163
                // do nothing
164
            }
165
        }
166
167
        return $identifiers;
168
    }
169
170
    public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
171
    {
172
        return $this->decorated->getIriFromResourceClass($resourceClass, $referenceType);
173
    }
174
}
175