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