Skip to content

Commit c4ba28e

Browse files
committed
Heavily refactored the property metadata attribute logic
The new method is much more universal and fixes issue #862
1 parent b38ef8e commit c4ba28e

File tree

5 files changed

+202
-123
lines changed

5 files changed

+202
-123
lines changed

src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php

Lines changed: 0 additions & 116 deletions
This file was deleted.

src/ApiPlatform/DocumentedAPIProperty.php renamed to src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
declare(strict_types=1);
2222

2323

24-
namespace App\ApiPlatform;
24+
namespace App\ApiPlatform\DocumentedAPIProperties;
25+
26+
use ApiPlatform\Metadata\ApiProperty;
2527

2628
/**
2729
* 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 @@ public function __construct(
6466
)
6567
{
6668
}
69+
70+
public function toAPIProperty(bool $use_swagger = false): ApiProperty
71+
{
72+
$openApiContext = [];
73+
74+
if (false === $this->writeable) {
75+
$openApiContext['readOnly'] = true;
76+
}
77+
if (!$use_swagger && false === $this->readable) {
78+
$openApiContext['writeOnly'] = true;
79+
}
80+
if (null !== $description = $this->description) {
81+
$openApiContext['description'] = $description;
82+
}
83+
84+
$deprecationReason = $this->deprecationReason;
85+
86+
// see https://github.com/json-schema-org/json-schema-spec/pull/737
87+
if (!$use_swagger && null !== $deprecationReason) {
88+
$openApiContext['deprecated'] = true;
89+
}
90+
91+
if (!empty($default = $this->default)) {
92+
if ($default instanceof \BackedEnum) {
93+
$default = $default->value;
94+
}
95+
$openApiContext['default'] = $default;
96+
}
97+
98+
if (!empty($example = $this->example)) {
99+
$openApiContext['example'] = $example;
100+
}
101+
102+
if (!isset($openApiContext['example']) && isset($openApiContext['default'])) {
103+
$openApiContext['example'] = $openApiContext['default'];
104+
}
105+
106+
$openApiContext['type'] = $this->type;
107+
$openApiContext['nullable'] = $this->nullable;
108+
109+
110+
111+
return new ApiProperty(
112+
description: $this->description,
113+
readable: $this->readable,
114+
writable: $this->writeable,
115+
openapiContext: $openApiContext,
116+
types: $this->type,
117+
property: $this->property
118+
);
119+
}
67120
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
24+
namespace App\ApiPlatform\DocumentedAPIProperties;
25+
26+
use ApiPlatform\Metadata\ApiProperty;
27+
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
28+
use ReflectionClass;
29+
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
30+
31+
/**
32+
* This decorator adds the virtual properties defined by the DocumentedAPIProperty attribute to the property metadata
33+
* which then get picked up by the openapi schema generator
34+
*/
35+
#[AsDecorator('api_platform.metadata.property.metadata_factory')]
36+
class PropertyMetadataFactory implements PropertyMetadataFactoryInterface
37+
{
38+
public function __construct(private PropertyMetadataFactoryInterface $decorated)
39+
{
40+
}
41+
42+
public function create(string $resourceClass, string $property, array $options = []): ApiProperty
43+
{
44+
$metadata = $this->decorated->create($resourceClass, $property, $options);
45+
46+
//Only become active in the context of the openapi schema generation
47+
if (!isset($options['schema_type'])) {
48+
return $metadata;
49+
}
50+
51+
if (!class_exists($resourceClass)) {
52+
return $metadata;
53+
}
54+
55+
$refClass = new ReflectionClass($resourceClass);
56+
$attributes = $refClass->getAttributes(DocumentedAPIProperty::class);
57+
58+
//Look for the DocumentedAPIProperty attribute with the given property name
59+
foreach ($attributes as $attribute) {
60+
/** @var DocumentedAPIProperty $api_property */
61+
$api_property = $attribute->newInstance();
62+
//If attribute not matches the property name, skip it
63+
if ($api_property->property !== $property) {
64+
continue;
65+
}
66+
67+
//Return the virtual property
68+
return $api_property->toAPIProperty();
69+
}
70+
71+
return $metadata;
72+
}
73+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
24+
namespace App\ApiPlatform\DocumentedAPIProperties;
25+
26+
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
27+
use ApiPlatform\Metadata\Property\PropertyNameCollection;
28+
use ReflectionClass;
29+
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
30+
31+
/**
32+
* This decorator adds the virtual property names defined by the DocumentedAPIProperty attribute to the property name collection
33+
* which then get picked up by the openapi schema generator
34+
*/
35+
#[AsDecorator('api_platform.metadata.property.name_collection_factory')]
36+
class PropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
37+
{
38+
public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated)
39+
{
40+
}
41+
42+
public function create(string $resourceClass, array $options = []): PropertyNameCollection
43+
{
44+
// Get the default properties from the decorated service
45+
$propertyNames = $this->decorated->create($resourceClass, $options);
46+
47+
//Only become active in the context of the openapi schema generation
48+
if (!isset($options['schema_type'])) {
49+
return $propertyNames;
50+
}
51+
52+
if (!class_exists($resourceClass)) {
53+
return $propertyNames;
54+
}
55+
56+
$properties = iterator_to_array($propertyNames);
57+
58+
$refClass = new ReflectionClass($resourceClass);
59+
60+
foreach ($refClass->getAttributes(DocumentedAPIProperty::class) as $attribute) {
61+
/** @var DocumentedAPIProperty $instance */
62+
$instance = $attribute->newInstance();
63+
$properties[] = $instance->property;
64+
}
65+
66+
return new PropertyNameCollection($properties);
67+
}
68+
}

src/Entity/Attachments/Attachment.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,24 @@
3333
use ApiPlatform\Metadata\GetCollection;
3434
use ApiPlatform\Metadata\Patch;
3535
use ApiPlatform\Metadata\Post;
36-
use App\ApiPlatform\DocumentedAPIProperty;
36+
use App\ApiPlatform\DocumentedAPIProperties\DocumentedAPIProperty;
3737
use App\ApiPlatform\Filter\EntityFilter;
3838
use App\ApiPlatform\Filter\LikeFilter;
3939
use App\ApiPlatform\HandleAttachmentsUploadsProcessor;
40-
use App\Repository\AttachmentRepository;
41-
use App\EntityListeners\AttachmentDeleteListener;
42-
use Doctrine\DBAL\Types\Types;
4340
use App\Entity\Base\AbstractNamedDBElement;
41+
use App\EntityListeners\AttachmentDeleteListener;
42+
use App\Repository\AttachmentRepository;
4443
use App\Validator\Constraints\Selectable;
44+
use Doctrine\DBAL\Types\Types;
4545
use Doctrine\ORM\Mapping as ORM;
46+
use InvalidArgumentException;
47+
use LogicException;
4648
use Symfony\Component\Serializer\Annotation\Groups;
4749
use Symfony\Component\Serializer\Annotation\SerializedName;
4850
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
4951
use Symfony\Component\Validator\Constraints as Assert;
52+
5053
use function in_array;
51-
use InvalidArgumentException;
52-
use LogicException;
5354

5455
/**
5556
* Class Attachment.

0 commit comments

Comments
 (0)