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

api-platform / core / 13925245599

18 Mar 2025 02:06PM UTC coverage: 61.074% (-0.9%) from 61.973%
13925245599

Pull #7031

github

web-flow
Merge 8990d8b26 into 7cb5a6db8
Pull Request #7031: fix: header parameter should be case insensitive

3 of 4 new or added lines in 1 file covered. (75.0%)

167 existing lines in 27 files now uncovered.

11314 of 18525 relevant lines covered (61.07%)

51.02 hits per line

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

0.0
/src/GraphQl/Resolver/Stage/ReadStage.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\Resolver\Stage;
15

16
use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait;
17
use ApiPlatform\GraphQl\Serializer\ItemNormalizer;
18
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
19
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
20
use ApiPlatform\Metadata\GraphQl\Operation;
21
use ApiPlatform\Metadata\IriConverterInterface;
22
use ApiPlatform\State\ProviderInterface;
23
use GraphQL\Type\Definition\ResolveInfo;
24
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
25

26
/**
27
 * Read stage of GraphQL resolvers.
28
 *
29
 * @author Alan Poulain <contact@alanpoulain.eu>
30
 *
31
 * @deprecated
32
 */
33
final class ReadStage implements ReadStageInterface
34
{
35
    use IdentifierTrait;
36

37
    public function __construct(private readonly IriConverterInterface $iriConverter, private readonly ProviderInterface $provider, private readonly SerializerContextBuilderInterface $serializerContextBuilder, private readonly string $nestingSeparator)
38
    {
UNCOV
39
    }
×
40

41
    /**
42
     * {@inheritdoc}
43
     */
44
    public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context): object|array|null
45
    {
UNCOV
46
        if (!($operation->canRead() ?? true)) {
×
47
            return $context['is_collection'] ? [] : null;
×
48
        }
49

UNCOV
50
        $args = $context['args'];
×
UNCOV
51
        $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, true);
×
52

UNCOV
53
        if (!$context['is_collection']) {
×
UNCOV
54
            $identifier = $this->getIdentifierFromContext($context);
×
UNCOV
55
            $item = $this->getItem($identifier, $normalizationContext);
×
56

UNCOV
57
            if ($identifier && ($context['is_mutation'] || $context['is_subscription'])) {
×
58
                if (null === $item) {
×
59
                    throw new NotFoundHttpException(\sprintf('Item "%s" not found.', $args['input']['id']));
×
60
                }
61

62
                if ($resourceClass !== $this->getObjectClass($item)) {
×
63
                    throw new \UnexpectedValueException(\sprintf('Item "%s" did not match expected type "%s".', $args['input']['id'], $operation->getShortName()));
×
64
                }
65
            }
66

UNCOV
67
            return $item;
×
68
        }
69

UNCOV
70
        if (null === $rootClass) {
×
71
            return [];
×
72
        }
73

UNCOV
74
        $uriVariables = [];
×
UNCOV
75
        $normalizationContext['filters'] = $this->getNormalizedFilters($args);
×
UNCOV
76
        $normalizationContext['operation'] = $operation;
×
77

UNCOV
78
        $source = $context['source'];
×
79
        /** @var ResolveInfo $info */
UNCOV
80
        $info = $context['info'];
×
UNCOV
81
        if (isset($source[$info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
×
82
            $uriVariables = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY];
×
83
            $normalizationContext['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY];
×
84
            $normalizationContext['linkProperty'] = $info->fieldName;
×
85
        }
86

UNCOV
87
        return $this->provider->provide($operation, $uriVariables, $normalizationContext);
×
88
    }
89

90
    private function getItem(?string $identifier, array $normalizationContext): ?object
91
    {
UNCOV
92
        if (null === $identifier) {
×
93
            return null;
×
94
        }
95

96
        try {
UNCOV
97
            $item = $this->iriConverter->getResourceFromIri($identifier, $normalizationContext);
×
98
        } catch (ItemNotFoundException) {
×
99
            return null;
×
100
        }
101

UNCOV
102
        return $item;
×
103
    }
104

105
    private function getNormalizedFilters(array $args): array
106
    {
UNCOV
107
        $filters = $args;
×
108

UNCOV
109
        foreach ($filters as $name => $value) {
×
110
            if (\is_array($value)) {
×
111
                if (strpos($name, '_list')) {
×
112
                    $name = substr($name, 0, \strlen($name) - \strlen('_list'));
×
113
                }
114

115
                // 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.
116
                if ($this->isSequentialArrayOfArrays($value)) {
×
117
                    $value = array_merge(...$value);
×
118
                }
119
                $filters[$name] = $this->getNormalizedFilters($value);
×
120
            }
121

122
            if (\is_string($name) && strpos($name, $this->nestingSeparator)) {
×
123
                // Gives a chance to relations/nested fields.
124
                $index = array_search($name, array_keys($filters), true);
×
125
                $filters =
×
126
                    \array_slice($filters, 0, $index + 1) +
×
127
                    [str_replace($this->nestingSeparator, '.', $name) => $value] +
×
128
                    \array_slice($filters, $index + 1);
×
129
            }
130
        }
131

UNCOV
132
        return $filters;
×
133
    }
134

135
    public function isSequentialArrayOfArrays(array $array): bool
136
    {
137
        if (!$this->isSequentialArray($array)) {
×
138
            return false;
×
139
        }
140

141
        return $this->arrayContainsOnly($array, 'array');
×
142
    }
143

144
    public function isSequentialArray(array $array): bool
145
    {
146
        if ([] === $array) {
×
147
            return false;
×
148
        }
149

150
        return array_is_list($array);
×
151
    }
152

153
    public function arrayContainsOnly(array $array, string $type): bool
154
    {
155
        return $array === array_filter($array, static fn ($item): bool => $type === \gettype($item));
×
156
    }
157

158
    /**
159
     * Get class name of the given object.
160
     */
161
    private function getObjectClass(object $object): string
162
    {
163
        return $this->getRealClassName($object::class);
×
164
    }
165

166
    /**
167
     * Get the real class name of a class name that could be a proxy.
168
     */
169
    private function getRealClassName(string $className): string
170
    {
171
        // __CG__: Doctrine Common Marker for Proxy (ODM < 2.0 and ORM < 3.0)
172
        // __PM__: Ocramius Proxy Manager (ODM >= 2.0)
173
        $positionCg = strrpos($className, '\\__CG__\\');
×
174
        $positionPm = strrpos($className, '\\__PM__\\');
×
175

176
        if (false === $positionCg && false === $positionPm) {
×
177
            return $className;
×
178
        }
179

180
        if (false !== $positionCg) {
×
181
            return substr($className, $positionCg + 8);
×
182
        }
183

184
        $className = ltrim($className, '\\');
×
185

186
        return substr(
×
187
            $className,
×
188
            8 + $positionPm,
×
189
            strrpos($className, '\\') - ($positionPm + 8)
×
190
        );
×
191
    }
192
}
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