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

api-platform / core / 14954769666

11 May 2025 10:14AM UTC coverage: 0.0% (-8.5%) from 8.457%
14954769666

Pull #7135

github

web-flow
Merge bf21e0bc7 into 4dd0cdfc4
Pull Request #7135: fix(symfony,laravel): InvalidUriVariableException status code (e400)

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

11040 existing lines in 370 files now uncovered.

0 of 48303 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/GraphQl/State/Provider/ReadProvider.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\GraphQl\State\Provider;
15

16
use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait;
17
use ApiPlatform\GraphQl\Serializer\ItemNormalizer;
18
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
19
use ApiPlatform\GraphQl\Util\ArrayTrait;
20
use ApiPlatform\Metadata\CollectionOperationInterface;
21
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
22
use ApiPlatform\Metadata\GraphQl\Mutation;
23
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
24
use ApiPlatform\Metadata\GraphQl\QueryCollection;
25
use ApiPlatform\Metadata\GraphQl\Subscription;
26
use ApiPlatform\Metadata\IriConverterInterface;
27
use ApiPlatform\Metadata\Operation;
28
use ApiPlatform\Metadata\Util\ClassInfoTrait;
29
use ApiPlatform\State\ProviderInterface;
30
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
31

32
/**
33
 * Reads data of the provided args, it is also called when reading relations in the same query.
34
 */
35
final class ReadProvider implements ProviderInterface
36
{
37
    use ArrayTrait;
38
    use ClassInfoTrait;
39
    use IdentifierTrait;
40

41
    public function __construct(private readonly ProviderInterface $provider, private readonly IriConverterInterface $iriConverter, private readonly ?SerializerContextBuilderInterface $serializerContextBuilder, private readonly string $nestingSeparator)
42
    {
UNCOV
43
    }
×
44

45
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
46
    {
UNCOV
47
        if (!$operation instanceof GraphQlOperation || !($operation->canRead() ?? true)) {
×
48
            return $operation instanceof QueryCollection ? [] : null;
×
49
        }
50

UNCOV
51
        $args = $context['args'] ?? [];
×
52

UNCOV
53
        if ($this->serializerContextBuilder) {
×
54
            // Builtin data providers are able to use the serialization context to automatically add join clauses
UNCOV
55
            $context += $this->serializerContextBuilder->create($operation->getClass(), $operation, $context, true);
×
56
        }
57

UNCOV
58
        if (!$operation instanceof CollectionOperationInterface) {
×
UNCOV
59
            $identifier = $this->getIdentifierFromOperation($operation, $args);
×
60

UNCOV
61
            if (!$identifier) {
×
UNCOV
62
                return null;
×
63
            }
64

65
            try {
UNCOV
66
                $item = $this->iriConverter->getResourceFromIri($identifier, $context);
×
67
            } catch (ItemNotFoundException) {
×
68
                $item = null;
×
69
            }
70

UNCOV
71
            if ($operation instanceof Subscription || $operation instanceof Mutation) {
×
72
                if (null === $item) {
×
73
                    throw new NotFoundHttpException(\sprintf('Item "%s" not found.', $args['input']['id']));
×
74
                }
75

76
                if ($operation->getClass() !== $this->getObjectClass($item)) {
×
77
                    throw new \UnexpectedValueException(\sprintf('Item "%s" did not match expected type "%s".', $args['input']['id'], $operation->getShortName()));
×
78
                }
79
            }
80

UNCOV
81
            if (null === $item) {
×
82
                return $item;
×
83
            }
84

UNCOV
85
            if (!\is_object($item)) {
×
86
                throw new \LogicException('Item from read provider should be a nullable object.');
×
87
            }
88

UNCOV
89
            if (isset($context['graphql_context']) && !enum_exists($item::class)) {
×
UNCOV
90
                $context['graphql_context']['previous_object'] = clone $item;
×
91
            }
92

UNCOV
93
            return $item;
×
94
        }
95

UNCOV
96
        if (null === ($context['root_class'] ?? null)) {
×
97
            return [];
×
98
        }
99

UNCOV
100
        $uriVariables = [];
×
UNCOV
101
        $context['filters'] = $this->getNormalizedFilters($args);
×
102

103
        // This is how we resolve graphql links see ApiPlatform\Doctrine\Common\State\LinksHandlerTrait, I'm wondering if we couldn't do that in an UriVariables
104
        // resolver within our ApiPlatform\GraphQl\Resolver\Factory\ResolverFactory, this would mimic what's happening in the HTTP controller and simplify some code.
UNCOV
105
        $source = $context['source'];
×
106
        /** @var \GraphQL\Type\Definition\ResolveInfo $info */
UNCOV
107
        $info = $context['info'];
×
UNCOV
108
        if (isset($source[$info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
×
109
            $uriVariables = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY];
×
110
            $context['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY];
×
111
            $context['linkProperty'] = $info->fieldName;
×
112
        }
113

UNCOV
114
        return $this->provider->provide($operation, $uriVariables, $context);
×
115
    }
116

117
    /**
118
     * @param array<string, string|array> $args
119
     *
120
     * @return array<string, string>
121
     */
122
    private function getNormalizedFilters(array $args): array
123
    {
UNCOV
124
        $filters = $args;
×
125

UNCOV
126
        foreach ($filters as $name => $value) {
×
UNCOV
127
            if (\is_array($value)) {
×
UNCOV
128
                if (strpos($name, '_list')) {
×
129
                    $name = substr($name, 0, \strlen($name) - \strlen('_list'));
×
130
                }
131

132
                // If the value contains arrays, we need to merge them for the filters to understand this syntax, proper to GraphQL to preserve the order of the arguments.
UNCOV
133
                if ($this->isSequentialArrayOfArrays($value)) {
×
134
                    $value = array_merge(...$value);
×
135
                }
UNCOV
136
                $filters[$name] = $this->getNormalizedFilters($value);
×
137
            }
138

UNCOV
139
            if (\is_string($name) && strpos($name, $this->nestingSeparator)) {
×
140
                // Gives a chance to relations/nested fields.
141
                $index = array_search($name, array_keys($filters), true);
×
142
                $filters =
×
143
                    \array_slice($filters, 0, $index + 1) +
×
144
                    [str_replace($this->nestingSeparator, '.', $name) => $value] +
×
145
                    \array_slice($filters, $index + 1);
×
146
            }
147
        }
148

UNCOV
149
        return $filters;
×
150
    }
151
}
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