[PropertyInfo] Add an extractor to guess if a property is initializable
This commit is contained in:
parent
9ad492f312
commit
9d2ab9e348
@ -76,6 +76,7 @@ use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
|
||||
@ -347,6 +348,8 @@ class FrameworkExtension extends Extension
|
||||
->addTag('property_info.description_extractor');
|
||||
$container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)
|
||||
->addTag('property_info.access_extractor');
|
||||
$container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class)
|
||||
->addTag('property_info.initializable_extractor');
|
||||
$container->registerForAutoconfiguration(EncoderInterface::class)
|
||||
->addTag('serializer.encoder');
|
||||
$container->registerForAutoconfiguration(DecoderInterface::class)
|
||||
|
@ -12,18 +12,21 @@
|
||||
<argument type="collection" />
|
||||
<argument type="collection" />
|
||||
<argument type="collection" />
|
||||
<argument type="collection" />
|
||||
</service>
|
||||
<service id="Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface" alias="property_info" />
|
||||
<service id="Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface" alias="property_info" />
|
||||
<service id="Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface" alias="property_info" />
|
||||
<service id="Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface" alias="property_info" />
|
||||
<service id="Symfony\Component\PropertyInfo\PropertyListExtractorInterface" alias="property_info" />
|
||||
<service id="Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface" alias="property_info" />
|
||||
|
||||
<!-- Extractor -->
|
||||
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor">
|
||||
<tag name="property_info.list_extractor" priority="-1000" />
|
||||
<tag name="property_info.type_extractor" priority="-1002" />
|
||||
<tag name="property_info.access_extractor" priority="-1000" />
|
||||
<tag name="property_info.initializable_extractor" priority="-1000" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* added `PropertyInitializableExtractorInterface` to test if a property can be initialized through the constructor (implemented by `ReflectionExtractor`)
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
|
@ -30,14 +30,16 @@ class PropertyInfoPass implements CompilerPassInterface
|
||||
private $typeExtractorTag;
|
||||
private $descriptionExtractorTag;
|
||||
private $accessExtractorTag;
|
||||
private $initializableExtractorTag;
|
||||
|
||||
public function __construct(string $propertyInfoService = 'property_info', string $listExtractorTag = 'property_info.list_extractor', string $typeExtractorTag = 'property_info.type_extractor', string $descriptionExtractorTag = 'property_info.description_extractor', string $accessExtractorTag = 'property_info.access_extractor')
|
||||
public function __construct(string $propertyInfoService = 'property_info', string $listExtractorTag = 'property_info.list_extractor', string $typeExtractorTag = 'property_info.type_extractor', string $descriptionExtractorTag = 'property_info.description_extractor', string $accessExtractorTag = 'property_info.access_extractor', string $initializableExtractorTag = 'property_info.initializable_extractor')
|
||||
{
|
||||
$this->propertyInfoService = $propertyInfoService;
|
||||
$this->listExtractorTag = $listExtractorTag;
|
||||
$this->typeExtractorTag = $typeExtractorTag;
|
||||
$this->descriptionExtractorTag = $descriptionExtractorTag;
|
||||
$this->accessExtractorTag = $accessExtractorTag;
|
||||
$this->initializableExtractorTag = $initializableExtractorTag;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,5 +64,8 @@ class PropertyInfoPass implements CompilerPassInterface
|
||||
|
||||
$accessExtractors = $this->findAndSortTaggedServices($this->accessExtractorTag, $container);
|
||||
$definition->replaceArgument(3, new IteratorArgument($accessExtractors));
|
||||
|
||||
$initializableExtractors = $this->findAndSortTaggedServices($this->initializableExtractorTag, $container);
|
||||
$definition->replaceArgument(4, new IteratorArgument($initializableExtractors));
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\PropertyInfo\Extractor;
|
||||
|
||||
use Symfony\Component\Inflector\Inflector;
|
||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
@ -24,7 +25,7 @@ use Symfony\Component\PropertyInfo\Type;
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
|
||||
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
@ -146,6 +147,34 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
||||
return null !== $reflectionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInitializable(string $class, string $property, array $context = array()): ?bool
|
||||
{
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass($class);
|
||||
} catch (\ReflectionException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$reflectionClass->isInstantiable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($constructor = $reflectionClass->getConstructor()) {
|
||||
foreach ($constructor->getParameters() as $parameter) {
|
||||
if ($property === $parameter->name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} elseif ($parentClass = $reflectionClass->getParentClass()) {
|
||||
return $this->isInitializable($parentClass->getName(), $property);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]|null
|
||||
*/
|
||||
|
@ -20,7 +20,7 @@ use Psr\Cache\CacheItemPoolInterface;
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
|
||||
class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface
|
||||
{
|
||||
private $propertyInfoExtractor;
|
||||
private $cacheItemPool;
|
||||
@ -80,6 +80,14 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
|
||||
return $this->extract('getTypes', array($class, $property, $context));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInitializable(string $class, string $property, array $context = array()): ?bool
|
||||
{
|
||||
return $this->extract('isInitializable', array($class, $property, $context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cached data if applicable or delegates to the decorated extractor.
|
||||
*
|
||||
|
@ -18,25 +18,28 @@ namespace Symfony\Component\PropertyInfo;
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class PropertyInfoExtractor implements PropertyInfoExtractorInterface
|
||||
class PropertyInfoExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface
|
||||
{
|
||||
private $listExtractors;
|
||||
private $typeExtractors;
|
||||
private $descriptionExtractors;
|
||||
private $accessExtractors;
|
||||
private $initializableExtractors;
|
||||
|
||||
/**
|
||||
* @param iterable|PropertyListExtractorInterface[] $listExtractors
|
||||
* @param iterable|PropertyTypeExtractorInterface[] $typeExtractors
|
||||
* @param iterable|PropertyDescriptionExtractorInterface[] $descriptionExtractors
|
||||
* @param iterable|PropertyAccessExtractorInterface[] $accessExtractors
|
||||
* @param iterable|PropertyListExtractorInterface[] $listExtractors
|
||||
* @param iterable|PropertyTypeExtractorInterface[] $typeExtractors
|
||||
* @param iterable|PropertyDescriptionExtractorInterface[] $descriptionExtractors
|
||||
* @param iterable|PropertyAccessExtractorInterface[] $accessExtractors
|
||||
* @param iterable|PropertyInitializableExtractorInterface[] $initializableExtractors
|
||||
*/
|
||||
public function __construct(iterable $listExtractors = array(), iterable $typeExtractors = array(), iterable $descriptionExtractors = array(), iterable $accessExtractors = array())
|
||||
public function __construct(iterable $listExtractors = array(), iterable $typeExtractors = array(), iterable $descriptionExtractors = array(), iterable $accessExtractors = array(), iterable $initializableExtractors = array())
|
||||
{
|
||||
$this->listExtractors = $listExtractors;
|
||||
$this->typeExtractors = $typeExtractors;
|
||||
$this->descriptionExtractors = $descriptionExtractors;
|
||||
$this->accessExtractors = $accessExtractors;
|
||||
$this->initializableExtractors = $initializableExtractors;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,6 +90,14 @@ class PropertyInfoExtractor implements PropertyInfoExtractorInterface
|
||||
return $this->extract($this->accessExtractors, 'isWritable', array($class, $property, $context));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInitializable(string $class, string $property, array $context = array()): ?bool
|
||||
{
|
||||
return $this->extract($this->initializableExtractors, 'isInitializable', array($class, $property, $context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over registered extractors and return the first value found.
|
||||
*
|
||||
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\PropertyInfo;
|
||||
|
||||
/**
|
||||
* Guesses if the property can be initialized through the constructor.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
interface PropertyInitializableExtractorInterface
|
||||
{
|
||||
/**
|
||||
* Is the property initializable? Returns true if a constructor's parameter matches the given property name.
|
||||
*/
|
||||
public function isInitializable(string $class, string $property, array $context = array()): ?bool;
|
||||
}
|
@ -13,6 +13,7 @@ namespace Symfony\Component\PropertyInfo\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
|
||||
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\NullExtractor;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
@ -30,7 +31,7 @@ class AbstractPropertyInfoExtractorTest extends TestCase
|
||||
protected function setUp()
|
||||
{
|
||||
$extractors = array(new NullExtractor(), new DummyExtractor());
|
||||
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors);
|
||||
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors, $extractors);
|
||||
}
|
||||
|
||||
public function testInstanceOf()
|
||||
@ -39,6 +40,7 @@ class AbstractPropertyInfoExtractorTest extends TestCase
|
||||
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface', $this->propertyInfo);
|
||||
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface', $this->propertyInfo);
|
||||
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface', $this->propertyInfo);
|
||||
$this->assertInstanceOf(PropertyInitializableExtractorInterface::class, $this->propertyInfo);
|
||||
}
|
||||
|
||||
public function testGetShortDescription()
|
||||
@ -70,4 +72,9 @@ class AbstractPropertyInfoExtractorTest extends TestCase
|
||||
{
|
||||
$this->assertEquals(array('a', 'b'), $this->propertyInfo->getProperties('Foo'));
|
||||
}
|
||||
|
||||
public function testIsInitializable()
|
||||
{
|
||||
$this->assertTrue($this->propertyInfo->isInitializable('Foo', 'bar', array()));
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class PropertyInfoPassTest extends TestCase
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$definition = $container->register('property_info')->setArguments(array(null, null, null, null));
|
||||
$definition = $container->register('property_info')->setArguments(array(null, null, null, null, null));
|
||||
$container->register('n2')->addTag($tag, array('priority' => 100));
|
||||
$container->register('n1')->addTag($tag, array('priority' => 200));
|
||||
$container->register('n3')->addTag($tag);
|
||||
@ -49,6 +49,7 @@ class PropertyInfoPassTest extends TestCase
|
||||
array(1, 'property_info.type_extractor'),
|
||||
array(2, 'property_info.description_extractor'),
|
||||
array(3, 'property_info.access_extractor'),
|
||||
array(4, 'property_info.initializable_extractor'),
|
||||
);
|
||||
}
|
||||
|
||||
@ -56,7 +57,7 @@ class PropertyInfoPassTest extends TestCase
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$propertyInfoExtractorDefinition = $container->register('property_info')
|
||||
->setArguments(array(array(), array(), array(), array()));
|
||||
->setArguments(array(array(), array(), array(), array(), array()));
|
||||
|
||||
$propertyInfoPass = new PropertyInfoPass();
|
||||
$propertyInfoPass->process($container);
|
||||
@ -65,5 +66,6 @@ class PropertyInfoPassTest extends TestCase
|
||||
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(1));
|
||||
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(2));
|
||||
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(3));
|
||||
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(4));
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
/**
|
||||
@ -270,4 +273,24 @@ class ReflectionExtractorTest extends TestCase
|
||||
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet'));
|
||||
$this->assertEquals(array('analyses', 'feet'), $this->extractor->getProperties(AdderRemoverDummy::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getInitializableProperties
|
||||
*/
|
||||
public function testIsInitializable(string $class, string $property, bool $expected)
|
||||
{
|
||||
$this->assertSame($expected, $this->extractor->isInitializable($class, $property));
|
||||
}
|
||||
|
||||
public function getInitializableProperties(): array
|
||||
{
|
||||
return array(
|
||||
array(Php71Dummy::class, 'string', true),
|
||||
array(Php71Dummy::class, 'intPrivate', true),
|
||||
array(Php71Dummy::class, 'notExist', false),
|
||||
array(Php71DummyExtended2::class, 'intWithAccessor', true),
|
||||
array(Php71DummyExtended2::class, 'intPrivate', false),
|
||||
array(NotInstantiable::class, 'foo', false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
@ -20,7 +21,7 @@ use Symfony\Component\PropertyInfo\Type;
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
|
||||
class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@ -69,4 +70,12 @@ class DummyExtractor implements PropertyListExtractorInterface, PropertyDescript
|
||||
{
|
||||
return array('a', 'b');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInitializable(string $class, string $property, array $context = array()): ?bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class NotInstantiable
|
||||
{
|
||||
private function __construct(string $foo)
|
||||
{
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
|
||||
@ -21,7 +22,7 @@ use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class NullExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
|
||||
class NullExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@ -76,6 +77,14 @@ class NullExtractor implements PropertyListExtractorInterface, PropertyDescripti
|
||||
$this->assertIsString($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInitializable(string $class, string $property, array $context = array()): ?bool
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private function assertIsString($string)
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
|
@ -16,6 +16,10 @@ namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
|
||||
*/
|
||||
class Php71Dummy
|
||||
{
|
||||
public function __construct(string $string, int $intPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
public function getFoo(): ?array
|
||||
{
|
||||
}
|
||||
@ -32,3 +36,18 @@ class Php71Dummy
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class Php71DummyExtended extends Php71Dummy
|
||||
{
|
||||
}
|
||||
|
||||
class Php71DummyExtended2 extends Php71Dummy
|
||||
{
|
||||
public function __construct(int $intWithAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public function getIntWithAccessor()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -61,4 +61,10 @@ class PropertyInfoCacheExtractorTest extends AbstractPropertyInfoExtractorTest
|
||||
parent::testGetProperties();
|
||||
parent::testGetProperties();
|
||||
}
|
||||
|
||||
public function testIsInitializable()
|
||||
{
|
||||
parent::testIsInitializable();
|
||||
parent::testIsInitializable();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user