diff --git a/src/GraphQl/Serializer/SerializerContextBuilder.php b/src/GraphQl/Serializer/SerializerContextBuilder.php index 571f3dae9b8..58b8061b749 100644 --- a/src/GraphQl/Serializer/SerializerContextBuilder.php +++ b/src/GraphQl/Serializer/SerializerContextBuilder.php @@ -40,12 +40,8 @@ public function create(?string $resourceClass, Operation $operation, array $reso } $context['operation'] = $operation; - if ($operation->getInput()) { - $context['input'] = $operation->getInput(); - } - if ($operation->getOutput()) { - $context['output'] = $operation->getOutput(); - } + $context['input'] =['class' => $operation->getInputClass()]; + $context['output'] = ['class' => $operation->getOutputClass()]; $context = $normalization ? array_merge($operation->getNormalizationContext() ?? [], $context) : array_merge($operation->getDenormalizationContext() ?? [], $context); if ($normalization) { diff --git a/src/GraphQl/State/Provider/ResolverProvider.php b/src/GraphQl/State/Provider/ResolverProvider.php index 5d51f5c8d50..7917f90d9f4 100644 --- a/src/GraphQl/State/Provider/ResolverProvider.php +++ b/src/GraphQl/State/Provider/ResolverProvider.php @@ -44,7 +44,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $item = $queryResolver($item, $context); if (!$operation instanceof CollectionOperationInterface) { // The item retrieved can be of another type when using an identifier (see Relay Nodes at query.feature:23) - $this->getResourceClass($item, $operation->getOutput()['class'] ?? $operation->getClass(), \sprintf('Custom query resolver "%s"', $queryResolverId).' has to return an item of class %s but returned an item of class %s.'); + $this->getResourceClass($item, $operation->getOutputClass(), \sprintf('Custom query resolver "%s"', $queryResolverId).' has to return an item of class %s but returned an item of class %s.'); } return $item; diff --git a/src/GraphQl/Type/TypeBuilder.php b/src/GraphQl/Type/TypeBuilder.php index 289bb8762a4..e9a9cb7fa5d 100644 --- a/src/GraphQl/Type/TypeBuilder.php +++ b/src/GraphQl/Type/TypeBuilder.php @@ -316,8 +316,8 @@ private function getResourceObjectTypeConfiguration(string $shortName, ResourceM $depth = $context['depth'] ?? 0; $wrapped = $context['wrapped'] ?? false; - $ioMetadata = $input ? $operation->getInput() : $operation->getOutput(); - if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) { + $ioMetadata = $input ? $operation->getInputClass() : $operation->getOutputClass(); + if (null !== $ioMetadata ) { $resourceClass = $ioMetadata['class']; } diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index 428c6da7a94..5b06218175d 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -216,13 +216,11 @@ private function getHydraProperties(string $resourceClass, ApiResource $resource continue; } - $inputMetadata = $operation->getInput(); - if (null !== $inputClass = $inputMetadata['class'] ?? null) { + if (null !== $inputClass = $operation->getInputClass()) { $classes[$inputClass] = true; } - $outputMetadata = $operation->getOutput(); - if (null !== $outputClass = $outputMetadata['class'] ?? null) { + if (null !== $outputClass = $operation->getOutputClass()) { $classes[$outputClass] = true; } } @@ -288,11 +286,9 @@ private function getHydraOperation(HttpOperation $operation, string $prefixedSho } $shortName = $operation->getShortName(); - $inputMetadata = $operation->getInput() ?? []; - $outputMetadata = $operation->getOutput() ?? []; - $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false; - $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false; + $inputClass = $operation->getInputClass(); + $outputClass = $operation->getOutputClass(); if ('GET' === $method && $operation instanceof CollectionOperationInterface) { $hydraOperation += [ diff --git a/src/JsonSchema/ResourceMetadataTrait.php b/src/JsonSchema/ResourceMetadataTrait.php index 71988820c76..639d14e9bc4 100644 --- a/src/JsonSchema/ResourceMetadataTrait.php +++ b/src/JsonSchema/ResourceMetadataTrait.php @@ -29,6 +29,7 @@ trait ResourceMetadataTrait private function findOutputClass(string $className, string $type, Operation $operation, ?array $serializerContext): ?string { + // TODO $inputOrOutput = ['class' => $className]; $inputOrOutput = Schema::TYPE_OUTPUT === $type ? ($operation->getOutput() ?? $inputOrOutput) : ($operation->getInput() ?? $inputOrOutput); $forceSubschema = $serializerContext[SchemaFactory::FORCE_SUBSCHEMA] ?? false; diff --git a/src/Mcp/Capability/Registry/Loader.php b/src/Mcp/Capability/Registry/Loader.php index 32bff5b1089..65b113ae30e 100644 --- a/src/Mcp/Capability/Registry/Loader.php +++ b/src/Mcp/Capability/Registry/Loader.php @@ -49,13 +49,13 @@ public function load(RegistryInterface $registry): void foreach ($metadata as $resource) { foreach ($resource->getMcp() ?? [] as $mcp) { if ($mcp instanceof McpTool) { - $inputClass = $mcp->getInput()['class'] ?? $mcp->getClass(); + $inputClass = $mcp->getInputClass(); $inputFormat = array_key_first($mcp->getInputFormats() ?? ['json' => ['application/json']]); $inputSchema = $this->schemaFactory->buildSchema($inputClass, $inputFormat, Schema::TYPE_INPUT, $mcp, null, [SchemaFactory::FORCE_SUBSCHEMA => true]); $outputSchema = null; if (false !== $mcp->getStructuredContent()) { - $outputClass = $mcp->getOutput()['class'] ?? $mcp->getClass(); + $outputClass = $mcp->getOutputClass(); $outputFormat = array_key_first($mcp->getOutputFormats() ?? ['json' => ['application/json']]); $outputSchema = $this->schemaFactory->buildSchema($outputClass, $outputFormat, Schema::TYPE_OUTPUT, $mcp, null, [SchemaFactory::FORCE_SUBSCHEMA => true])->getArrayCopy(); } diff --git a/src/Mcp/State/ToolProvider.php b/src/Mcp/State/ToolProvider.php index dff2e8874eb..59411e746e6 100644 --- a/src/Mcp/State/ToolProvider.php +++ b/src/Mcp/State/ToolProvider.php @@ -35,7 +35,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $data = (object) $context['mcp_data']; - $class = $operation->getInput()['class'] ?? $operation->getClass(); + $class = $operation->getInputClass(); return $this->objectMapper->map($data, $class); } diff --git a/src/Metadata/Metadata.php b/src/Metadata/Metadata.php index 009612c9900..b4baca7aa19 100644 --- a/src/Metadata/Metadata.php +++ b/src/Metadata/Metadata.php @@ -277,6 +277,14 @@ public function getInput(): mixed return $this->input; } + /** + * @return class-string|null + */ + public function getInputClass(): ?string + { + return isset($this->input['class']) ? $this->input['class'] : $this->getClass(); + } + public function withInput(mixed $input): static { $self = clone $this; @@ -290,6 +298,14 @@ public function getOutput(): mixed return $this->output; } + /** + * @return class-string|null + */ + public function getOutputClass(): ?string + { + return isset($this->output['class']) ? $this->inpuoutputt['class'] : $this->getClass(); + } + public function withOutput(mixed $output): static { $self = clone $this; diff --git a/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php index 00d2c6ea252..3d496398fe7 100644 --- a/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php @@ -37,6 +37,7 @@ public function create(string $resourceClass): ResourceMetadataCollection $resourceMetadataCollection = $this->decorated->create($resourceClass); foreach ($resourceMetadataCollection as $key => $resourceMetadata) { + // TODO $resourceMetadata = $resourceMetadata->withInput($this->transformInputOutput($resourceMetadata->getInput())); $resourceMetadata = $resourceMetadata->withOutput($this->transformInputOutput($resourceMetadata->getOutput())); @@ -65,9 +66,7 @@ private function getTransformedOperations(Operations|array $operations, ApiResou $operation = $operation->withOutput(null !== $operation->getOutput() ? $this->transformInputOutput($operation->getOutput()) : $resourceMetadata->getOutput()); if ( - $operation->getInput() - && \array_key_exists('class', $operation->getInput()) - && null === $operation->getInput()['class'] + null === $operation->getInputClass() ) { $operation = $operation->withDeserialize(null === $operation->canDeserialize() ? false : $operation->canDeserialize()); $operation = $operation->withValidate(null === $operation->canValidate() ? false : $operation->canValidate()); @@ -75,9 +74,7 @@ private function getTransformedOperations(Operations|array $operations, ApiResou if ( $operation instanceof HttpOperation - && $operation->getOutput() - && \array_key_exists('class', $operation->getOutput()) - && null === $operation->getOutput()['class'] + && null === $operation->getOutputClass() && null === $operation->getStatus() ) { $operation = $operation->withStatus(204); diff --git a/src/Metadata/Resource/Factory/ObjectMapperMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ObjectMapperMetadataCollectionFactory.php index 37204def6a4..1db559543b7 100644 --- a/src/Metadata/Resource/Factory/ObjectMapperMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ObjectMapperMetadataCollectionFactory.php @@ -57,8 +57,8 @@ public function create(string $resourceClass): ResourceMetadataCollection $entityClass = $options->getModelClass(); } - $class = $operation->getInput()['class'] ?? $operation->getClass(); - $outputClass = $operation->getOutput()['class'] ?? null; + $class = $operation->getInputClass(); + $outputClass = $operation->getOutputClass(); $entityMap = null; // Look for Mapping metadata diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 351b9d68193..1929697b158 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -453,7 +453,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection if ( \in_array($method, ['PATCH', 'PUT', 'POST'], true) - && !(false === ($input = $operation->getInput()) || (\is_array($input) && null === $input['class'])) + && $operation->getInputClass() !== null ) { $content = $openapiOperation->getRequestBody()?->getContent(); if (null === $content) { @@ -501,7 +501,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection */ private function buildOpenApiResponse(array $existingResponses, int|string $status, string $description, Operation $openapiOperation, ?HttpOperation $operation = null, ?array $responseMimeTypes = null, ?array $operationOutputSchemas = null, ?ResourceMetadataCollection $resourceMetadataCollection = null, bool $isErrorResponse = false): Operation { - $noOutput = !$isErrorResponse && \is_array($operation?->getOutput()) && null === $operation->getOutput()['class']; + $noOutput = !$isErrorResponse && null === $operation?->getOutputClass(); $response = $existingResponses[$status] ?? new Response($description); if (null === $response->getDescription()) { diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index 63ee797f426..bb271569235 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -64,8 +64,8 @@ public function createFromRequest(Request $request, bool $normalization, ?array $context['iri_only'] ??= false; $context['request_uri'] = $request->getRequestUri(); $context['uri'] = $request->getUri(); - $context['input'] = $operation->getInput(); - $context['output'] = $operation->getOutput(); + $context['input'] = ['class' => $operation->getInputClass()]; + $context['output'] = ['class' => $operation->getOutputClass()]; // Special case as this is usually handled by our OperationContextTrait, here we want to force the IRI in the response if (!$operation instanceof CollectionOperationInterface && method_exists($operation, 'getItemUriTemplate') && $operation->getItemUriTemplate()) { diff --git a/src/State/Processor/ObjectMapperInputProcessor.php b/src/State/Processor/ObjectMapperInputProcessor.php index 845be27e098..b4e8b976ea8 100644 --- a/src/State/Processor/ObjectMapperInputProcessor.php +++ b/src/State/Processor/ObjectMapperInputProcessor.php @@ -39,7 +39,7 @@ public function __construct( public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): object|array|null { - $class = $operation->getInput()['class'] ?? $operation->getClass(); + $class = $operation->getInputClass(); if ( $data instanceof Response diff --git a/src/State/Processor/ObjectMapperProcessor.php b/src/State/Processor/ObjectMapperProcessor.php index f7bb34a367e..437c7352833 100644 --- a/src/State/Processor/ObjectMapperProcessor.php +++ b/src/State/Processor/ObjectMapperProcessor.php @@ -40,7 +40,7 @@ public function __construct( public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): object|array|null { - $class = $operation->getInput()['class'] ?? $operation->getClass(); + $class = $operation->getInputClass(); if ( $data instanceof Response diff --git a/src/State/Provider/ContentNegotiationProvider.php b/src/State/Provider/ContentNegotiationProvider.php index 42e81252282..05f292b8994 100644 --- a/src/State/Provider/ContentNegotiationProvider.php +++ b/src/State/Provider/ContentNegotiationProvider.php @@ -98,8 +98,7 @@ private function flattenMimeTypes(array $formats): array private function getInputFormat(HttpOperation $operation, Request $request): ?string { if ( - false === ($input = $operation->getInput()) - || (\is_array($input) && null === $input['class']) + null === $operation->getInputClass() || false === $operation->canDeserialize() ) { return null; diff --git a/src/State/Provider/ObjectMapperProvider.php b/src/State/Provider/ObjectMapperProvider.php index 9986f43954f..0896ea77a55 100644 --- a/src/State/Provider/ObjectMapperProvider.php +++ b/src/State/Provider/ObjectMapperProvider.php @@ -41,7 +41,7 @@ public function __construct( public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { $data = $this->decorated->provide($operation, $uriVariables, $context); - $class = $operation->getOutput()['class'] ?? $operation->getClass(); + $class = $operation->getOutputClass(); if (!$this->objectMapper || !$operation->canMap()) { return $data; diff --git a/src/State/Util/HttpResponseHeadersTrait.php b/src/State/Util/HttpResponseHeadersTrait.php index e3f4fb01a62..1e17edbd8fa 100644 --- a/src/State/Util/HttpResponseHeadersTrait.php +++ b/src/State/Util/HttpResponseHeadersTrait.php @@ -78,7 +78,7 @@ private function getHeaders(Request $request, HttpOperation $operation, array $c $method = $request->getMethod(); $originalData = $context['original_data'] ?? null; - $outputMetadata = $operation->getOutput() ?? ['class' => $operation->getClass()]; + $outputMetadata = ['class' => $operation->getOutputClass()]; $hasOutput = \is_array($outputMetadata) && \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class']; $hasData = !$hasOutput ? false : ($this->resourceClassResolver && $originalData && \is_object($originalData) && $this->resourceClassResolver->isResourceClass($this->getObjectClass($originalData))); diff --git a/src/State/Util/HttpResponseStatusTrait.php b/src/State/Util/HttpResponseStatusTrait.php index 89b9156c3ea..904e8c6285d 100644 --- a/src/State/Util/HttpResponseStatusTrait.php +++ b/src/State/Util/HttpResponseStatusTrait.php @@ -40,7 +40,7 @@ private function getStatus(Request $request, HttpOperation $operation, array $co $status = $operation->getStatus(); $method = $request->getMethod(); - $outputMetadata = $operation->getOutput() ?? ['class' => $operation->getClass()]; + $outputMetadata = ['class' => $operation->getOutputClass()]; $hasOutput = \is_array($outputMetadata) && \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class']; $originalData = $context['original_data'] ?? null; $hasData = !$hasOutput ? false : ($this->resourceClassResolver && $originalData && \is_object($originalData) && $this->resourceClassResolver->isResourceClass($this->getObjectClass($originalData))); diff --git a/tests/Fixtures/TestBundle/State/DummyCollectionDtoProvider.php b/tests/Fixtures/TestBundle/State/DummyCollectionDtoProvider.php index 7aa5703655e..6b31e860d63 100644 --- a/tests/Fixtures/TestBundle/State/DummyCollectionDtoProvider.php +++ b/tests/Fixtures/TestBundle/State/DummyCollectionDtoProvider.php @@ -23,7 +23,7 @@ class DummyCollectionDtoProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context = []): array { - $class = $operation->getOutput()['class']; + $class = $operation->getOutputClass(); switch ($class) { case DummyCollectionDtoOutput::class: case DummyFooCollectionDto::class: