1 | <?php |
||||
2 | |||||
3 | /* |
||||
4 | * This file is part of the API Platform project. |
||||
5 | * |
||||
6 | * (c) Kévin Dunglas <[email protected]> |
||||
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\Core\GraphQl\Resolver\Stage; |
||||
15 | |||||
16 | use ApiPlatform\Core\Api\IriConverterInterface; |
||||
17 | use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; |
||||
18 | use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; |
||||
19 | use ApiPlatform\Core\Exception\ItemNotFoundException; |
||||
20 | use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; |
||||
21 | use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface; |
||||
22 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
||||
23 | use ApiPlatform\Core\Util\ClassInfoTrait; |
||||
24 | use GraphQL\Error\Error; |
||||
25 | use GraphQL\Type\Definition\ResolveInfo; |
||||
26 | |||||
27 | /** |
||||
28 | * Read stage of GraphQL resolvers. |
||||
29 | * |
||||
30 | * @experimental |
||||
31 | * |
||||
32 | * @author Alan Poulain <[email protected]> |
||||
33 | */ |
||||
34 | final class ReadStage implements ReadStageInterface |
||||
35 | { |
||||
36 | use ClassInfoTrait; |
||||
37 | |||||
38 | private $resourceMetadataFactory; |
||||
39 | private $iriConverter; |
||||
40 | private $collectionDataProvider; |
||||
41 | private $subresourceDataProvider; |
||||
42 | private $serializerContextBuilder; |
||||
43 | |||||
44 | public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, ContextAwareCollectionDataProviderInterface $collectionDataProvider, SubresourceDataProviderInterface $subresourceDataProvider, SerializerContextBuilderInterface $serializerContextBuilder) |
||||
45 | { |
||||
46 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||||
47 | $this->iriConverter = $iriConverter; |
||||
48 | $this->collectionDataProvider = $collectionDataProvider; |
||||
49 | $this->subresourceDataProvider = $subresourceDataProvider; |
||||
50 | $this->serializerContextBuilder = $serializerContextBuilder; |
||||
51 | } |
||||
52 | |||||
53 | /** |
||||
54 | * {@inheritdoc} |
||||
55 | */ |
||||
56 | public function __invoke(?string $resourceClass, ?string $rootClass, string $operationName, array $context) |
||||
57 | { |
||||
58 | $resourceMetadata = $resourceClass ? $this->resourceMetadataFactory->create($resourceClass) : null; |
||||
59 | if ($resourceMetadata && !$resourceMetadata->getGraphqlAttribute($operationName, 'read', true, true)) { |
||||
60 | return $context['is_collection'] ? [] : null; |
||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||||
61 | } |
||||
62 | |||||
63 | $args = $context['args']; |
||||
64 | /** @var ResolveInfo $info */ |
||||
65 | $info = $context['info']; |
||||
66 | |||||
67 | $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, true); |
||||
0 ignored issues
–
show
It seems like
$resourceClass can also be of type null ; however, parameter $resourceClass of ApiPlatform\Core\GraphQl...lderInterface::create() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
68 | |||||
69 | if (!$context['is_collection']) { |
||||
70 | $identifier = $this->getIdentifier($context); |
||||
71 | $item = $this->getItem($identifier, $normalizationContext); |
||||
72 | |||||
73 | if ($identifier && $context['is_mutation']) { |
||||
74 | if (null === $item) { |
||||
75 | throw Error::createLocatedError(sprintf('Item "%s" not found.', $args['input']['id']), $info->fieldNodes, $info->path); |
||||
76 | } |
||||
77 | |||||
78 | if ($resourceClass !== $this->getObjectClass($item)) { |
||||
79 | throw Error::createLocatedError(sprintf('Item "%s" did not match expected type "%s".', $args['input']['id'], $resourceMetadata->getShortName()), $info->fieldNodes, $info->path); |
||||
0 ignored issues
–
show
The method
getShortName() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||
80 | } |
||||
81 | } |
||||
82 | |||||
83 | return $item; |
||||
84 | } |
||||
85 | |||||
86 | if (null === $rootClass) { |
||||
87 | return []; |
||||
0 ignored issues
–
show
The expression
return array() returns the type array which is incompatible with the return type mandated by ApiPlatform\Core\GraphQl...geInterface::__invoke() of iterable|null|object .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
Loading history...
|
|||||
88 | } |
||||
89 | |||||
90 | $normalizationContext['filters'] = $this->getNormalizedFilters($args); |
||||
91 | |||||
92 | $source = $context['source']; |
||||
93 | if (isset($source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY])) { |
||||
94 | $rootResolvedFields = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY]; |
||||
95 | $subresourceCollection = $this->getSubresource($rootClass, $rootResolvedFields, $rootProperty, $resourceClass, $normalizationContext, $operationName); |
||||
0 ignored issues
–
show
It seems like
$resourceClass can also be of type null ; however, parameter $subresourceClass of ApiPlatform\Core\GraphQl...Stage::getSubresource() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
96 | if (!is_iterable($subresourceCollection)) { |
||||
97 | throw new \UnexpectedValueException('Expected subresource collection to be iterable'); |
||||
98 | } |
||||
99 | |||||
100 | return $subresourceCollection; |
||||
101 | } |
||||
102 | |||||
103 | return $this->collectionDataProvider->getCollection($resourceClass, $operationName, $normalizationContext); |
||||
0 ignored issues
–
show
It seems like
$resourceClass can also be of type null ; however, parameter $resourceClass of ApiPlatform\Core\DataPro...erface::getCollection() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
104 | } |
||||
105 | |||||
106 | private function getIdentifier(array $context): ?string |
||||
107 | { |
||||
108 | $args = $context['args']; |
||||
109 | |||||
110 | if ($context['is_mutation']) { |
||||
111 | return $args['input']['id'] ?? null; |
||||
112 | } |
||||
113 | |||||
114 | return $args['id'] ?? null; |
||||
115 | } |
||||
116 | |||||
117 | /** |
||||
118 | * @return object|null |
||||
119 | */ |
||||
120 | private function getItem(?string $identifier, array $normalizationContext) |
||||
121 | { |
||||
122 | if (null === $identifier) { |
||||
123 | return null; |
||||
124 | } |
||||
125 | |||||
126 | try { |
||||
127 | $item = $this->iriConverter->getItemFromIri($identifier, $normalizationContext); |
||||
128 | } catch (ItemNotFoundException $e) { |
||||
129 | return null; |
||||
130 | } |
||||
131 | |||||
132 | return $item; |
||||
133 | } |
||||
134 | |||||
135 | private function getNormalizedFilters(array $args): array |
||||
136 | { |
||||
137 | $filters = $args; |
||||
138 | |||||
139 | foreach ($filters as $name => $value) { |
||||
140 | if (\is_array($value)) { |
||||
141 | if (strpos($name, '_list')) { |
||||
142 | $name = substr($name, 0, \strlen($name) - \strlen('_list')); |
||||
143 | } |
||||
144 | $filters[$name] = $this->getNormalizedFilters($value); |
||||
145 | } |
||||
146 | |||||
147 | if (\is_string($name) && strpos($name, '_')) { |
||||
148 | // Gives a chance to relations/nested fields. |
||||
149 | $filters[str_replace('_', '.', $name)] = $value; |
||||
150 | } |
||||
151 | } |
||||
152 | |||||
153 | return $filters; |
||||
154 | } |
||||
155 | |||||
156 | /** |
||||
157 | * @return iterable|object|null |
||||
158 | */ |
||||
159 | private function getSubresource(string $rootClass, array $rootResolvedFields, string $rootProperty, string $subresourceClass, array $normalizationContext, string $operationName) |
||||
160 | { |
||||
161 | $resolvedIdentifiers = []; |
||||
162 | $rootIdentifiers = array_keys($rootResolvedFields); |
||||
163 | foreach ($rootIdentifiers as $rootIdentifier) { |
||||
164 | $resolvedIdentifiers[] = [$rootIdentifier, $rootClass]; |
||||
165 | } |
||||
166 | |||||
167 | return $this->subresourceDataProvider->getSubresource($subresourceClass, $rootResolvedFields, $normalizationContext + [ |
||||
168 | 'property' => $rootProperty, |
||||
169 | 'identifiers' => $resolvedIdentifiers, |
||||
170 | 'collection' => true, |
||||
171 | ], $operationName); |
||||
172 | } |
||||
173 | } |
||||
174 |