Skip to content

Commit b5fae3a

Browse files
authored
Merge pull request #2386 from aaa2000/swagger-default-property-value
Use default value of PHP properties to Swagger doc
2 parents b72a90b + a83c118 commit b5fae3a

File tree

13 files changed

+361
-3
lines changed

13 files changed

+361
-3
lines changed

src/Annotation/ApiProperty.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ final class ApiProperty
7676
*/
7777
public $identifier;
7878

79+
/**
80+
* @var mixed
81+
*/
82+
public $default;
83+
84+
/**
85+
* @var mixed
86+
*/
87+
public $example;
88+
7989
/**
8090
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
8191
*

src/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ public function create(string $resourceClass, string $property, array $options =
7676
$propertyMetadata = $propertyMetadata->withIdentifier(false);
7777
}
7878

79+
if ($doctrineClassMetadata instanceof ClassMetadataInfo && \in_array($property, $doctrineClassMetadata->getFieldNames(), true)) {
80+
$fieldMapping = $doctrineClassMetadata->getFieldMapping($property);
81+
if (\array_key_exists('options', $fieldMapping) && \array_key_exists('default', $fieldMapping['options'])) {
82+
$propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default']);
83+
}
84+
}
85+
7986
return $propertyMetadata;
8087
}
8188
}

src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
<argument type="service" id="api_platform.metadata.property.metadata_factory.cached.inner" />
7575
</service>
7676

77+
<service id="api_platform.metadata.property.metadata_factory.default_property" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="30" class="ApiPlatform\Core\Metadata\Property\Factory\DefaultPropertyMetadataFactory" public="false">
78+
<argument type="service" id="api_platform.metadata.property.metadata_factory.default_property.inner" />
79+
</service>
80+
7781
<service id="ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface" alias="api_platform.metadata.property.metadata_factory" />
7882

7983
<!-- Cache -->

src/JsonSchema/SchemaFactory.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,18 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
176176
$propertySchema['externalDocs'] = ['url' => $iri];
177177
}
178178

179+
if (!isset($propertySchema['default']) && null !== $default = $propertyMetadata->getDefault()) {
180+
$propertySchema['default'] = $default;
181+
}
182+
183+
if (!isset($propertySchema['example']) && null !== $example = $propertyMetadata->getExample()) {
184+
$propertySchema['example'] = $example;
185+
}
186+
187+
if (!isset($propertySchema['example']) && isset($propertySchema['default'])) {
188+
$propertySchema['example'] = $propertySchema['default'];
189+
}
190+
179191
$valueSchema = [];
180192
if (null !== $type = $propertyMetadata->getType()) {
181193
$isCollection = $type->isCollection();

src/Metadata/Property/Factory/AnnotationPropertyMetadataFactory.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,16 @@ private function createMetadata(ApiProperty $annotation, PropertyMetadata $paren
112112
$annotation->identifier,
113113
$annotation->iri,
114114
null,
115-
$annotation->attributes
115+
$annotation->attributes,
116+
null,
117+
null,
118+
$annotation->default,
119+
$annotation->example
116120
);
117121
}
118122

119123
$propertyMetadata = $parentPropertyMetadata;
120-
foreach ([['get', 'description'], ['is', 'readable'], ['is', 'writable'], ['is', 'readableLink'], ['is', 'writableLink'], ['is', 'required'], ['get', 'iri'], ['is', 'identifier'], ['get', 'attributes']] as $property) {
124+
foreach ([['get', 'description'], ['is', 'readable'], ['is', 'writable'], ['is', 'readableLink'], ['is', 'writableLink'], ['is', 'required'], ['get', 'iri'], ['is', 'identifier'], ['get', 'attributes'], ['get', 'default'], ['get', 'example']] as $property) {
121125
if (null !== $value = $annotation->{$property[1]}) {
122126
$propertyMetadata = $this->createWith($propertyMetadata, $property, $value);
123127
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[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 ApiPlatform\Core\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Exception\PropertyNotFoundException;
17+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
18+
19+
/**
20+
* Populates defaults values of the ressource properties using the default PHP values of properties.
21+
*/
22+
final class DefaultPropertyMetadataFactory implements PropertyMetadataFactoryInterface
23+
{
24+
private $decorated;
25+
26+
public function __construct(PropertyMetadataFactoryInterface $decorated = null)
27+
{
28+
$this->decorated = $decorated;
29+
}
30+
31+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
32+
{
33+
if (null === $this->decorated) {
34+
$propertyMetadata = new PropertyMetadata();
35+
} else {
36+
try {
37+
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
38+
} catch (PropertyNotFoundException $propertyNotFoundException) {
39+
$propertyMetadata = new PropertyMetadata();
40+
}
41+
}
42+
43+
try {
44+
$reflectionClass = new \ReflectionClass($resourceClass);
45+
} catch (\ReflectionException $reflectionException) {
46+
return $propertyMetadata;
47+
}
48+
49+
$defaultProperties = $reflectionClass->getDefaultProperties();
50+
51+
if (!\array_key_exists($property, $defaultProperties) || null === ($defaultProperty = $defaultProperties[$property])) {
52+
return $propertyMetadata;
53+
}
54+
55+
return $propertyMetadata->withDefault($defaultProperty);
56+
}
57+
}

