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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

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

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

94.12
/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
    }
148✔
44

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

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

UNCOV
53
        if ($this->serializerContextBuilder) {
122✔
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);
122✔
56
        }
57

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

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

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

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

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

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

UNCOV
85
            if (!\is_object($item)) {
61✔
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)) {
61✔
UNCOV
90
                $context['graphql_context']['previous_object'] = clone $item;
54✔
91
            }
92

UNCOV
93
            return $item;
61✔
94
        }
95

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

UNCOV
100
        $uriVariables = [];
52✔
UNCOV
101
        $context['filters'] = $this->getNormalizedFilters($args);
52✔
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'];
52✔
106
        /** @var \GraphQL\Type\Definition\ResolveInfo $info */
UNCOV
107
        $info = $context['info'];
52✔
UNCOV
108
        if (isset($source[$info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
52✔
109
            $uriVariables = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY];
20✔
110
            $context['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY];
20✔
111
            $context['linkProperty'] = $info->fieldName;
20✔
112
        }
113

UNCOV
114
        return $this->provider->provide($operation, $uriVariables, $context);
52✔
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;
52✔
125

UNCOV
126
        foreach ($filters as $name => $value) {
52✔
UNCOV
127
            if (\is_array($value)) {
23✔
UNCOV
128
                if (strpos($name, '_list')) {
6✔
129
                    $name = substr($name, 0, \strlen($name) - \strlen('_list'));
1✔
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)) {
6✔
134
                    $value = array_merge(...$value);
4✔
135
                }
UNCOV
136
                $filters[$name] = $this->getNormalizedFilters($value);
6✔
137
            }
138

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

UNCOV
149
        return $filters;
52✔
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