• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 16531587208

25 Jul 2025 09:05PM UTC coverage: 0.0% (-22.1%) from 22.07%
16531587208

Pull #7225

github

web-flow
Merge 23f449a58 into 02a764950
Pull Request #7225: feat: json streamer

0 of 294 new or added lines in 31 files covered. (0.0%)

11514 existing lines in 375 files now uncovered.

0 of 51976 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Symfony/Routing/IriConverter.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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
declare(strict_types=1);
13

14
namespace ApiPlatform\Symfony\Routing;
15

16
use ApiPlatform\Metadata\CollectionOperationInterface;
17
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
18
use ApiPlatform\Metadata\Exception\InvalidIdentifierException;
19
use ApiPlatform\Metadata\Exception\InvalidUriVariableException;
20
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
21
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
22
use ApiPlatform\Metadata\Exception\RuntimeException;
23
use ApiPlatform\Metadata\Get;
24
use ApiPlatform\Metadata\GetCollection;
25
use ApiPlatform\Metadata\HttpOperation;
26
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
27
use ApiPlatform\Metadata\IriConverterInterface;
28
use ApiPlatform\Metadata\Operation;
29
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
30
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
31
use ApiPlatform\Metadata\ResourceClassResolverInterface;
32
use ApiPlatform\Metadata\UriVariablesConverterInterface;
33
use ApiPlatform\Metadata\UrlGeneratorInterface;
34
use ApiPlatform\Metadata\Util\AttributesExtractor;
35
use ApiPlatform\Metadata\Util\ClassInfoTrait;
36
use ApiPlatform\Metadata\Util\ResourceClassInfoTrait;
37
use ApiPlatform\State\ProviderInterface;
38
use ApiPlatform\State\UriVariablesResolverTrait;
39
use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
40
use Symfony\Component\Routing\RouterInterface;
41

42
/**
43
 * {@inheritdoc}
44
 *
45
 * @author Antoine Bluchet <soyuka@gmail.com>
46
 */
