• 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/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 (isset($context['graphql_context']) && !enum_exists($item::class)) {
×
UNCOV
86
                $context['graphql_context']['previous_object'] = clone $item;
×
87
            }
88

UNCOV
89
            return $item;
×
90
        }
91

UNCOV
92
        if (null === ($context['root_class'] ?? null)) {
×
93
            return [];
×
94
        }
95

UNCOV
96
        $uriVariables = [];
×
UNCOV
97
        $context['filters'] = $this->getNormalizedFilters($args);
×
98

99
        // 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
100
        // resolver within our ApiPlatform\GraphQl\Resolver\Factory\ResolverFactory, this would mimic what's happening in the HTTP controller and simplify some code.
UNCOV
101
        $source = $context['source'];
×
102
        /** @var \GraphQL\Type\Definition\ResolveInfo $info */
UNCOV
103
        $info = $context['info'];
×
UNCOV
104
        if (isset($source[$info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
×
105
            $uriVariables = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY];
×
106
            $context['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY];
×
107
            $context['linkProperty'] = $info->fieldName;
×
108
        }
109

UNCOV
110
        return $this->provider->provide($operation, $uriVariables, $context);
×
111
    }
112

113
    /**
114
     * @param array<string, string|array> $args
115
     *
116
     * @return array<string, string>
117
     */
118
    private function getNormalizedFilters(array $args): array
119
    {
UNCOV
120
        $filters = $args;
×
121

UNCOV
122
        foreach ($filters as $name => $value) {
×
123
            // Prevent numeric keys like `'1'`
UNCOV
124
            $name = (string) $name;
×
125

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

131
                // 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
132
                if ($this->isSequentialArrayOfArrays($value)) {
×
133
                    $value = array_merge(...$value);
×
134
                }
UNCOV
135
                $filters[$name] = $this->getNormalizedFilters($value);
×
136
            }
137

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

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