src/Metadata/Property/PropertyMetadata.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ final class PropertyMetadata
3838
private $attributes;
3939
private $subresource;
4040
private $initializable;
41+
/**
42+
* @var null
43+
*/
44+
private $default;
45+
/**
46+
* @var null
47+
*/
48+
private $example;
4149

42-
public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null, bool $initializable = null)
50+
public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null, bool $initializable = null, $default = null, $example = null)
4351
{
4452
$this->type = $type;
4553
$this->description = $description;
@@ -57,6 +65,8 @@ public function __construct(Type $type = null, string $description = null, bool
5765
$this->attributes = $attributes;
5866
$this->subresource = $subresource;
5967
$this->initializable = $initializable;
68+
$this->default = $default;
69+
$this->example = $example;
6070
}
6171

6272
/**
@@ -349,4 +359,42 @@ public function withInitializable(bool $initializable): self
349359

350360
return $metadata;
351361
}
362+
363+
/**
364+
* Returns the default value of the property or NULL if the property doesn't have a default value.
365+
*/
366+
public function getDefault()
367+
{
368+
return $this->default;
369+
}
370+
371+
/**
372+
* Returns a new instance with the given default value for the property.
373+
*/
374+
public function withDefault($default): self
375+
{
376+
$metadata = clone $this;
377+
$metadata->default = $default;
378+
379+
return $metadata;
380+
}
381+
382+
/**
383+
* Returns an example of the value of the property.
384+
*/
385+
public function getExample()
386+
{
387+
return $this->example;
388+
}
389+
390+
/**
391+
* Returns a new instance with the given example.
392+
*/
393+
public function withExample($example): self
394+
{
395+
$metadata = clone $this;
396+
$metadata->example = $example;
397+
398+
return $metadata;
399+
}
352400
}

tests/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
1818
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
1919
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
20+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyPropertyWithDefaultValue;
2021
use Doctrine\Common\Persistence\ManagerRegistry;
2122
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
2223
use Doctrine\Common\Persistence\ObjectManager;
@@ -73,6 +74,7 @@ public function testCreateIsWritable()
7374

7475
$classMetadata = $this->prophesize(ClassMetadataInfo::class);
7576
$classMetadata->getIdentifier()->shouldBeCalled()->willReturn(['id']);
77+
$classMetadata->getFieldNames()->shouldBeCalled()->willReturn([]);
7678

7779
$objectManager = $this->prophesize(ObjectManager::class);
7880
$objectManager->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadata->reveal());
@@ -88,6 +90,31 @@ public function testCreateIsWritable()
8890
$this->assertEquals($doctrinePropertyMetadata->isWritable(), false);
8991
}
9092