47
final class IriConverter implements IriConverterInterface
48
{
49
    use ClassInfoTrait;
50
    use ResourceClassInfoTrait;
51
    use UriVariablesResolverTrait;
52

53
    private array $localOperationCache = [];
54
    private array $localIdentifiersExtractorOperationCache = [];
55

56
    public function __construct(private readonly ProviderInterface $provider, private readonly RouterInterface $router, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ?UriVariablesConverterInterface $uriVariablesConverter = null, private readonly ?IriConverterInterface $decorated = null, private readonly ?OperationMetadataFactoryInterface $operationMetadataFactory = null)
57
    {
UNCOV
58
        $this->resourceClassResolver = $resourceClassResolver;
×
UNCOV
59
        $this->uriVariablesConverter = $uriVariablesConverter;
×
60
    }
61

62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function getResourceFromIri(string $iri, array $context = [], ?Operation $operation = null): object
66
    {
67
        try {
UNCOV
68
            $parameters = $this->router->match($iri);
×
69
        } catch (RoutingExceptionInterface $e) {
×
70
            throw new InvalidArgumentException(\sprintf('No route matches "%s".', $iri), $e->getCode(), $e);
×
71
        }
72

UNCOV
73
        $parameters['_api_operation_name'] ??= null;
×
74

UNCOV
75
        if (!isset($parameters['_api_resource_class'], $parameters['_api_operation_name'])) {
×
76
            throw new InvalidArgumentException(\sprintf('No resource associated to "%s".', $iri));
×
77
        }
78

79
        // uri_variables come from the Request context and may not be available
UNCOV
80
        foreach ($context['uri_variables'] ?? [] as $key => $value) {
×
81
            if (!isset($parameters[$key]) || $parameters[$key] !== (string) $value) {
×
82
                throw new InvalidArgumentException(\sprintf('The iri "%s" does not reference the correct resource.', $iri));
×
83
            }
84
        }
85

UNCOV
86
        if ($operation && !is_a($parameters['_api_resource_class'], $operation->getClass(), true)) {
×
87
            throw new InvalidArgumentException(\sprintf('The iri "%s" does not reference the correct resource.', $iri));
×
88
        }
89

UNCOV
90
        $operation = $parameters['_api_operation'] = $this->resourceMetadataCollectionFactory->create($parameters['_api_resource_class'])->getOperation($parameters['_api_operation_name']);
×
91

UNCOV
92
        if ($operation instanceof CollectionOperationInterface) {
×
UNCOV
93
            throw new InvalidArgumentException(\sprintf('The iri "%s" references a collection not an item.', $iri));
×
94
        }
95

UNCOV
96
        if (!$operation instanceof HttpOperation) {
×
97
            throw new RuntimeException(\sprintf('The iri "%s" does not reference an HTTP operation.', $iri));
×
98
        }
UNCOV
99
        $attributes = AttributesExtractor::extractAttributes($parameters);
×
100

101
        try {
UNCOV
102
            $uriVariables = $this->getOperationUriVariables($operation, $parameters, $attributes['resource_class']);
×
UNCOV
103
        } catch (InvalidIdentifierException|InvalidUriVariableException $e) {
×
UNCOV
104
            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
×
105
        }
106

UNCOV
107
        if ($item = $this->provider->provide($operation, $uriVariables, $context)) {
×
UNCOV
108
            return $item;
×
109
        }
110

UNCOV
111
        throw new ItemNotFoundException(\sprintf('Item not found for "%s".', $iri));
×
112
    }
113

114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function getIriFromResource(object|string $resource, int $referenceType = UrlGeneratorInterface::ABS_PATH, ?Operation $operation = null, array $context = []): string
118
    {
UNCOV
119
        $resourceClass = $context['force_resource_class'] ?? (\is_string($resource) ? $resource : $this->getObjectClass($resource));
×
120

UNCOV
121
        if ($this->operationMetadataFactory && isset($context['item_uri_template'])) {
×
UNCOV
122
            $operation = $this->operationMetadataFactory->create($context['item_uri_template']);
×
123
        }
124

UNCOV
125
        $localOperationCacheKey = ($operation?->getName() ?? '').$resourceClass.((\is_string($resource) || $operation instanceof CollectionOperationInterface) ? '_c' : '_i');
×
UNCOV
126
        if ($operation && isset($this->localOperationCache[$localOperationCacheKey])) {
×
UNCOV
127
            return $this->generateSymfonyRoute($resource, $referenceType, $this->localOperationCache[$localOperationCacheKey], $context, $this->localIdentifiersExtractorOperationCache[$localOperationCacheKey] ?? null);
×
128
        }
129

UNCOV
130
        if (!$this->resourceClassResolver->isResourceClass($resourceClass)) {
×
UNCOV
131
            return $this->generateSkolemIri($resource, $referenceType, $operation, $context, $resourceClass);
×
132
        }
133

134
        // This is only for when a class (that is not a resource) extends another one that is a resource, we should remove this behavior
UNCOV
135
        if (!\is_string($resource) && !isset($context['force_resource_class'])) {
×
UNCOV
136
            $resourceClass = $this->getResourceClass($resource, true);
×
137
        }
138

UNCOV
139
        if (!$operation) {
×
UNCOV
140
            $operation = (new Get())->withClass($resourceClass);
×
141
        }
142

UNCOV
143
        if ($operation instanceof HttpOperation && 301 === $operation->getStatus()) {
×
144
            $operation = ($operation instanceof CollectionOperationInterface ? new GetCollection() : new Get())->withClass($operation->getClass());
×
145
            unset($context['uri_variables']);
×
146
        }
147

UNCOV
148
        $identifiersExtractorOperation = $operation;
×
149
        // In symfony the operation name is the route name, try to find one if none provided
150
        if (
UNCOV
151
            !$operation->getName()
×
UNCOV
152
            || ($operation instanceof HttpOperation && 'POST' === $operation->getMethod())
×
153
        ) {
UNCOV
154
            $forceCollection = $operation instanceof CollectionOperationInterface;
×
155
            try {
UNCOV
156
                $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(null, $forceCollection, true);
×
UNCOV
157
                $identifiersExtractorOperation = $operation;
×
UNCOV
158
            } catch (OperationNotFoundException) {
×
159
            }
160
        }
161

UNCOV
162
        if (!$operation->getName() || ($operation instanceof HttpOperation && $operation->getUriTemplate() && str_starts_with($operation->getUriTemplate(), SkolemIriConverter::$skolemUriTemplate))) {
×
UNCOV
163
            return $this->generateSkolemIri($resource, $referenceType, $operation, $context, $resourceClass);
×
164
        }
165

UNCOV
166
        $this->localOperationCache[$localOperationCacheKey] = $operation;
×
UNCOV
167
        $this->localIdentifiersExtractorOperationCache[$localOperationCacheKey] = $identifiersExtractorOperation;
×
168

UNCOV
169
        return $this->generateSymfonyRoute($resource, $referenceType, $operation, $context, $identifiersExtractorOperation);
×
170
    }
171

172
    private function generateSkolemIri(object|string $resource, int $referenceType = UrlGeneratorInterface::ABS_PATH, ?Operation $operation = null, array $context = [], ?string $resourceClass = null): string
173
    {
UNCOV
174
        if (!$this->decorated) {
×
UNCOV
175
            throw new InvalidArgumentException(\sprintf('Unable to generate an IRI for the item of type "%s"', $resourceClass));
×
176
        }
177

178
        // Use a skolem iri, the route is defined in genid.xml
UNCOV
179
        return $this->decorated->getIriFromResource($resource, $referenceType, $operation, $context);
×
180
    }
181

182
    private function generateSymfonyRoute(object|string $resource, int $referenceType = UrlGeneratorInterface::ABS_PATH, ?Operation $operation = null, array $context = [], ?Operation $identifiersExtractorOperation = null): string
183
    {
UNCOV
184
        $identifiers = $context['uri_variables'] ?? [];
×
185

UNCOV
186
        if (\is_object($resource)) {
×
187
            try {
UNCOV
188
                $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($resource, $identifiersExtractorOperation, $context);
×
UNCOV
189
            } catch (InvalidArgumentException|RuntimeException $e) {
×
190
                // We can try using context uri variables if any
UNCOV
191
                if (!$identifiers) {
×
UNCOV
192
                    throw new InvalidArgumentException(\sprintf('Unable to generate an IRI for the item of type "%s"', $operation->getClass()), $e->getCode(), $e);
×
193
                }
194
            }
195
        }
196

197
        try {
UNCOV
198
            $routeName = $operation instanceof HttpOperation ? ($operation->getRouteName() ?? $operation->getName()) : $operation->getName();
×
199

UNCOV
200
            return $this->router->generate($routeName, $identifiers, $operation->getUrlGenerationStrategy() ?? $referenceType);
×
UNCOV
201
        } catch (RoutingExceptionInterface $e) {
×
UNCOV
202
            throw new InvalidArgumentException(\sprintf('Unable to generate an IRI for the item of type "%s"', $operation->getClass()), $e->getCode(), $e);
×
203
        }
204
    }
205
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc