From 5e6ebb4882397543250f135fe8f7610bc297d21c Mon Sep 17 00:00:00 2001 From: soyuka Date: Wed, 18 Oct 2023 19:13:38 +0200 Subject: [PATCH] fix(jsonschema): restore type factory usage fixes #5896 --- phpstan.neon.dist | 2 +- src/JsonSchema/SchemaFactory.php | 10 ++++- src/JsonSchema/TypeFactory.php | 7 ++++ src/OpenApi/Factory/OpenApiFactory.php | 2 +- .../Bundle/Resources/config/json_schema.xml | 2 +- .../TestBundle/ApiResource/Issue5896/Foo.php | 25 ++++++++++++ .../ApiResource/Issue5896/LocalDate.php | 18 +++++++++ .../Issue5896/TypeFactoryDecorator.php | 38 +++++++++++++++++++ tests/Fixtures/app/config/config_common.yml | 5 +++ .../Command/JsonSchemaGenerateCommandTest.php | 14 +++++++ 10 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 tests/Fixtures/TestBundle/ApiResource/Issue5896/Foo.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/Issue5896/LocalDate.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/Issue5896/TypeFactoryDecorator.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 17e3d3db072..d6dee67d939 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -26,7 +26,7 @@ parameters: - src/Symfony/Bundle/DependencyInjection/Configuration.php # Templates for Maker - src/Symfony/Maker/Resources/skeleton - - src/*/vendor + - **vendor** earlyTerminatingMethodCalls: PHPUnit\Framework\Constraint\Constraint: - fail diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index 179a7cb11c7..2874c9ac9f7 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -37,7 +37,7 @@ final class SchemaFactory implements SchemaFactoryInterface { use ResourceClassInfoTrait; private array $distinctFormats = []; - + private ?TypeFactoryInterface $typeFactory = null; // Edge case where the related resource is not readable (for example: NotExposed) but we have groups to read the whole related object public const FORCE_SUBSCHEMA = '_api_subschema_force_readable_link'; public const OPENAPI_DEFINITION_NAME = 'openapi_definition_name'; @@ -45,7 +45,7 @@ final class SchemaFactory implements SchemaFactoryInterface public function __construct(?TypeFactoryInterface $typeFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ?NameConverterInterface $nameConverter = null, ResourceClassResolverInterface $resourceClassResolver = null) { if ($typeFactory) { - trigger_deprecation('api-platform/core', '3.2', sprintf('The "%s" is not needed anymore and will not be used anymore.', TypeFactoryInterface::class)); + $this->typeFactory = $typeFactory; } $this->resourceMetadataFactory = $resourceMetadataFactory; @@ -198,6 +198,12 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str $subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema foreach ($types as $type) { + // TODO: in 3.3 add trigger_deprecation() as type factories are not used anymore, we moved this logic to SchemaPropertyMetadataFactory so that it gets cached + if ($typeFromFactory = $this->typeFactory?->getType($type, 'jsonschema', $propertyMetadata->isReadableLink(), $serializerContext)) { + $propertySchema = $typeFromFactory; + break; + } + $isCollection = $type->isCollection(); if ($isCollection) { $valueType = $type->getCollectionValueTypes()[0] ?? null; diff --git a/src/JsonSchema/TypeFactory.php b/src/JsonSchema/TypeFactory.php index 05e695a707b..872737ce547 100644 --- a/src/JsonSchema/TypeFactory.php +++ b/src/JsonSchema/TypeFactory.php @@ -23,6 +23,8 @@ /** * {@inheritdoc} * + * @deprecated since 3.3 https://github.com/api-platform/core/pull/5470 + * * @author Kévin Dunglas */ final class TypeFactory implements TypeFactoryInterface @@ -46,6 +48,11 @@ public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void */ public function getType(Type $type, string $format = 'json', bool $readableLink = null, array $serializerContext = null, Schema $schema = null): array { + if ('jsonschema' === $format) { + return []; + } + + // TODO: OpenApiFactory uses this to compute filter types if ($type->isCollection()) { $keyType = $type->getCollectionKeyTypes()[0] ?? null; $subType = ($type->getCollectionValueTypes()[0] ?? null) ?? new Type($type->getBuiltinType(), false, $type->getClassName(), false); diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index d6014da2063..6f18de73814 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -573,7 +573,7 @@ private function getFiltersParameters(CollectionOperationInterface|HttpOperation } foreach ($filter->getDescription($entityClass) as $name => $data) { - $schema = $data['schema'] ?? (\in_array($data['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)) : ['type' => 'string']); + $schema = $data['schema'] ?? (\in_array($data['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false), 'openapi') : ['type' => 'string']); $parameters[] = new Parameter( $name, diff --git a/src/Symfony/Bundle/Resources/config/json_schema.xml b/src/Symfony/Bundle/Resources/config/json_schema.xml index 4081591327e..2ad6fe11746 100644 --- a/src/Symfony/Bundle/Resources/config/json_schema.xml +++ b/src/Symfony/Bundle/Resources/config/json_schema.xml @@ -16,7 +16,7 @@ - null + diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5896/Foo.php b/tests/Fixtures/TestBundle/ApiResource/Issue5896/Foo.php new file mode 100644 index 00000000000..59d70e6e924 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5896/Foo.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5896; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Get; + +#[Get] +class Foo +{ + #[ApiProperty(readable: false, writable: false, identifier: true)] + public ?int $id = null; + public ?LocalDate $expiration; +} diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5896/LocalDate.php b/tests/Fixtures/TestBundle/ApiResource/Issue5896/LocalDate.php new file mode 100644 index 00000000000..5a63f34d1ac --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5896/LocalDate.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5896; + +class LocalDate +{ +} diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5896/TypeFactoryDecorator.php b/tests/Fixtures/TestBundle/ApiResource/Issue5896/TypeFactoryDecorator.php new file mode 100644 index 00000000000..ff28992f826 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5896/TypeFactoryDecorator.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5896; + +use ApiPlatform\JsonSchema\Schema; +use ApiPlatform\JsonSchema\TypeFactoryInterface; +use Symfony\Component\PropertyInfo\Type; + +class TypeFactoryDecorator implements TypeFactoryInterface +{ + public function __construct( + private readonly TypeFactoryInterface $decorated, + ) { + } + + public function getType(Type $type, string $format = 'json', bool $readableLink = null, array $serializerContext = null, Schema $schema = null): array + { + if (is_a($type->getClassName(), LocalDate::class, true)) { + return [ + 'type' => 'string', + 'format' => 'date', + ]; + } + + return $this->decorated->getType($type, $format, $readableLink, $serializerContext, $schema); + } +} diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml index 449c1d268f3..71fe2a590bb 100644 --- a/tests/Fixtures/app/config/config_common.yml +++ b/tests/Fixtures/app/config/config_common.yml @@ -457,3 +457,8 @@ services: ApiPlatform\Tests\Fixtures\TestBundle\State\ODMLinkHandledDummyLinksHandler: tags: - {name: 'api_platform.doctrine.odm.links_handler'} + + ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5896\TypeFactoryDecorator: + decorates: 'api_platform.json_schema.type_factory' + arguments: + $decorated: '@.inner' diff --git a/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php b/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php index 1c338717d5c..75ed827f8fb 100644 --- a/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php +++ b/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php @@ -124,4 +124,18 @@ public function testArraySchemaWithReference(): void '$ref' => '#/definitions/TestEntity.jsonld-write', ]); } + + /** + * TODO: add deprecation (TypeFactory will be deprecated in api platform 3.3). + * + * @group legacy + */ + public function testArraySchemaWithTypeFactory(): void + { + $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => 'ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5896\Foo', '--type' => 'output']); + $result = $this->tester->getDisplay(); + $json = json_decode($result, associative: true); + + $this->assertEquals($json['definitions']['Foo.jsonld']['properties']['expiration'], ['type' => 'string', 'format' => 'date']); + } }