components-web-app /
api-components-bundle
| 1 | <?php |
||||||||
| 2 | |||||||||
| 3 | /* |
||||||||
| 4 | * This file is part of the Silverback API Components Bundle Project |
||||||||
| 5 | * |
||||||||
| 6 | * (c) Daniel West <[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 Silverback\ApiComponentsBundle\DataTransformer; |
||||||||
| 15 | |||||||||
| 16 | use ApiPlatform\Core\DataTransformer\DataTransformerInterface; |
||||||||
| 17 | use ApiPlatform\Exception\InvalidIdentifierException; |
||||||||
| 18 | use ApiPlatform\Metadata\ApiResource; |
||||||||
| 19 | use ApiPlatform\Metadata\CollectionOperationInterface; |
||||||||
| 20 | use ApiPlatform\Metadata\HttpOperation; |
||||||||
| 21 | use ApiPlatform\Metadata\Operation; |
||||||||
| 22 | use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; |
||||||||
| 23 | use ApiPlatform\Serializer\SerializerContextBuilderInterface; |
||||||||
| 24 | use ApiPlatform\State\ProviderInterface; |
||||||||
| 25 | use ApiPlatform\State\UriVariablesResolverTrait; |
||||||||
| 26 | use ApiPlatform\Util\AttributesExtractor; |
||||||||
| 27 | use ApiPlatform\Util\RequestParser; |
||||||||
| 28 | use Silverback\ApiComponentsBundle\Entity\Component\Collection; |
||||||||
| 29 | use Silverback\ApiComponentsBundle\Exception\OutOfBoundsException; |
||||||||
| 30 | use Silverback\ApiComponentsBundle\Serializer\SerializeFormatResolver; |
||||||||
| 31 | use Silverback\ApiComponentsBundle\Utility\ApiResourceRouteFinder; |
||||||||
| 32 | use Symfony\Component\HttpFoundation\RequestStack; |
||||||||
| 33 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
||||||||
| 34 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
||||||||
| 35 | |||||||||
| 36 | /** |
||||||||
| 37 | * @author Daniel West <[email protected]> |
||||||||
| 38 | */ |
||||||||
| 39 | class CollectionOutputDataTransformer implements DataTransformerInterface |
||||||||
| 40 | { |
||||||||
| 41 | use UriVariablesResolverTrait; |
||||||||
| 42 | |||||||||
| 43 | private ApiResourceRouteFinder $resourceRouteFinder; |
||||||||
| 44 | private ProviderInterface $provider; |
||||||||
| 45 | private RequestStack $requestStack; |
||||||||
| 46 | private SerializerContextBuilderInterface $serializerContextBuilder; |
||||||||
| 47 | private NormalizerInterface $itemNormalizer; |
||||||||
| 48 | private SerializeFormatResolver $serializeFormatResolver; |
||||||||
| 49 | private string $itemsPerPageParameterName; |
||||||||
| 50 | private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory; |
||||||||
| 51 | |||||||||
| 52 | public function __construct( |
||||||||
| 53 | ApiResourceRouteFinder $resourceRouteFinder, |
||||||||
| 54 | ProviderInterface $provider, |
||||||||
| 55 | RequestStack $requestStack, |
||||||||
| 56 | SerializerContextBuilderInterface $serializerContextBuilder, |
||||||||
| 57 | NormalizerInterface $itemNormalizer, |
||||||||
| 58 | SerializeFormatResolver $serializeFormatResolver, |
||||||||
| 59 | ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, |
||||||||
| 60 | string $itemsPerPageParameterName |
||||||||
| 61 | ) { |
||||||||
| 62 | $this->resourceRouteFinder = $resourceRouteFinder; |
||||||||
| 63 | $this->provider = $provider; |
||||||||
| 64 | $this->requestStack = $requestStack; |
||||||||
| 65 | $this->serializerContextBuilder = $serializerContextBuilder; |
||||||||
| 66 | $this->itemNormalizer = $itemNormalizer; |
||||||||
| 67 | $this->serializeFormatResolver = $serializeFormatResolver; |
||||||||
| 68 | $this->itemsPerPageParameterName = $itemsPerPageParameterName; |
||||||||
| 69 | $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; |
||||||||
| 70 | } |
||||||||
| 71 | |||||||||
| 72 | public function supportsTransformation($data, string $to, array $context = []): bool |
||||||||
| 73 | { |
||||||||
| 74 | return $data instanceof Collection && Collection::class === $to; |
||||||||
| 75 | } |
||||||||
| 76 | |||||||||
| 77 | /** |
||||||||
| 78 | * @param object|Collection $object |
||||||||
| 79 | */ |
||||||||
| 80 | public function transform($object, string $to, array $context = []): Collection |
||||||||
| 81 | { |
||||||||
| 82 | $parameters = $this->resourceRouteFinder->findByIri($object->getResourceIri()); |
||||||||
| 83 | $attributes = AttributesExtractor::extractAttributes($parameters); |
||||||||
| 84 | $request = $this->requestStack->getMainRequest(); |
||||||||
| 85 | if (!$request) { |
||||||||
| 86 | return $object; |
||||||||
| 87 | } |
||||||||
| 88 | // Fetch the collection with computed context |
||||||||
| 89 | $resourceClass = $attributes['resource_class']; |
||||||||
| 90 | |||||||||
| 91 | $getCollectionOperation = $this->findGetCollectionOperation($resourceClass); |
||||||||
| 92 | if (!$getCollectionOperation) { |
||||||||
| 93 | return $object; |
||||||||
| 94 | } |
||||||||
| 95 | |||||||||
| 96 | // Build context |
||||||||
| 97 | $collectionContext = ['operation' => $getCollectionOperation]; |
||||||||
| 98 | |||||||||
| 99 | // Build filters |
||||||||
| 100 | $filters = []; |
||||||||
| 101 | if (($perPage = $object->getPerPage()) !== null) { |
||||||||
| 102 | $filters[$this->itemsPerPageParameterName] = $perPage; |
||||||||
| 103 | } |
||||||||
| 104 | if (($defaultQueryParams = $object->getDefaultQueryParameters()) !== null) { |
||||||||
| 105 | $filters += $defaultQueryParams; |
||||||||
| 106 | } |
||||||||
| 107 | if (null === $requestFilters = $request->attributes->get('_api_filters')) { |
||||||||
| 108 | $queryString = RequestParser::getQueryString($request); |
||||||||
| 109 | $requestFilters = $queryString ? RequestParser::parseRequestParams($queryString) : null; |
||||||||
| 110 | } |
||||||||
| 111 | if ($requestFilters) { |
||||||||
| 112 | // not += because we want to overwrite with an empty string if provided in querystring. |
||||||||
| 113 | // e.g. a default search value could be overridden by no search value |
||||||||
| 114 | $filters = array_merge($filters, $requestFilters); |
||||||||
| 115 | } |
||||||||
| 116 | $collectionContext['filters'] = $filters; |
||||||||
| 117 | |||||||||
| 118 | // Compose context for provider |
||||||||
| 119 | $collectionContext += $normalizationContext = $this->serializerContextBuilder->createFromRequest($request, true, $attributes); |
||||||||
| 120 | |||||||||
| 121 | try { |
||||||||
| 122 | $uriVariables = $this->getOperationUriVariables($getCollectionOperation, $parameters, $resourceClass); |
||||||||
| 123 | $collectionData = $this->provider->provide($resourceClass, $uriVariables, $getCollectionOperation->getName(), $collectionContext); |
||||||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
$resourceClass of type string is incompatible with the type ApiPlatform\Metadata\Operation expected by parameter $operation of ApiPlatform\State\ProviderInterface::provide().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
The call to
ApiPlatform\State\ProviderInterface::provide() has too many arguments starting with $collectionContext.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. Loading history...
|
|||||||||
| 124 | } catch (InvalidIdentifierException $e) { |
||||||||
| 125 | throw new NotFoundHttpException('Invalid identifier value or configuration.', $e); |
||||||||
| 126 | } |
||||||||
| 127 | |||||||||
| 128 | // Normalize the collection into an array |
||||||||
| 129 | // Pagination disabled |
||||||||
| 130 | if (\is_array($collectionData)) { |
||||||||
| 131 | $collection = $collectionData; |
||||||||
| 132 | } else { |
||||||||
| 133 | if (!$collectionData instanceof \Traversable) { |
||||||||
| 134 | throw new OutOfBoundsException('$collectionData should be Traversable'); |
||||||||
| 135 | } |
||||||||
| 136 | $collection = iterator_count($collectionData) ? $collectionData : []; |
||||||||
| 137 | } |
||||||||
| 138 | $format = $this->serializeFormatResolver->getFormatFromRequest($request); |
||||||||
| 139 | $normalizedCollection = $this->itemNormalizer->normalize($collection, $format, $normalizationContext); |
||||||||
| 140 | |||||||||
| 141 | // Update the original collection resource |
||||||||
| 142 | $object->setCollection($normalizedCollection); |
||||||||
| 143 | |||||||||
| 144 | return $object; |
||||||||
| 145 | } |
||||||||
| 146 | |||||||||
| 147 | private function findGetCollectionOperation(string $resourceClass): ?Operation |
||||||||
| 148 | { |
||||||||
| 149 | $metadata = $this->resourceMetadataCollectionFactory->create($resourceClass); |
||||||||
| 150 | $it = $metadata->getIterator(); |
||||||||
| 151 | /** @var ApiResource $apiResource */ |
||||||||
| 152 | foreach ($it as $apiResource) { |
||||||||
| 153 | $operations = $apiResource->getOperations(); |
||||||||
| 154 | if ($operations) { |
||||||||
| 155 | /** @var Operation $operation */ |
||||||||
| 156 | foreach ($operations as $operation) { |
||||||||
| 157 | if ($operation instanceof CollectionOperationInterface && HttpOperation::METHOD_GET === $operation->getMethod()) { |
||||||||
|
0 ignored issues
–
show
The method
getMethod() does not exist on ApiPlatform\Metadata\CollectionOperationInterface. It seems like you code against a sub-type of ApiPlatform\Metadata\CollectionOperationInterface such as ApiPlatform\Metadata\GetCollection.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||||
| 158 | return $operation; |
||||||||
|
0 ignored issues
–
show
|
|||||||||
| 159 | } |
||||||||
| 160 | } |
||||||||
| 161 | } |
||||||||
| 162 | } |
||||||||
| 163 | |||||||||
| 164 | return null; |
||||||||
| 165 | } |
||||||||
| 166 | } |
||||||||
| 167 |