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

api-platform / core / 9937215276

15 Jul 2024 09:45AM UTC coverage: 64.687%. Remained the same
9937215276

push

github

web-flow
chore: symfony 7.1 dependency and branch alias (#6468)

* chore: symfony 7.1 dependency and branch alias

* chore: main_request

11480 of 17747 relevant lines covered (64.69%)

69.1 hits per line

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

34.33
/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
final class ReadStage implements ReadStageInterface
32
{
33
    use IdentifierTrait;
34

35
    public function __construct(private readonly IriConverterInterface $iriConverter, private readonly ProviderInterface $provider, private readonly SerializerContextBuilderInterface $serializerContextBuilder, private readonly string $nestingSeparator)
36
    {
37
    }
8✔
38

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

48
        $args = $context['args'];
8✔
49
        $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, true);
8✔
50

51
        if (!$context['is_collection']) {
8✔
52
            $identifier = $this->getIdentifierFromContext($context);
7✔
53
            $item = $this->getItem($identifier, $normalizationContext);
7✔
54

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

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

65
            return $item;
7✔
66
        }
67

68
        if (null === $rootClass) {
1✔
69
            return [];
×
70
        }
71

72
        $uriVariables = [];
1✔
73
        $normalizationContext['filters'] = $this->getNormalizedFilters($args);
1✔
74
        $normalizationContext['operation'] = $operation;
1✔
75

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

85
        return $this->provider->provide($operation, $uriVariables, $normalizationContext);
1✔
86
    }
87

88
    private function getItem(?string $identifier, array $normalizationContext): ?object
89
    {
90
        if (null === $identifier) {
7✔
91
            return null;
×
92
        }
93

94
        try {
95
            $item = $this->iriConverter->getResourceFromIri($identifier, $normalizationContext);
7✔
96
        } catch (ItemNotFoundException) {
×
97
            return null;
×
98
        }
99

100
        return $item;
7✔
101
    }
102

103
    private function getNormalizedFilters(array $args): array
104
    {
105
        $filters = $args;
1✔
106

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

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

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

130
        return $filters;
1✔
131
    }
132

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

139
        return $this->arrayContainsOnly($array, 'array');
×
140
    }
141

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

148
        return array_is_list($array);
×
149
    }
150

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

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

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

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

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

182
        $className = ltrim($className, '\\');
×
183

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

© 2026 Coveralls, Inc