93+
public function testCreateWithDefaultOption()
94+
{
95+
$propertyMetadata = new PropertyMetadata();
96+
97+
$propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
98+
$propertyMetadataFactory->create(DummyPropertyWithDefaultValue::class, 'dummyDefaultOption', [])->shouldBeCalled()->willReturn($propertyMetadata);
99+
100+
$classMetadata = new ClassMetadataInfo(DummyPropertyWithDefaultValue::class);
101+
$classMetadata->fieldMappings = [
102+
'dummyDefaultOption' => ['options' => ['default' => 'default value']],
103+
];
104+
105+
$objectManager = $this->prophesize(ObjectManager::class);
106+
$objectManager->getClassMetadata(DummyPropertyWithDefaultValue::class)->shouldBeCalled()->willReturn($classMetadata);
107+
108+
$managerRegistry = $this->prophesize(ManagerRegistry::class);
109+
$managerRegistry->getManagerForClass(DummyPropertyWithDefaultValue::class)->shouldBeCalled()->willReturn($objectManager->reveal());
110+
111+
$doctrineOrmPropertyMetadataFactory = new DoctrineOrmPropertyMetadataFactory($managerRegistry->reveal(), $propertyMetadataFactory->reveal());
112+
113+
$doctrinePropertyMetadata = $doctrineOrmPropertyMetadataFactory->create(DummyPropertyWithDefaultValue::class, 'dummyDefaultOption');
114+
115+
$this->assertEquals($doctrinePropertyMetadata->getDefault(), 'default value');
116+
}
117+
91118
public function testCreateClassMetadataInfo()
92119
{
93120
$propertyMetadata = new PropertyMetadata();
@@ -98,6 +125,7 @@ public function testCreateClassMetadataInfo()
98125
$classMetadata = $this->prophesize(ClassMetadataInfo::class);
99126
$classMetadata->getIdentifier()->shouldBeCalled()->willReturn(['id']);
100127
$classMetadata->isIdentifierNatural()->shouldBeCalled()->willReturn(true);
128+
$classMetadata->getFieldNames()->shouldBeCalled()->willReturn([]);
101129

102130
$objectManager = $this->prophesize(ObjectManager::class);
103131
$objectManager->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadata->reveal());

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,7 @@ private function getPartialContainerBuilderProphecy($configuration = null)
936936
'api_platform.metadata.resource.metadata_factory.xml',
937937
'api_platform.metadata.resource.name_collection_factory.cached',
938938
'api_platform.metadata.resource.name_collection_factory.xml',
939+
'api_platform.metadata.property.metadata_factory.default_property',
939940
'api_platform.negotiator',
940941
'api_platform.operation_method_resolver',
941942
'api_platform.operation_path_resolver.custom',
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[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 ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
use Symfony\Component\Serializer\Annotation\Groups;
19+
20+
/**
21+
* DummyPropertyWithDefaultValue.
22+
*
23+
* @ORM\Entity
24+
*
25+
* @ApiResource(attributes={
26+
* "normalization_context"={"groups"={"dummy_read"}},
27+
* "denormalization_context"={"groups"={"dummy_write"}}
28+
* })
29+
*/
30+
class DummyPropertyWithDefaultValue
31+
{
32+
/**
33+
* @var int
34+
*
35+
* @ORM\Id
36+
* @ORM\Column(type="integer")
37+
* @ORM\GeneratedValue(strategy="AUTO")
38+
*
39+
* @Groups("dummy_read")
40+
*/
41+
private $id;
42+
43+
/**
44+
* @var string
45+
*
46+
* @ORM\Column(nullable=true)
47+
*
48+
* @Groups({"dummy_read", "dummy_write"})
49+
*/
50+
public $foo = 'foo';
51+
52+
/**
53+
* @var string A dummy with a Doctrine default options
54+
*
55+
* @ORM\Column(options={"default"="default value"})
56+
*/
57+
public $dummyDefaultOption;
58+
59+
/**
60+
* @return int
61+
*/
62+
public function getId()
63+
{
64+
return $this->id;
65+
}
66+
}

0 commit comments

Comments
 (0)