8000 Make resource constructor parameters writables · api-platform/core@addec68 · GitHub
[go: up one dir, main page]

Skip to content

Commit addec68

Browse files
committed
Make resource constructor parameters writables
This is motivated because other tools are already constructor friendly (ie Symfony serializer and Doctrine). Also using constructors must be recommended and not supporting them is a serious feature missing.
1 parent 9ea5d37 commit addec68

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
<argument type="service" id="api_platform.metadata.property.metadata_factory.serializer.inner" />
7070
</service>
7171

72+
<service id="api_platform.metadata.property.metadata_factory.constructor" class="ApiPlatform\Core\Metadata\Property\Factory\ConstructorPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="20" public="false">
73+
<argument type="service" id="api_platform.metadata.property.metadata_factory.constructor.inner" />
74+
</service>
75+
7276
<service id="api_platform.metadata.property.metadata_factory.cached" class="ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="-10" public="false">
7377
<argument type="service" id="api_platform.cache.metadata.property" />
7478
<argument type="service" id="api_platform.metadata.property.metadata_factory.cached.inner" />
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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+
* Set properties available in the constructor as writable.
21+
*
22+
* @author Maxime Veber <maxime.veber@nekland.fr>
23+
*/
24+
class ConstructorPropertyMetadataFactory implements PropertyMetadataFactoryInterface
25+
{
26+
/**
27+
* @var PropertyMetadataFactoryInterface
28+
*/
29+
private $decorated;
30+
31+
public function __construct(PropertyMetadataFactoryInterface $decorated = null)
32+
{
33+
$this->decorated = $decorated;
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
40+
{
41+
if (null === $this->decorated) {
42+
$propertyMetadata = new PropertyMetadata();
43+
} else {
44+
try {
45+
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
46+
} catch (PropertyNotFoundException $propertyNotFoundException) {
47+
$propertyMetadata = new PropertyMetadata();
48+
}
49+
}
50+
51+
// Constructor arguments are obviously accessible only on post operation, put will result in an error.
52+
if (!isset($options['collection_operation_name']) || $options['collection_operation_name'] !== 'post') {
53+
return $propertyMetadata;
54+
}
55+
56+
$ref = new \ReflectionClass($resourceClass);
57+
if (!$ref->isInstantiable()) {
58+
return $propertyMetadata;
59+
}
60+
61+
$constructorParameters = $ref->getConstructor()->getParameters();
62+
foreach ($constructorParameters as $constructorParameter) {
63+
if ($constructorParameter->getName() === $property) {
64+
return $propertyMetadata->withWritable(true);
65+
}
66+
}
67+
68+
return $propertyMetadata;
69+
}
70+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
3030
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
3131
use ApiPlatform\Core\Exception\InvalidArgumentException;
32+
use ApiPlatform\Core\Metadata\Property\Factory\ConstructorPropertyMetadataFactory;
3233
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
3334
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
3435
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
@@ -506,6 +507,7 @@ private function getPartialContainerBuilderProphecy($test = false)
506507
'api_platform.metadata.property.name_collection_factory.inherited',
507508
'api_platform.metadata.property.name_collection_factory.property_info',
508509
'api_platform.metadata.property.name_collection_factory.xml',
510+
'api_platform.metadata.property.metadata_factory.constructor',
509511
'api_platform.metadata.resource.metadata_factory.cached',
510512
'api_platform.metadata.resource.metadata_factory.operation',
511513
'api_platform.metadata.resource.metadata_factory.short_name',
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Metadata\Property\Factory\ConstructorPropertyMetadataFactory;
17+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
19+
use PHPUnit\Framework\TestCase;
20+
use Prophecy\Argument;
21+
use Symfony\Component\PropertyInfo\Type;
22+
23+
/**
24+
* @author Maxime Veber <maxime.veber@nekland.fr>
25+
*/
26+
class ConstructorPropertyMetadataFactoryTest extends TestCase
27+
{
28+
public function testItIsAnInstanceOfPropertyMetadataFactory()
29+
{
30+
$factory = new ConstructorPropertyMetadataFactory();
31+
$this->assertInstanceOf(PropertyMetadataFactoryInterface::class, $factory);
32+
}
33+
34+
public function testItAddsWritableForConstructorProperties()
35+
{
36+
$propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
37+
38+
$type = new Type(Type::BUILTIN_TYPE_STRING);
39+
$fooMetadata = new PropertyMetadata($type, 'foo', true, false, false, false, true, false, 'http://example.com/foo', null, ['foo' => 'bar']);
40+
$propertyMetadataFactory->create(DummyObjectWithConstructor::class, 'foo', Argument::any())->willReturn($fooMetadata)->shouldBeCalled();
41+
$factory = new ConstructorPropertyMetadataFactory($propertyMetadataFactory->reveal());
42+
43+
$fooMetadata = $factory->create(DummyObjectWithConstructor::class, 'foo', [
44+
'collection_operation_name' => 'post',
45+
]);
46+
$this->assertTrue($fooMetadata->isWritable());
47+
}
48+
49+
public function testItDoesntAddWritableOnWrongOperations()
50+
{
51+
$propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
52+
53+
$type = new Type(Type::BUILTIN_TYPE_STRING);
54+
$fooMetadata = new PropertyMetadata($type, 'foo', true, false, false, false, true, false, 'http://example.com/foo', null, ['foo' => 'bar']);
55+
$propertyMetadataFactory->create(DummyObjectWithConstructor::class, 'foo', Argument::any())->willReturn($fooMetadata)->shouldBeCalled();
56+
$factory = new ConstructorPropertyMetadataFactory($propertyMetadataFactory->reveal());
57+
58+
$fooMetadata = $factory->create(DummyObjectWithConstructor::class, 'foo', [
59+
'collection_operation_name' => 'get',
60+
]);
61+
$this->assertFalse($fooMetadata->isWritable());
62+
}
63+
64+
public function testItCreateAndSetRightMetadataIfNoFactoryGiven()
65+
{
66+
$factory = new ConstructorPropertyMetadataFactory();
67+
$fooMetadata = $factory->create(DummyObjectWithConstructor::class, 'bar', [
68+
'collection_operation_name' => 'post',
69+
]);
70+
71+
$this->assertTrue($fooMetadata->isWritable());
72+
}
73+
}
74+
75+
class DummyObjectWithConstructor
76+
{
77+
public function __construct(string $foo, \stdClass $bar)
78+
{
79+
}
80+
}

0 commit comments

Comments
 (0)
0