diff --git a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php deleted file mode 100644 index 07ff0f1b..00000000 --- a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php +++ /dev/null @@ -1,116 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\ApiPlatform; - -use ApiPlatform\JsonSchema\Schema; -use ApiPlatform\JsonSchema\SchemaFactoryInterface; -use ApiPlatform\Metadata\Operation; -use Symfony\Component\DependencyInjection\Attribute\AsDecorator; - -/** - * This decorator adds the properties given by DocumentedAPIProperty attributes on the classes to the schema. - */ -#[AsDecorator('api_platform.json_schema.schema_factory')] -class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterface -{ - - public function __construct(private readonly SchemaFactoryInterface $decorated) - { - } - - public function buildSchema( - string $className, - string $format = 'json', - string $type = Schema::TYPE_OUTPUT, - ?Operation $operation = null, - ?Schema $schema = null, - ?array $serializerContext = null, - bool $forceCollection = false - ): Schema { - - - $schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection); - - //Check if there is are DocumentedAPIProperty attributes on the class - $reflectionClass = new \ReflectionClass($className); - $attributes = $reflectionClass->getAttributes(DocumentedAPIProperty::class); - foreach ($attributes as $attribute) { - /** @var DocumentedAPIProperty $api_property */ - $api_property = $attribute->newInstance(); - $this->addPropertyToSchema($schema, $api_property->schemaName, $api_property->property, - $api_property, $serializerContext ?? [], $format); - } - - return $schema; - } - - private function addPropertyToSchema(Schema $schema, string $definitionName, string $normalizedPropertyName, DocumentedAPIProperty $propertyMetadata, array $serializerContext, string $format): void - { - $version = $schema->getVersion(); - $swagger = Schema::VERSION_SWAGGER === $version; - - $propertySchema = []; - - if (false === $propertyMetadata->writeable) { - $propertySchema['readOnly'] = true; - } - if (!$swagger && false === $propertyMetadata->readable) { - $propertySchema['writeOnly'] = true; - } - if (null !== $description = $propertyMetadata->description) { - $propertySchema['description'] = $description; - } - - $deprecationReason = $propertyMetadata->deprecationReason; - - // see https://github.com/json-schema-org/json-schema-spec/pull/737 - if (!$swagger && null !== $deprecationReason) { - $propertySchema['deprecated'] = true; - } - - if (!empty($default = $propertyMetadata->default)) { - if ($default instanceof \BackedEnum) { - $default = $default->value; - } - $propertySchema['default'] = $default; - } - - if (!empty($example = $propertyMetadata->example)) { - $propertySchema['example'] = $example; - } - - if (!isset($propertySchema['example']) && isset($propertySchema['default'])) { - $propertySchema['example'] = $propertySchema['default']; - } - - $propertySchema['type'] = $propertyMetadata->type; - $propertySchema['nullable'] = $propertyMetadata->nullable; - - $propertySchema = new \ArrayObject($propertySchema); - - $schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema; - } - - -} \ No newline at end of file diff --git a/src/ApiPlatform/DocumentedAPIProperty.php b/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php similarity index 59% rename from src/ApiPlatform/DocumentedAPIProperty.php rename to src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php index c4c0a337..57d275be 100644 --- a/src/ApiPlatform/DocumentedAPIProperty.php +++ b/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php @@ -21,7 +21,9 @@ declare(strict_types=1); -namespace App\ApiPlatform; +namespace App\ApiPlatform\DocumentedAPIProperties; + +use ApiPlatform\Metadata\ApiProperty; /** * When this attribute is applied to a class, an property will be added to the API documentation using the given parameters. @@ -64,4 +66,55 @@ final class DocumentedAPIProperty ) { } + + public function toAPIProperty(bool $use_swagger = false): ApiProperty + { + $openApiContext = []; + + if (false === $this->writeable) { + $openApiContext['readOnly'] = true; + } + if (!$use_swagger && false === $this->readable) { + $openApiContext['writeOnly'] = true; + } + if (null !== $description = $this->description) { + $openApiContext['description'] = $description; + } + + $deprecationReason = $this->deprecationReason; + + // see https://github.com/json-schema-org/json-schema-spec/pull/737 + if (!$use_swagger && null !== $deprecationReason) { + $openApiContext['deprecated'] = true; + } + + if (!empty($default = $this->default)) { + if ($default instanceof \BackedEnum) { + $default = $default->value; + } + $openApiContext['default'] = $default; + } + + if (!empty($example = $this->example)) { + $openApiContext['example'] = $example; + } + + if (!isset($openApiContext['example']) && isset($openApiContext['default'])) { + $openApiContext['example'] = $openApiContext['default']; + } + + $openApiContext['type'] = $this->type; + $openApiContext['nullable'] = $this->nullable; + + + + return new ApiProperty( + description: $this->description, + readable: $this->readable, + writable: $this->writeable, + openapiContext: $openApiContext, + types: $this->type, + property: $this->property + ); + } } \ No newline at end of file diff --git a/src/ApiPlatform/DocumentedAPIProperties/PropertyMetadataFactory.php b/src/ApiPlatform/DocumentedAPIProperties/PropertyMetadataFactory.php new file mode 100644 index 00000000..49e9a031 --- /dev/null +++ b/src/ApiPlatform/DocumentedAPIProperties/PropertyMetadataFactory.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\DocumentedAPIProperties; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ReflectionClass; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +/** + * This decorator adds the virtual properties defined by the DocumentedAPIProperty attribute to the property metadata + * which then get picked up by the openapi schema generator + */ +#[AsDecorator('api_platform.metadata.property.metadata_factory')] +class PropertyMetadataFactory implements PropertyMetadataFactoryInterface +{ + public function __construct(private PropertyMetadataFactoryInterface $decorated) + { + } + + public function create(string $resourceClass, string $property, array $options = []): ApiProperty + { + $metadata = $this->decorated->create($resourceClass, $property, $options); + + //Only become active in the context of the openapi schema generation + if (!isset($options['schema_type'])) { + return $metadata; + } + + if (!class_exists($resourceClass)) { + return $metadata; + } + + $refClass = new ReflectionClass($resourceClass); + $attributes = $refClass->getAttributes(DocumentedAPIProperty::class); + + //Look for the DocumentedAPIProperty attribute with the given property name + foreach ($attributes as $attribute) { + /** @var DocumentedAPIProperty $api_property */ + $api_property = $attribute->newInstance(); + //If attribute not matches the property name, skip it + if ($api_property->property !== $property) { + continue; + } + + //Return the virtual property + return $api_property->toAPIProperty(); + } + + return $metadata; + } +} \ No newline at end of file diff --git a/src/ApiPlatform/DocumentedAPIProperties/PropertyNameCollectionFactory.php b/src/ApiPlatform/DocumentedAPIProperties/PropertyNameCollectionFactory.php new file mode 100644 index 00000000..3157cbf3 --- /dev/null +++ b/src/ApiPlatform/DocumentedAPIProperties/PropertyNameCollectionFactory.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\DocumentedAPIProperties; + +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Property\PropertyNameCollection; +use ReflectionClass; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +/** + * This decorator adds the virtual property names defined by the DocumentedAPIProperty attribute to the property name collection + * which then get picked up by the openapi schema generator + */ +#[AsDecorator('api_platform.metadata.property.name_collection_factory')] +class PropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface +{ + public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated) + { + } + + public function create(string $resourceClass, array $options = []): PropertyNameCollection + { + // Get the default properties from the decorated service + $propertyNames = $this->decorated->create($resourceClass, $options); + + //Only become active in the context of the openapi schema generation + if (!isset($options['schema_type'])) { + return $propertyNames; + } + + if (!class_exists($resourceClass)) { + return $propertyNames; + } + + $properties = iterator_to_array($propertyNames); + + $refClass = new ReflectionClass($resourceClass); + + foreach ($refClass->getAttributes(DocumentedAPIProperty::class) as $attribute) { + /** @var DocumentedAPIProperty $instance */ + $instance = $attribute->newInstance(); + $properties[] = $instance->property; + } + + return new PropertyNameCollection($properties); + } +} \ No newline at end of file diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 4847eea5..00cf581a 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -33,23 +33,24 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; -use App\ApiPlatform\DocumentedAPIProperty; +use App\ApiPlatform\DocumentedAPIProperties\DocumentedAPIProperty; use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; use App\ApiPlatform\HandleAttachmentsUploadsProcessor; -use App\Repository\AttachmentRepository; -use App\EntityListeners\AttachmentDeleteListener; -use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractNamedDBElement; +use App\EntityListeners\AttachmentDeleteListener; +use App\Repository\AttachmentRepository; use App\Validator\Constraints\Selectable; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use InvalidArgumentException; +use LogicException; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Attribute\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; + use function in_array; -use InvalidArgumentException; -use LogicException; /** * Class Attachment.