v1.0 Initial commit of project

This commit is contained in:
2026-01-01 10:54:18 +01:00
commit 768cf78b57
990 changed files with 241213 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
<?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;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\NullExtractor;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class AbstractPropertyInfoExtractorTest extends TestCase
{
protected PropertyInfoExtractorInterface $propertyInfo;
protected function setUp(): void
{
$extractors = [new NullExtractor(), new DummyExtractor()];
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors, $extractors);
}
public function testInstanceOf()
{
$this->assertInstanceOf(PropertyInfoExtractorInterface::class, $this->propertyInfo);
$this->assertInstanceOf(PropertyTypeExtractorInterface::class, $this->propertyInfo);
$this->assertInstanceOf(PropertyDescriptionExtractorInterface::class, $this->propertyInfo);
$this->assertInstanceOf(PropertyAccessExtractorInterface::class, $this->propertyInfo);
$this->assertInstanceOf(PropertyInitializableExtractorInterface::class, $this->propertyInfo);
}
public function testGetShortDescription()
{
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', []));
}
public function testGetLongDescription()
{
$this->assertSame('long', $this->propertyInfo->getLongDescription('Foo', 'bar', []));
}
public function testGetTypes()
{
$this->assertEquals([new Type(Type::BUILTIN_TYPE_INT)], $this->propertyInfo->getTypes('Foo', 'bar', []));
}
public function testIsReadable()
{
$this->assertTrue($this->propertyInfo->isReadable('Foo', 'bar', []));
}
public function testIsWritable()
{
$this->assertTrue($this->propertyInfo->isWritable('Foo', 'bar', []));
}
public function testGetProperties()
{
$this->assertEquals(['a', 'b'], $this->propertyInfo->getProperties('Foo'));
}
public function testIsInitializable()
{
$this->assertTrue($this->propertyInfo->isInitializable('Foo', 'bar', []));
}
}

View File

@@ -0,0 +1,54 @@
<?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\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoConstructorPass;
class PropertyInfoConstructorPassTest extends TestCase
{
public function testServicesAreOrderedAccordingToPriority()
{
$container = new ContainerBuilder();
$tag = 'property_info.constructor_extractor';
$definition = $container->register('property_info.constructor_extractor')->setArguments([null, null]);
$container->register('n2')->addTag($tag, ['priority' => 100]);
$container->register('n1')->addTag($tag, ['priority' => 200]);
$container->register('n3')->addTag($tag);
$pass = new PropertyInfoConstructorPass();
$pass->process($container);
$expected = new IteratorArgument([
new Reference('n1'),
new Reference('n2'),
new Reference('n3'),
]);
$this->assertEquals($expected, $definition->getArgument(0));
}
public function testReturningEmptyArrayWhenNoService()
{
$container = new ContainerBuilder();
$propertyInfoExtractorDefinition = $container->register('property_info.constructor_extractor')
->setArguments([[]]);
$pass = new PropertyInfoConstructorPass();
$pass->process($container);
$this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(0));
}
}

View File

@@ -0,0 +1,71 @@
<?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\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
class PropertyInfoPassTest extends TestCase
{
/**
* @dataProvider provideTags
*/
public function testServicesAreOrderedAccordingToPriority($index, $tag)
{
$container = new ContainerBuilder();
$definition = $container->register('property_info')->setArguments([null, null, null, null, null]);
$container->register('n2')->addTag($tag, ['priority' => 100]);
$container->register('n1')->addTag($tag, ['priority' => 200]);
$container->register('n3')->addTag($tag);
$propertyInfoPass = new PropertyInfoPass();
$propertyInfoPass->process($container);
$expected = new IteratorArgument([
new Reference('n1'),
new Reference('n2'),
new Reference('n3'),
]);
$this->assertEquals($expected, $definition->getArgument($index));
}
public static function provideTags()
{
return [
[0, 'property_info.list_extractor'],
[1, 'property_info.type_extractor'],
[2, 'property_info.description_extractor'],
[3, 'property_info.access_extractor'],
[4, 'property_info.initializable_extractor'],
];
}
public function testReturningEmptyArrayWhenNoService()
{
$container = new ContainerBuilder();
$propertyInfoExtractorDefinition = $container->register('property_info')
->setArguments([[], [], [], [], []]);
$propertyInfoPass = new PropertyInfoPass();
$propertyInfoPass->process($container);
$this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(0));
$this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(1));
$this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(2));
$this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(3));
$this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(4));
}
}

View File

@@ -0,0 +1,46 @@
<?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\Extractor;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\ConstructorExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
class ConstructorExtractorTest extends TestCase
{
private ConstructorExtractor $extractor;
protected function setUp(): void
{
$this->extractor = new ConstructorExtractor([new DummyExtractor()]);
}
public function testInstanceOf()
{
$this->assertInstanceOf(\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface::class, $this->extractor);
}
public function testGetTypes()
{
$this->assertEquals([new Type(Type::BUILTIN_TYPE_STRING)], $this->extractor->getTypes('Foo', 'bar', []));
}
public function testGetTypesIfNoExtractors()
{
$extractor = new ConstructorExtractor([]);
$this->assertNull($extractor->getTypes('Foo', 'bar', []));
}
}

View File

@@ -0,0 +1,483 @@
<?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\Extractor;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyCollection;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypeDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PhpDocExtractorTest extends TestCase
{
private PhpDocExtractor $extractor;
protected function setUp(): void
{
$this->extractor = new PhpDocExtractor();
}
/**
* @dataProvider typesProvider
*/
public function testExtract($property, ?array $type, $shortDescription, $longDescription)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
$this->assertSame($shortDescription, $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
$this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public function testParamTagTypeIsOmitted()
{
$this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType'));
}
public static function invalidTypesProvider()
{
return [
'pub' => ['pub', null, null],
'stat' => ['stat', null, null],
'bar' => ['bar', 'Bar.', null],
];
}
/**
* @dataProvider invalidTypesProvider
*/
public function testInvalid($property, $shortDescription, $longDescription)
{
$this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property));
$this->assertSame($shortDescription, $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property));
$this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property));
}
/**
* @group legacy
*/
public function testEmptyParamAnnotation()
{
$this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', 'foo'));
$this->assertSame('Foo.', $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', 'foo'));
$this->assertNull($this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', 'foo'));
}
/**
* @dataProvider typesWithNoPrefixesProvider
*/
public function testExtractTypesWithNoPrefixes($property, ?array $type = null)
{
$noPrefixExtractor = new PhpDocExtractor(null, [], [], []);
$this->assertEquals($type, $noPrefixExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public static function typesProvider()
{
return [
['foo', null, 'Short description.', 'Long description.'],
['bar', [new Type(Type::BUILTIN_TYPE_STRING)], 'This is bar', null],
['baz', [new Type(Type::BUILTIN_TYPE_INT)], 'Should be used.', null],
['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)], null, null],
['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)], null, null],
['foo4', [new Type(Type::BUILTIN_TYPE_NULL)], null, null],
['foo5', null, null, null],
[
'files',
[
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
],
null,
null,
],
['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null],
['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null],
['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))], null, null],
['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null],
['a', [new Type(Type::BUILTIN_TYPE_INT)], 'A.', null],
['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], 'B.', null],
['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)], null, null],
['ct', [new Type(Type::BUILTIN_TYPE_TRUE, true)], null, null],
['cf', [new Type(Type::BUILTIN_TYPE_FALSE, true)], null, null],
['d', [new Type(Type::BUILTIN_TYPE_BOOL)], null, null],
['dt', [new Type(Type::BUILTIN_TYPE_TRUE)], null, null],
['df', [new Type(Type::BUILTIN_TYPE_FALSE)], null, null],
['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))], null, null],
['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null],
['h', [new Type(Type::BUILTIN_TYPE_STRING, true)], null, null],
['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null],
['donotexist', null, null, null],
['staticGetter', null, null, null],
['staticSetter', null, null, null],
['emptyVar', null, 'This should not be removed.', null],
['arrayWithKeys', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_STRING))], null, null],
['arrayOfMixed', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), null)], null, null],
['listOfStrings', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], null, null],
['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], null, null],
['collectionAsObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyCollection::class, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_STRING)])], null, null],
];
}
/**
* @dataProvider provideCollectionTypes
*/
public function testExtractCollection($property, ?array $type, $shortDescription, $longDescription)
{
$this->testExtract($property, $type, $shortDescription, $longDescription);
}
public static function provideCollectionTypes()
{
return [
['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], new Type(Type::BUILTIN_TYPE_STRING))], null, null],
['iteratorCollectionWithKey', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], null, null],
[
'nestedIterators',
[new Type(
Type::BUILTIN_TYPE_OBJECT,
false,
'Iterator',
true,
new Type(Type::BUILTIN_TYPE_INT),
new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))
)],
null,
null,
],
[
'arrayWithKeys',
[new Type(
Type::BUILTIN_TYPE_ARRAY,
false,
null,
true,
new Type(Type::BUILTIN_TYPE_STRING),
new Type(Type::BUILTIN_TYPE_STRING)
)],
null,
null,
],
[
'arrayWithKeysAndComplexValue',
[new Type(
Type::BUILTIN_TYPE_ARRAY,
false,
null,
true,
new Type(Type::BUILTIN_TYPE_STRING),
new Type(
Type::BUILTIN_TYPE_ARRAY,
true,
null,
true,
new Type(Type::BUILTIN_TYPE_INT),
new Type(Type::BUILTIN_TYPE_STRING, true)
)
)],
null,
null,
],
];
}
/**
* @dataProvider typesWithCustomPrefixesProvider
*/
public function testExtractTypesWithCustomPrefixes($property, ?array $type = null)
{
$customExtractor = new PhpDocExtractor(null, ['add', 'remove'], ['is', 'can']);
$this->assertEquals($type, $customExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public static function typesWithCustomPrefixesProvider()
{
return [
['foo', null, 'Short description.', 'Long description.'],
['bar', [new Type(Type::BUILTIN_TYPE_STRING)], 'This is bar', null],
['baz', [new Type(Type::BUILTIN_TYPE_INT)], 'Should be used.', null],
['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)], null, null],
['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)], null, null],
['foo4', [new Type(Type::BUILTIN_TYPE_NULL)], null, null],
['foo5', null, null, null],
[
'files',
[
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
],
null,
null,
],
['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null],
['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null],
['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))], null, null],
['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null],
['a', null, 'A.', null],
['b', null, 'B.', null],
['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)], null, null],
['d', [new Type(Type::BUILTIN_TYPE_BOOL)], null, null],
['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))], null, null],
['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null],
['h', [new Type(Type::BUILTIN_TYPE_STRING, true)], null, null],
['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null],
['nonNullableCollectionOfNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, true))], null, null],
['nullableCollectionOfMultipleNonNullableElementTypes', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)])], null, null],
['donotexist', null, null, null],
['staticGetter', null, null, null],
['staticSetter', null, null, null],
];
}
public static function typesWithNoPrefixesProvider()
{
return [
['foo', null, 'Short description.', 'Long description.'],
['bar', [new Type(Type::BUILTIN_TYPE_STRING)], 'This is bar', null],
['baz', [new Type(Type::BUILTIN_TYPE_INT)], 'Should be used.', null],
['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)], null, null],
['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)], null, null],
['foo4', [new Type(Type::BUILTIN_TYPE_NULL)], null, null],
['foo5', null, null, null],
[
'files',
[
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
],
null,
null,
],
['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null],
['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null],
['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))], null, null],
['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null],
['a', null, 'A.', null],
['b', null, 'B.', null],
['c', null, null, null],
['d', null, null, null],
['e', null, null, null],
['f', null, null, null],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null],
['h', [new Type(Type::BUILTIN_TYPE_STRING, true)], null, null],
['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null],
['donotexist', null, null, null],
['staticGetter', null, null, null],
['staticSetter', null, null, null],
];
}
public function testReturnNullOnEmptyDocBlock()
{
$this->assertNull($this->extractor->getShortDescription(EmptyDocBlock::class, 'foo'));
}
public static function dockBlockFallbackTypesProvider()
{
return [
'pub' => [
'pub', [new Type(Type::BUILTIN_TYPE_STRING)],
],
'protAcc' => [
'protAcc', [new Type(Type::BUILTIN_TYPE_INT)],
],
'protMut' => [
'protMut', [new Type(Type::BUILTIN_TYPE_BOOL)],
],
];
}
/**
* @dataProvider dockBlockFallbackTypesProvider
*/
public function testDocBlockFallback($property, $types)
{
$this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property));
}
/**
* @dataProvider propertiesDefinedByTraitsProvider
*/
public function testPropertiesDefinedByTraits(string $property, Type $type)
{
$this->assertEquals([$type], $this->extractor->getTypes(DummyUsingTrait::class, $property));
}
public static function propertiesDefinedByTraitsProvider(): array
{
return [
['propertyInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
['propertyInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
['propertyInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
['propertyInExternalTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
['propertyInExternalTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
['propertyInExternalTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
];
}
/**
* @dataProvider methodsDefinedByTraitsProvider
*/
public function testMethodsDefinedByTraits(string $property, Type $type)
{
$this->assertEquals([$type], $this->extractor->getTypes(DummyUsingTrait::class, $property));
}
public static function methodsDefinedByTraitsProvider(): array
{
return [
['methodInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
['methodInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
['methodInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
['methodInExternalTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
['methodInExternalTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
['methodInExternalTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
];
}
/**
* @dataProvider propertiesStaticTypeProvider
*/
public function testPropertiesStaticType(string $class, string $property, Type $type)
{
$this->assertEquals([$type], $this->extractor->getTypes($class, $property));
}
public static function propertiesStaticTypeProvider(): array
{
return [
[ParentDummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)],
[Dummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
];
}
/**
* @dataProvider propertiesParentTypeProvider
*/
public function testPropertiesParentType(string $class, string $property, ?array $types)
{
$this->assertEquals($types, $this->extractor->getTypes($class, $property));
}
public static function propertiesParentTypeProvider(): array
{
return [
[ParentDummy::class, 'parentAnnotationNoParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'parent')]],
[Dummy::class, 'parentAnnotation', [new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]],
];
}
public function testUnknownPseudoType()
{
$this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'scalar')], $this->extractor->getTypes(PseudoTypeDummy::class, 'unknownPseudoType'));
}
public function testGenericInterface()
{
$this->assertNull($this->extractor->getTypes(Dummy::class, 'genericInterface'));
}
/**
* @dataProvider constructorTypesProvider
*/
public function testExtractConstructorTypes($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
}
public static function constructorTypesProvider()
{
return [
['date', [new Type(Type::BUILTIN_TYPE_INT)]],
['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]],
['dateTime', null],
['ddd', null],
['mixed', null],
];
}
/**
* @dataProvider pseudoTypesProvider
*/
public function testPseudoTypes($property, array $type)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypesDummy', $property));
}
public static function pseudoTypesProvider(): array
{
return [
['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]],
];
}
/**
* @dataProvider promotedPropertyProvider
*/
public function testExtractPromotedProperty(string $property, ?array $types)
{
$this->assertEquals($types, $this->extractor->getTypes(Php80Dummy::class, $property));
}
public static function promotedPropertyProvider(): array
{
return [
['promoted', null],
['promotedAndMutated', [new Type(Type::BUILTIN_TYPE_STRING)]],
];
}
}
class EmptyDocBlock
{
public $foo;
}
class OmittedParamTagTypeDocBlock
{
/**
* The type is omitted here to ensure that the extractor doesn't choke on missing types.
*
* @param $omittedTagType
*/
public function setOmittedType(array $omittedTagType)
{
}
}

View File

@@ -0,0 +1,583 @@
<?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\Extractor;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Clazz;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyCollection;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyGeneric;
use Symfony\Component\PropertyInfo\Tests\Fixtures\IFace;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80PromotedDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\RootDummy\RootDummyItem;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\AnotherNamespace\DummyInAnotherNamespace;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait;
use Symfony\Component\PropertyInfo\Type;
require_once __DIR__.'/../Fixtures/Extractor/DummyNamespace.php';
/**
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class PhpStanExtractorTest extends TestCase
{
private PhpStanExtractor $extractor;
private PhpDocExtractor $phpDocExtractor;
protected function setUp(): void
{
$this->extractor = new PhpStanExtractor();
$this->phpDocExtractor = new PhpDocExtractor();
}
/**
* @dataProvider typesProvider
*/
public function testExtract($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public function testParamTagTypeIsOmitted()
{
$this->assertNull($this->extractor->getTypes(PhpStanOmittedParamTagTypeDocBlock::class, 'omittedType'));
}
public static function invalidTypesProvider()
{
return [
'pub' => ['pub'],
'stat' => ['stat'],
'foo' => ['foo'],
'bar' => ['bar'],
];
}
/**
* @dataProvider invalidTypesProvider
*/
public function testInvalid($property)
{
$this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property));
}
/**
* @dataProvider typesWithNoPrefixesProvider
*/
public function testExtractTypesWithNoPrefixes($property, ?array $type = null)
{
$noPrefixExtractor = new PhpStanExtractor([], [], []);
$this->assertEquals($type, $noPrefixExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public static function typesProvider()
{
return [
['foo', null],
['bar', [new Type(Type::BUILTIN_TYPE_STRING)]],
['baz', [new Type(Type::BUILTIN_TYPE_INT)]],
['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)]],
['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)]],
['foo4', [new Type(Type::BUILTIN_TYPE_NULL)]],
['foo5', null],
[
'files',
[
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
],
],
['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]],
['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))]],
['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], null)]],
['a', [new Type(Type::BUILTIN_TYPE_INT)]],
['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)]],
['d', [new Type(Type::BUILTIN_TYPE_BOOL)]],
['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))]],
['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]],
['h', [new Type(Type::BUILTIN_TYPE_STRING, true)]],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))]],
['donotexist', null],
['staticGetter', null],
['staticSetter', null],
['emptyVar', null],
['arrayWithKeys', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_STRING))]],
['arrayOfMixed', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), null)]],
['listOfStrings', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]],
['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)]],
['rootDummyItems', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, RootDummyItem::class))]],
['rootDummyItem', [new Type(Type::BUILTIN_TYPE_OBJECT, false, RootDummyItem::class)]],
['collectionAsObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyCollection::class, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_STRING)])]],
];
}
/**
* @dataProvider provideCollectionTypes
*/
public function testExtractCollection($property, ?array $type = null)
{
$this->testExtract($property, $type);
}
public static function provideCollectionTypes()
{
return [
['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, null, new Type(Type::BUILTIN_TYPE_STRING))]],
['iteratorCollectionWithKey', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]],
[
'nestedIterators',
[new Type(
Type::BUILTIN_TYPE_OBJECT,
false,
'Iterator',
true,
new Type(Type::BUILTIN_TYPE_INT),
new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))
)],
],
[
'arrayWithKeys',
[new Type(
Type::BUILTIN_TYPE_ARRAY,
false,
null,
true,
new Type(Type::BUILTIN_TYPE_STRING),
new Type(Type::BUILTIN_TYPE_STRING)
)],
],
[
'arrayWithKeysAndComplexValue',
[new Type(
Type::BUILTIN_TYPE_ARRAY,
false,
null,
true,
new Type(Type::BUILTIN_TYPE_STRING),
new Type(
Type::BUILTIN_TYPE_ARRAY,
true,
null,
true,
new Type(Type::BUILTIN_TYPE_INT),
new Type(Type::BUILTIN_TYPE_STRING, true)
)
)],
],
];
}
/**
* @dataProvider typesWithCustomPrefixesProvider
*/
public function testExtractTypesWithCustomPrefixes($property, ?array $type = null)
{
$customExtractor = new PhpStanExtractor(['add', 'remove'], ['is', 'can']);
$this->assertEquals($type, $customExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public static function typesWithCustomPrefixesProvider()
{
return [
['foo', null],
['bar', [new Type(Type::BUILTIN_TYPE_STRING)]],
['baz', [new Type(Type::BUILTIN_TYPE_INT)]],
['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)]],
['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)]],
['foo4', [new Type(Type::BUILTIN_TYPE_NULL)]],
['foo5', null],
[
'files',
[
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
],
],
['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]],
['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))]],
['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], null)]],
['a', null],
['b', null],
['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)]],
['d', [new Type(Type::BUILTIN_TYPE_BOOL)]],
['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))]],
['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]],
['h', [new Type(Type::BUILTIN_TYPE_STRING, true)]],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))]],
['donotexist', null],
['staticGetter', null],
['staticSetter', null],
];
}
public static function typesWithNoPrefixesProvider()
{
return [
['foo', null],
['bar', [new Type(Type::BUILTIN_TYPE_STRING)]],
['baz', [new Type(Type::BUILTIN_TYPE_INT)]],
['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)]],
['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)]],
['foo4', [new Type(Type::BUILTIN_TYPE_NULL)]],
['foo5', null],
[
'files',
[
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
],
],
['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]],
['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))]],
['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], null)]],
['a', null],
['b', null],
['c', null],
['d', null],
['e', null],
['f', null],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]],
['h', [new Type(Type::BUILTIN_TYPE_STRING, true)]],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))]],
['donotexist', null],
['staticGetter', null],
['staticSetter', null],
];
}
public static function dockBlockFallbackTypesProvider()
{
return [
'pub' => [
'pub', [new Type(Type::BUILTIN_TYPE_STRING)],
],
'protAcc' => [
'protAcc', [new Type(Type::BUILTIN_TYPE_INT)],
],
'protMut' => [
'protMut', [new Type(Type::BUILTIN_TYPE_BOOL)],
],
];
}
/**
* @dataProvider dockBlockFallbackTypesProvider
*/
public function testDocBlockFallback($property, $types)
{
$this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property));
}
/**
* @dataProvider propertiesDefinedByTraitsProvider
*/
public function testPropertiesDefinedByTraits(string $property, Type $type)
{
$this->assertEquals([$type], $this->extractor->getTypes(DummyUsingTrait::class, $property));
}
public static function propertiesDefinedByTraitsProvider(): array
{
return [
['propertyInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
['propertyInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
['propertyInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
['dummyInAnotherNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyInAnotherNamespace::class)],
];
}
/**
* @dataProvider propertiesStaticTypeProvider
*/
public function testPropertiesStaticType(string $class, string $property, Type $type)
{
$this->assertEquals([$type], $this->extractor->getTypes($class, $property));
}
public static function propertiesStaticTypeProvider(): array
{
return [
[ParentDummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)],
[Dummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
];
}
/**
* @dataProvider propertiesParentTypeProvider
*/
public function testPropertiesParentType(string $class, string $property, ?array $types)
{
$this->assertEquals($types, $this->extractor->getTypes($class, $property));
}
public static function propertiesParentTypeProvider(): array
{
return [
[ParentDummy::class, 'parentAnnotationNoParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'parent')]],
[Dummy::class, 'parentAnnotation', [new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]],
];
}
/**
* @dataProvider constructorTypesProvider
*/
public function testExtractConstructorTypes($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
}
/**
* @dataProvider constructorTypesProvider
*/
public function testExtractConstructorTypesReturnNullOnEmptyDocBlock($property)
{
$this->assertNull($this->extractor->getTypesFromConstructor(ConstructorDummyWithoutDocBlock::class, $property));
}
public static function constructorTypesProvider()
{
return [
['date', [new Type(Type::BUILTIN_TYPE_INT)]],
['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]],
['dateTime', null],
['ddd', null],
];
}
/**
* @dataProvider unionTypesProvider
*/
public function testExtractorUnionTypes(string $property, ?array $types)
{
$this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyUnionType', $property));
}
public static function unionTypesProvider(): array
{
return [
['a', [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]],
['b', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]],
['c', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]],
['d', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING)])])]],
['e', [new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class, false, [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING)])], [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_OBJECT, false, \Traversable::class, true, [], [new Type(Type::BUILTIN_TYPE_OBJECT, false, DefaultValue::class)])])]), new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]],
['f', null],
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]],
];
}
/**
* @dataProvider pseudoTypesProvider
*/
public function testPseudoTypes($property, array $type)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PhpStanPseudoTypesDummy', $property));
}
public static function pseudoTypesProvider(): array
{
return [
['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['interfaceString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['literalString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]],
['negativeInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]],
['nonEmptyArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]],
['nonEmptyList', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))]],
['scalar', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)]],
['number', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]],
['numeric', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)]],
['arrayKey', [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]],
['double', [new Type(Type::BUILTIN_TYPE_FLOAT)]],
];
}
public function testDummyNamespace()
{
$this->assertEquals(
[new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')],
$this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyNamespace', 'dummy')
);
}
public function testDummyNamespaceWithProperty()
{
$phpStanTypes = $this->extractor->getTypes(\B\Dummy::class, 'property');
$phpDocTypes = $this->phpDocExtractor->getTypes(\B\Dummy::class, 'property');
$this->assertEquals('A\Property', $phpStanTypes[0]->getClassName());
$this->assertEquals($phpDocTypes[0]->getClassName(), $phpStanTypes[0]->getClassName());
}
/**
* @dataProvider intRangeTypeProvider
*/
public function testExtractorIntRangeType(string $property, ?array $types)
{
$this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\IntRangeDummy', $property));
}
public static function intRangeTypeProvider(): array
{
return [
['a', [new Type(Type::BUILTIN_TYPE_INT)]],
['b', [new Type(Type::BUILTIN_TYPE_INT, true)]],
['c', [new Type(Type::BUILTIN_TYPE_INT)]],
];
}
/**
* @dataProvider php80TypesProvider
*/
public function testExtractPhp80Type(string $class, $property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes($class, $property, []));
}
public static function php80TypesProvider()
{
return [
[Php80Dummy::class, 'promotedWithDocCommentAndType', [new Type(Type::BUILTIN_TYPE_INT)]],
[Php80Dummy::class, 'promotedWithDocComment', [new Type(Type::BUILTIN_TYPE_STRING)]],
[Php80Dummy::class, 'promotedAndMutated', [new Type(Type::BUILTIN_TYPE_STRING)]],
[Php80Dummy::class, 'promoted', null],
[Php80Dummy::class, 'collection', [new Type(Type::BUILTIN_TYPE_ARRAY, collection: true, collectionValueType: new Type(Type::BUILTIN_TYPE_STRING))]],
[Php80PromotedDummy::class, 'promoted', null],
];
}
public function testGenericInterface()
{
$this->assertEquals(
[
new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: \BackedEnum::class,
collectionValueType: new Type(
builtinType: Type::BUILTIN_TYPE_STRING,
)
),
],
$this->extractor->getTypes(Dummy::class, 'genericInterface')
);
}
/**
* @param list<Type> $expectedTypes
* @dataProvider genericsProvider
*/
public function testGenericsLegacy(string $property, array $expectedTypes)
{
$this->assertEquals($expectedTypes, $this->extractor->getTypes(DummyGeneric::class, $property));
}
/**
* @return iterable<array{0: string, 1: list<Type>}>
*/
public static function genericsProvider(): iterable
{
yield [
'basicClass',
[
new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: Clazz::class,
collectionValueType: new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: Dummy::class,
)
),
],
];
yield [
'nullableClass',
[
new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: Clazz::class,
nullable: true,
collectionValueType: new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: Dummy::class,
)
),
],
];
yield [
'basicInterface',
[
new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: IFace::class,
collectionValueType: new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: Dummy::class,
)
),
],
];
yield [
'nullableInterface',
[
new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: IFace::class,
nullable: true,
collectionValueType: new Type(
builtinType: Type::BUILTIN_TYPE_OBJECT,
class: Dummy::class,
)
),
],
];
}
}
class PhpStanOmittedParamTagTypeDocBlock
{
/**
* The type is omitted here to ensure that the extractor doesn't choke on missing types.
*
* @param $omittedTagType
*/
public function setOmittedType(array $omittedTagType)
{
}
}

View File

@@ -0,0 +1,793 @@
<?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\Extractor;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
use Symfony\Component\PropertyInfo\PropertyWriteInfo;
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\AsymmetricVisibility;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php74Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7ParentDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\VirtualProperties;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ReflectionExtractorTest extends TestCase
{
private ReflectionExtractor $extractor;
protected function setUp(): void
{
$this->extractor = new ReflectionExtractor();
}
public function testGetProperties()
{
$this->assertSame(
[
'bal',
'parent',
'collection',
'collectionAsObject',
'nestedCollection',
'mixedCollection',
'B',
'Guid',
'g',
'h',
'i',
'j',
'nullableCollectionOfNonNullableElements',
'nonNullableCollectionOfNullableElements',
'nullableCollectionOfMultipleNonNullableElementTypes',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
'nestedIterators',
'arrayWithKeys',
'arrayWithKeysAndComplexValue',
'arrayOfMixed',
'listOfStrings',
'parentAnnotation',
'genericInterface',
'foo',
'foo2',
'foo3',
'foo4',
'foo5',
'files',
'propertyTypeStatic',
'parentAnnotationNoParent',
'rootDummyItems',
'rootDummyItem',
'a',
'DOB',
'Id',
'123',
'self',
'realParent',
'xTotals',
'YT',
'date',
'element',
'c',
'ct',
'cf',
'd',
'dt',
'df',
'e',
'f',
],
$this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')
);
$this->assertNull($this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\NoProperties'));
}
public function testGetPropertiesWithCustomPrefixes()
{
$customExtractor = new ReflectionExtractor(['add', 'remove'], ['is', 'can']);
$this->assertSame(
[
'bal',
'parent',
'collection',
'collectionAsObject',
'nestedCollection',
'mixedCollection',
'B',
'Guid',
'g',
'h',
'i',
'j',
'nullableCollectionOfNonNullableElements',
'nonNullableCollectionOfNullableElements',
'nullableCollectionOfMultipleNonNullableElementTypes',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
'nestedIterators',
'arrayWithKeys',
'arrayWithKeysAndComplexValue',
'arrayOfMixed',
'listOfStrings',
'parentAnnotation',
'genericInterface',
'foo',
'foo2',
'foo3',
'foo4',
'foo5',
'files',
'propertyTypeStatic',
'parentAnnotationNoParent',
'rootDummyItems',
'rootDummyItem',
'date',
'c',
'ct',
'cf',
'd',
'dt',
'df',
'e',
'f',
],
$customExtractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')
);
}
public function testGetPropertiesWithNoPrefixes()
{
$noPrefixExtractor = new ReflectionExtractor([], [], []);
$this->assertSame(
[
'bal',
'parent',
'collection',
'collectionAsObject',
'nestedCollection',
'mixedCollection',
'B',
'Guid',
'g',
'h',
'i',
'j',
'nullableCollectionOfNonNullableElements',
'nonNullableCollectionOfNullableElements',
'nullableCollectionOfMultipleNonNullableElementTypes',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
'nestedIterators',
'arrayWithKeys',
'arrayWithKeysAndComplexValue',
'arrayOfMixed',
'listOfStrings',
'parentAnnotation',
'genericInterface',
'foo',
'foo2',
'foo3',
'foo4',
'foo5',
'files',
'propertyTypeStatic',
'parentAnnotationNoParent',
'rootDummyItems',
'rootDummyItem',
],
$noPrefixExtractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')
);
}
/**
* @dataProvider typesProvider
*/
public function testExtractors($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, []));
}
public static function typesProvider()
{
return [
['a', null],
['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['c', [new Type(Type::BUILTIN_TYPE_BOOL)]],
['d', [new Type(Type::BUILTIN_TYPE_BOOL)]],
['e', null],
['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]],
['donotexist', null],
['staticGetter', null],
['staticSetter', null],
['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')]],
['realParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['date', [new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class)]],
['dates', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class))]],
];
}
/**
* @dataProvider php7TypesProvider
*/
public function testExtractPhp7Type(string $class, string $property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes($class, $property, []));
}
public static function php7TypesProvider()
{
return [
[Php7Dummy::class, 'foo', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]],
[Php7Dummy::class, 'bar', [new Type(Type::BUILTIN_TYPE_INT)]],
[Php7Dummy::class, 'baz', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]],
[Php7Dummy::class, 'buz', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy')]],
[Php7Dummy::class, 'biz', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Php7ParentDummy::class)]],
[Php7Dummy::class, 'donotexist', null],
[Php7ParentDummy::class, 'parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, \stdClass::class)]],
];
}
/**
* @dataProvider php71TypesProvider
*/
public function testExtractPhp71Type($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy', $property, []));
}
public static function php71TypesProvider()
{
return [
['foo', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]],
['buz', [new Type(Type::BUILTIN_TYPE_NULL)]],
['bar', [new Type(Type::BUILTIN_TYPE_INT, true)]],
['baz', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]],
['donotexist', null],
];
}
/**
* @dataProvider php80TypesProvider
*/
public function testExtractPhp80Type($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy', $property, []));
}
public static function php80TypesProvider()
{
return [
['foo', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]],
['bar', [new Type(Type::BUILTIN_TYPE_INT, true)]],
['timeout', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]],
['optional', [new Type(Type::BUILTIN_TYPE_INT, true), new Type(Type::BUILTIN_TYPE_FLOAT, true)]],
['string', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Stringable'), new Type(Type::BUILTIN_TYPE_STRING)]],
['payload', null],
['data', null],
['mixedProperty', null],
];
}
/**
* @dataProvider php81TypesProvider
*/
public function testExtractPhp81Type($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy', $property, []));
}
public static function php81TypesProvider()
{
return [
['nothing', null],
['collection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Traversable'), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Countable')]],
];
}
public function testReadonlyPropertiesAreNotWriteable()
{
$this->assertFalse($this->extractor->isWritable(Php81Dummy::class, 'foo'));
}
/**
* @dataProvider php82TypesProvider
*
* @requires PHP 8.2
*/
public function testExtractPhp82Type($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php82Dummy', $property, []));
}
public static function php82TypesProvider(): iterable
{
yield ['nil', null];
yield ['false', [new Type(Type::BUILTIN_TYPE_FALSE)]];
yield ['true', [new Type(Type::BUILTIN_TYPE_TRUE)]];
// Nesting intersection and union types is not supported yet,
// but we should make sure this kind of composite types does not crash the extractor.
yield ['someCollection', null];
}
/**
* @dataProvider defaultValueProvider
*/
public function testExtractWithDefaultValue($property, $type)
{
$this->assertEquals($type, $this->extractor->getTypes(DefaultValue::class, $property, []));
}
public static function defaultValueProvider()
{
return [
['defaultInt', [new Type(Type::BUILTIN_TYPE_INT, false)]],
['defaultFloat', [new Type(Type::BUILTIN_TYPE_FLOAT, false)]],
['defaultString', [new Type(Type::BUILTIN_TYPE_STRING, false)]],
['defaultArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]],
['defaultNull', null],
];
}
/**
* @dataProvider getReadableProperties
*/
public function testIsReadable($property, $expected)
{
$this->assertSame(
$expected,
$this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, [])
);
}
public static function getReadableProperties()
{
return [
['bar', false],
['baz', false],
['parent', true],
['a', true],
['b', false],
['c', true],
['d', true],
['e', false],
['f', false],
['Id', true],
['id', true],
['Guid', true],
['guid', false],
['element', false],
];
}
/**
* @dataProvider getWritableProperties
*/
public function testIsWritable($property, $expected)
{
$this->assertSame(
$expected,
$this->extractor->isWritable(Dummy::class, $property, [])
);
}
public static function getWritableProperties()
{
return [
['bar', false],
['baz', false],
['parent', true],
['a', false],
['b', true],
['c', false],
['d', false],
['e', true],
['f', true],
['Id', false],
['Guid', true],
['guid', false],
];
}
public function testIsReadableSnakeCase()
{
$this->assertTrue($this->extractor->isReadable(SnakeCaseDummy::class, 'snake_property'));
$this->assertTrue($this->extractor->isReadable(SnakeCaseDummy::class, 'snake_readonly'));
}
public function testIsWriteableSnakeCase()
{
$this->assertTrue($this->extractor->isWritable(SnakeCaseDummy::class, 'snake_property'));
$this->assertFalse($this->extractor->isWritable(SnakeCaseDummy::class, 'snake_readonly'));
// Ensure that it's still possible to write to the property using the (old) snake name
$this->assertTrue($this->extractor->isWritable(SnakeCaseDummy::class, 'snake_method'));
}
public function testSingularize()
{
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'analyses'));
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet'));
$this->assertEquals(['analyses', 'feet'], $this->extractor->getProperties(AdderRemoverDummy::class));
}
public function testPrivatePropertyExtractor()
{
$privateExtractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED);
$properties = $privateExtractor->getProperties(Dummy::class);
$this->assertContains('bar', $properties);
$this->assertContains('baz', $properties);
$this->assertTrue($privateExtractor->isReadable(Dummy::class, 'bar'));
$this->assertTrue($privateExtractor->isReadable(Dummy::class, 'baz'));
$protectedExtractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED);
$properties = $protectedExtractor->getProperties(Dummy::class);
$this->assertNotContains('bar', $properties);
$this->assertContains('baz', $properties);
$this->assertFalse($protectedExtractor->isReadable(Dummy::class, 'bar'));
$this->assertTrue($protectedExtractor->isReadable(Dummy::class, 'baz'));
}
/**
* @dataProvider getInitializableProperties
*/
public function testIsInitializable(string $class, string $property, bool $expected)
{
$this->assertSame($expected, $this->extractor->isInitializable($class, $property));
}
public static function getInitializableProperties(): array
{
return [
[Php71Dummy::class, 'string', true],
[Php71Dummy::class, 'intPrivate', true],
[Php71Dummy::class, 'notExist', false],
[Php71DummyExtended2::class, 'intWithAccessor', true],
[Php71DummyExtended2::class, 'intPrivate', false],
[NotInstantiable::class, 'foo', false],
];
}
/**
* @dataProvider constructorTypesProvider
*/
public function testExtractTypeConstructor(string $class, string $property, ?array $type = null)
{
/* Check that constructor extractions works by default, and if passed in via context.
Check that null is returned if constructor extraction is disabled */
$this->assertEquals($type, $this->extractor->getTypes($class, $property, []));
$this->assertEquals($type, $this->extractor->getTypes($class, $property, ['enable_constructor_extraction' => true]));
$this->assertNull($this->extractor->getTypes($class, $property, ['enable_constructor_extraction' => false]));
}
public static function constructorTypesProvider(): array
{
return [
// php71 dummy has following constructor: __construct(string $string, int $intPrivate)
[Php71Dummy::class, 'string', [new Type(Type::BUILTIN_TYPE_STRING, false)]],
[Php71Dummy::class, 'intPrivate', [new Type(Type::BUILTIN_TYPE_INT, false)]],
// Php71DummyExtended2 adds int $intWithAccessor
[Php71DummyExtended2::class, 'intWithAccessor', [new Type(Type::BUILTIN_TYPE_INT, false)]],
[Php71DummyExtended2::class, 'intPrivate', [new Type(Type::BUILTIN_TYPE_INT, false)]],
[DefaultValue::class, 'foo', null],
];
}
public function testNullOnPrivateProtectedAccessor()
{
$barAcessor = $this->extractor->getReadInfo(Dummy::class, 'bar');
$barMutator = $this->extractor->getWriteInfo(Dummy::class, 'bar');
$bazAcessor = $this->extractor->getReadInfo(Dummy::class, 'baz');
$bazMutator = $this->extractor->getWriteInfo(Dummy::class, 'baz');
$this->assertNull($barAcessor);
$this->assertEquals(PropertyWriteInfo::TYPE_NONE, $barMutator->getType());
$this->assertNull($bazAcessor);
$this->assertEquals(PropertyWriteInfo::TYPE_NONE, $bazMutator->getType());
}
public function testTypedProperties()
{
$this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], $this->extractor->getTypes(Php74Dummy::class, 'dummy'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_BOOL, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableBoolProp'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], $this->extractor->getTypes(Php74Dummy::class, 'stringCollection'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_INT, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableWithDefault'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)], $this->extractor->getTypes(Php74Dummy::class, 'collection'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class))], $this->extractor->getTypes(Php74Dummy::class, 'nullableTypedCollection'));
}
/**
* @dataProvider readAccessorProvider
*/
public function testGetReadAccessor($class, $property, $found, $type, $name, $visibility, $static)
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE);
$readAcessor = $extractor->getReadInfo($class, $property);
if (!$found) {
$this->assertNull($readAcessor);
return;
}
$this->assertNotNull($readAcessor);
$this->assertSame($type, $readAcessor->getType());
$this->assertSame($name, $readAcessor->getName());
$this->assertSame($visibility, $readAcessor->getVisibility());
$this->assertSame($static, $readAcessor->isStatic());
}
public static function readAccessorProvider(): array
{
return [
[Dummy::class, 'bar', true, PropertyReadInfo::TYPE_PROPERTY, 'bar', PropertyReadInfo::VISIBILITY_PRIVATE, false],
[Dummy::class, 'baz', true, PropertyReadInfo::TYPE_PROPERTY, 'baz', PropertyReadInfo::VISIBILITY_PROTECTED, false],
[Dummy::class, 'bal', true, PropertyReadInfo::TYPE_PROPERTY, 'bal', PropertyReadInfo::VISIBILITY_PUBLIC, false],
[Dummy::class, 'parent', true, PropertyReadInfo::TYPE_PROPERTY, 'parent', PropertyReadInfo::VISIBILITY_PUBLIC, false],
[Dummy::class, 'static', true, PropertyReadInfo::TYPE_METHOD, 'getStatic', PropertyReadInfo::VISIBILITY_PUBLIC, true],
[Dummy::class, 'foo', true, PropertyReadInfo::TYPE_PROPERTY, 'foo', PropertyReadInfo::VISIBILITY_PUBLIC, false],
[Php71Dummy::class, 'foo', true, PropertyReadInfo::TYPE_METHOD, 'getFoo', PropertyReadInfo::VISIBILITY_PUBLIC, false],
[Php71Dummy::class, 'buz', true, PropertyReadInfo::TYPE_METHOD, 'getBuz', PropertyReadInfo::VISIBILITY_PUBLIC, false],
];
}
/**
* @dataProvider writeMutatorProvider
*/
public function testGetWriteMutator($class, $property, $allowConstruct, $found, $type, $name, $addName, $removeName, $visibility, $static)
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE);
$writeMutator = $extractor->getWriteInfo($class, $property, [
'enable_constructor_extraction' => $allowConstruct,
'enable_getter_setter_extraction' => true,
]);
if (!$found) {
$this->assertEquals(PropertyWriteInfo::TYPE_NONE, $writeMutator->getType());
return;
}
$this->assertNotNull($writeMutator);
$this->assertSame($type, $writeMutator->getType());
if (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $writeMutator->getType()) {
$this->assertNotNull($writeMutator->getAdderInfo());
$this->assertSame($addName, $writeMutator->getAdderInfo()->getName());
$this->assertNotNull($writeMutator->getRemoverInfo());
$this->assertSame($removeName, $writeMutator->getRemoverInfo()->getName());
}
if (PropertyWriteInfo::TYPE_CONSTRUCTOR === $writeMutator->getType()) {
$this->assertSame($name, $writeMutator->getName());
}
if (PropertyWriteInfo::TYPE_PROPERTY === $writeMutator->getType()) {
$this->assertSame($name, $writeMutator->getName());
$this->assertSame($visibility, $writeMutator->getVisibility());
$this->assertSame($static, $writeMutator->isStatic());
}
if (PropertyWriteInfo::TYPE_METHOD === $writeMutator->getType()) {
$this->assertSame($name, $writeMutator->getName());
$this->assertSame($visibility, $writeMutator->getVisibility());
$this->assertSame($static, $writeMutator->isStatic());
}
}
public static function writeMutatorProvider(): array
{
return [
[Dummy::class, 'bar', false, true, PropertyWriteInfo::TYPE_PROPERTY, 'bar', null, null, PropertyWriteInfo::VISIBILITY_PRIVATE, false],
[Dummy::class, 'baz', false, true, PropertyWriteInfo::TYPE_PROPERTY, 'baz', null, null, PropertyWriteInfo::VISIBILITY_PROTECTED, false],
[Dummy::class, 'bal', false, true, PropertyWriteInfo::TYPE_PROPERTY, 'bal', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Dummy::class, 'parent', false, true, PropertyWriteInfo::TYPE_PROPERTY, 'parent', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Dummy::class, 'staticSetter', false, true, PropertyWriteInfo::TYPE_METHOD, 'staticSetter', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, true],
[Dummy::class, 'foo', false, true, PropertyWriteInfo::TYPE_PROPERTY, 'foo', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71Dummy::class, 'bar', false, true, PropertyWriteInfo::TYPE_METHOD, 'setBar', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71Dummy::class, 'string', false, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71Dummy::class, 'string', true, true, PropertyWriteInfo::TYPE_CONSTRUCTOR, 'string', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71Dummy::class, 'baz', false, true, PropertyWriteInfo::TYPE_ADDER_AND_REMOVER, null, 'addBaz', 'removeBaz', PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended::class, 'bar', false, true, PropertyWriteInfo::TYPE_METHOD, 'setBar', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended::class, 'string', false, false, -1, '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended::class, 'string', true, true, PropertyWriteInfo::TYPE_CONSTRUCTOR, 'string', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended::class, 'baz', false, true, PropertyWriteInfo::TYPE_ADDER_AND_REMOVER, null, 'addBaz', 'removeBaz', PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended2::class, 'bar', false, true, PropertyWriteInfo::TYPE_METHOD, 'setBar', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended2::class, 'string', false, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended2::class, 'string', true, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended2::class, 'baz', false, true, PropertyWriteInfo::TYPE_ADDER_AND_REMOVER, null, 'addBaz', 'removeBaz', PropertyWriteInfo::VISIBILITY_PUBLIC, false],
];
}
public function testDisabledAdderAndRemoverReturnsError()
{
$writeMutator = $this->extractor->getWriteInfo(Php71Dummy::class, 'baz', [
'enable_adder_remover_extraction' => false,
]);
self::assertNotNull($writeMutator);
self::assertSame(PropertyWriteInfo::TYPE_NONE, $writeMutator->getType());
self::assertSame([\sprintf('The property "baz" in class "%s" can be defined with the methods "addBaz()", "removeBaz()" but the new value must be an array or an instance of \Traversable', Php71Dummy::class)], $writeMutator->getErrors());
}
public function testGetWriteInfoReadonlyProperties()
{
$writeMutatorConstructor = $this->extractor->getWriteInfo(Php81Dummy::class, 'foo', ['enable_constructor_extraction' => true]);
$writeMutatorWithoutConstructor = $this->extractor->getWriteInfo(Php81Dummy::class, 'foo', ['enable_constructor_extraction' => false]);
$this->assertSame(PropertyWriteInfo::TYPE_CONSTRUCTOR, $writeMutatorConstructor->getType());
$this->assertSame(PropertyWriteInfo::TYPE_NONE, $writeMutatorWithoutConstructor->getType());
}
/**
* @dataProvider extractConstructorTypesProvider
*/
public function testExtractConstructorTypes(string $property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
}
public static function extractConstructorTypesProvider(): array
{
return [
['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
['date', null],
['dateObject', null],
['dateTime', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]],
['ddd', null],
];
}
/**
* @requires PHP 8.4
*/
public function testAsymmetricVisibility()
{
$this->assertTrue($this->extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertTrue($this->extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertFalse($this->extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
$this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
}
/**
* @requires PHP 8.4
*/
public function testAsymmetricVisibilityAllowPublicOnly()
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC);
$this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
$this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
}
/**
* @requires PHP 8.4
*/
public function testAsymmetricVisibilityAllowProtectedOnly()
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PROTECTED);
$this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
$this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
}
/**
* @requires PHP 8.4
*/
public function testAsymmetricVisibilityAllowPrivateOnly()
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PRIVATE);
$this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
$this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
$this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
$this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
}
/**
* @requires PHP 8.4
*/
public function testVirtualProperties()
{
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualNoSetHook'));
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualSetHookOnly'));
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualHook'));
$this->assertFalse($this->extractor->isWritable(VirtualProperties::class, 'virtualNoSetHook'));
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class, 'virtualSetHookOnly'));
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class, 'virtualHook'));
}
/**
* @dataProvider provideAsymmetricVisibilityMutator
* @requires PHP 8.4
*/
public function testAsymmetricVisibilityMutator(string $property, string $readVisibility, string $writeVisibility)
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE);
$readMutator = $extractor->getReadInfo(AsymmetricVisibility::class, $property);
$writeMutator = $extractor->getWriteInfo(AsymmetricVisibility::class, $property, [
'enable_getter_setter_extraction' => true,
]);
$this->assertSame(PropertyReadInfo::TYPE_PROPERTY, $readMutator->getType());
$this->assertSame(PropertyWriteInfo::TYPE_PROPERTY, $writeMutator->getType());
$this->assertSame($readVisibility, $readMutator->getVisibility());
$this->assertSame($writeVisibility, $writeMutator->getVisibility());
}
public static function provideAsymmetricVisibilityMutator(): iterable
{
yield ['publicPrivate', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE];
yield ['publicProtected', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PROTECTED];
yield ['protectedPrivate', PropertyReadInfo::VISIBILITY_PROTECTED, PropertyWriteInfo::VISIBILITY_PRIVATE];
}
/**
* @dataProvider provideVirtualPropertiesMutator
* @requires PHP 8.4
*/
public function testVirtualPropertiesMutator(string $property, string $readVisibility, string $writeVisibility)
{
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE);
$readMutator = $extractor->getReadInfo(VirtualProperties::class, $property);
$writeMutator = $extractor->getWriteInfo(VirtualProperties::class, $property, [
'enable_getter_setter_extraction' => true,
]);
$this->assertSame(PropertyReadInfo::TYPE_PROPERTY, $readMutator->getType());
$this->assertSame(PropertyWriteInfo::TYPE_PROPERTY, $writeMutator->getType());
$this->assertSame($readVisibility, $readMutator->getVisibility());
$this->assertSame($writeVisibility, $writeMutator->getVisibility());
}
public static function provideVirtualPropertiesMutator(): iterable
{
yield ['virtualNoSetHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE];
yield ['virtualSetHookOnly', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC];
yield ['virtualHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC];
}
}

View File

@@ -0,0 +1,63 @@
<?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\Extractor;
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\IgnorePropertyDummy;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class SerializerExtractorTest extends TestCase
{
private SerializerExtractor $extractor;
protected function setUp(): void
{
if (class_exists(AttributeLoader::class)) {
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
} else {
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
}
$this->extractor = new SerializerExtractor($classMetadataFactory);
}
public function testGetProperties()
{
$this->assertEquals(
['collection'],
$this->extractor->getProperties(Dummy::class, ['serializer_groups' => ['a']])
);
}
public function testGetPropertiesWithIgnoredProperties()
{
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['a']]));
}
public function testGetPropertiesWithAnyGroup()
{
$this->assertSame(['analyses', 'feet'], $this->extractor->getProperties(AdderRemoverDummy::class, ['serializer_groups' => null]));
}
public function testGetPropertiesWithNonExistentClassReturnsNull()
{
$this->assertSame(null, $this->extractor->getProperties('NonExistent'));
}
}

View File

@@ -0,0 +1,29 @@
<?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 AdderRemoverDummy
{
private $analyses;
private $feet;
public function addAnalyse(Dummy $analyse)
{
}
public function removeFoot(Dummy $foot)
{
}
}

View File

@@ -0,0 +1,19 @@
<?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;
class AsymmetricVisibility
{
public private(set) mixed $publicPrivate;
public protected(set) mixed $publicProtected;
protected private(set) mixed $protectedPrivate;
}

View File

@@ -0,0 +1,40 @@
<?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 Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
class ConstructorDummy
{
/** @var string */
private $timezone;
/** @var \DateTimeInterface */
private $date;
/** @var int */
private $dateTime;
/**
* @param \DateTimeZone $timezone
* @param int $date Timestamp
* @param \DateTimeInterface $dateObject
* @param mixed $mixed
*/
public function __construct(\DateTimeZone $timezone, $date, $dateObject, \DateTimeImmutable $dateTime, $mixed)
{
$this->timezone = $timezone->getName();
$this->date = \DateTimeImmutable::createFromFormat('U', $date);
$this->dateTime = $dateTime->getTimestamp();
}
}

View File

@@ -0,0 +1,19 @@
<?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;
class ConstructorDummyWithoutDocBlock
{
public function __construct(\DateTimeZone $timezone, $date, $dateObject, \DateTimeImmutable $dateTime, $mixed)
{
}
}

View File

@@ -0,0 +1,24 @@
<?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 Tales Santos <tales.augusto.santos@gmail.com>
*/
class DefaultValue
{
public $defaultInt = 30;
public $defaultFloat = 30.5;
public $defaultString = 'foo';
public $defaultArray = [];
public $defaultNull = null;
}

View File

@@ -0,0 +1,62 @@
<?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;
/**
* PhpDocExtractor should fallback from property -> accessor -> mutator when looking up docblocks.
*
* @author Martin Rademacher <mano@radebatz.net>
*/
class DockBlockFallback
{
/** @var string $pub */
public $pub = 'pub';
protected $protAcc;
protected $protMut;
public function getPub()
{
return $this->pub;
}
public function setPub($pub)
{
$this->pub = $pub;
}
/**
* @return int
*/
public function getProtAcc(): int
{
return $this->protAcc;
}
public function setProt($protAcc)
{
$this->protAcc = $protAcc;
}
public function getProtMut()
{
return $this->protMut;
}
/**
* @param bool $protMut
*/
public function setProtMut($protMut)
{
$this->protMut = $protMut;
}
}

View File

@@ -0,0 +1,272 @@
<?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;
use Symfony\Component\Serializer\Annotation\Groups as GroupsAnnotation;
use Symfony\Component\Serializer\Attribute\Groups;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class Dummy extends ParentDummy
{
/**
* @var string This is bar
*/
private $bar;
/**
* Should be used.
*
* @var int Should be ignored
*/
protected $baz;
/**
* @var \DateTimeImmutable
*/
public $bal;
/**
* @var ParentDummy
*/
public $parent;
/**
* @var \DateTimeImmutable[]
* @GroupsAnnotation({"a", "b"})
*/
#[Groups(['a', 'b'])]
public $collection;
/**
* @var DummyCollection<int, string>
*/
public $collectionAsObject;
/**
* @var string[][]
*/
public $nestedCollection;
/**
* @var mixed[]
*/
public $mixedCollection;
/**
* @var ParentDummy
*/
public $B;
/**
* @var int
*/
protected $Id;
/**
* @var string
*/
public $Guid;
/**
* Nullable array.
*
* @var array|null
*/
public $g;
/**
* @var ?string
*/
public $h;
/**
* @var string|int|null
*/
public $i;
/**
* @var ?\DateTimeImmutable
*/
public $j;
/**
* @var int[]|null
*/
public $nullableCollectionOfNonNullableElements;
/**
* @var array<null|int>
*/
public $nonNullableCollectionOfNullableElements;
/**
* @var null|array<int|string>
*/
public $nullableCollectionOfMultipleNonNullableElementTypes;
/**
* @var array
*/
private $xTotals;
/**
* @var string
*/
private $YT;
/**
* This should not be removed.
*
* @var
*/
public $emptyVar;
/**
* @var \Iterator<string>
*/
public $iteratorCollection;
/**
* @var \Iterator<integer,string>
*/
public $iteratorCollectionWithKey;
/**
* @var \Iterator<integer,\Iterator<integer,string>>
*/
public $nestedIterators;
/**
* @var array<string,string>
*/
public $arrayWithKeys;
/**
* @var array<string,array<integer,null|string>|null>
*/
public $arrayWithKeysAndComplexValue;
/**
* @var array<string,mixed>
*/
public $arrayOfMixed;
/**
* @var list<string>
*/
public $listOfStrings;
/**
* @var parent
*/
public $parentAnnotation;
/**
* @var \BackedEnum<string>
*/
public $genericInterface;
public static function getStatic()
{
}
/**
* @return string
*/
public static function staticGetter()
{
}
public static function staticSetter(\DateTimeImmutable $d)
{
}
/**
* A.
*
* @return int
*/
public function getA()
{
}
/**
* B.
*
* @param ParentDummy|null $parent
*/
public function setB(?ParentDummy $parent = null)
{
}
/**
* Date of Birth.
*
* @return \DateTimeImmutable
*/
public function getDOB()
{
}
/**
* @return int
*/
public function getId()
{
}
public function get123()
{
}
/**
* @param self $self
*/
public function setSelf(self $self)
{
}
/**
* @param parent $realParent
*/
public function setRealParent(parent $realParent)
{
}
/**
* @return array
*/
public function getXTotals()
{
}
/**
* @return string
*/
public function getYT()
{
}
public function setDate(\DateTimeImmutable $date)
{
}
public function addDate(\DateTimeImmutable $date)
{
}
public function hasElement(string $element): bool
{
}
}

View File

@@ -0,0 +1,20 @@
<?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;
final class DummyCollection implements \IteratorAggregate
{
public function getIterator(): \Traversable
{
return [];
}
}

View File

@@ -0,0 +1,66 @@
<?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;
use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface;
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;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
public function getShortDescription($class, $property, array $context = []): ?string
{
return 'short';
}
public function getLongDescription($class, $property, array $context = []): ?string
{
return 'long';
}
public function getTypes($class, $property, array $context = []): ?array
{
return [new Type(Type::BUILTIN_TYPE_INT)];
}
public function getTypesFromConstructor(string $class, string $property): ?array
{
return [new Type(Type::BUILTIN_TYPE_STRING)];
}
public function isReadable($class, $property, array $context = []): ?bool
{
return true;
}
public function isWritable($class, $property, array $context = []): ?bool
{
return true;
}
public function getProperties($class, array $context = []): ?array
{
return ['a', 'b'];
}
public function isInitializable(string $class, string $property, array $context = []): ?bool
{
return true;
}
}

View File

@@ -0,0 +1,41 @@
<?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;
interface IFace {}
class Clazz {}
class DummyGeneric
{
/**
* @var Clazz<Dummy>
*/
public $basicClass;
/**
* @var ?Clazz<Dummy>
*/
public $nullableClass;
/**
* @var IFace<Dummy>
*/
public $basicInterface;
/**
* @var ?IFace<Dummy>
*/
public $nullableInterface;
}

View File

@@ -0,0 +1,23 @@
<?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;
use Symfony\Component\PropertyInfo\Tests as TestNamespace;
/**
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class DummyNamespace
{
/** @var TestNamespace\Fixtures\Dummy */
private $dummy;
}

View File

@@ -0,0 +1,56 @@
<?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;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait;
trait DummyTraitExternal
{
/**
* @var string
*/
private $propertyInExternalTraitPrimitiveType;
/**
* @var Dummy
*/
private $propertyInExternalTraitObjectSameNamespace;
/**
* @var DummyUsedInTrait
*/
private $propertyInExternalTraitObjectDifferentNamespace;
/**
* @return string
*/
public function getMethodInExternalTraitPrimitiveType()
{
return 'value';
}
/**
* @return Dummy
*/
public function getMethodInExternalTraitObjectSameNamespace()
{
return new Dummy();
}
/**
* @return DummyUsedInTrait
*/
public function getMethodInExternalTraitObjectDifferentNamespace()
{
return new DummyUsedInTrait();
}
}

View File

@@ -0,0 +1,56 @@
<?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 Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class DummyUnionType
{
private const TYPE_A = 'a';
private const TYPE_B = 'b';
/**
* @var string|int
*/
public $a;
/**
* @var (string|int)[]
*/
public $b;
/**
* @var array<string|int>
*/
public $c;
/**
* @var array<string|int, array<string>>
*/
public $d;
/**
* @var (Dummy<array<mixed, string>, (int | (\Traversable<DefaultValue>)[])> | ParentDummy | null)
*/
public $e;
/**
* @var self::TYPE_*|null
*/
public $f;
/**
* @var non-empty-array<string|int>
*/
public $g;
}

View File

@@ -0,0 +1,29 @@
<?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 A {
class Property {
}
class Dummy {
/**
* @var Property
*/
public $property;
}
}
namespace B {
class Dummy extends \A\Dummy {
}
}

View File

@@ -0,0 +1,36 @@
<?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;
use Symfony\Component\Serializer\Annotation\Groups as GroupsAnnotation;
use Symfony\Component\Serializer\Annotation\Ignore as IgnoreAnnotation;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\Ignore;
/**
* @author Vadim Borodavko <vadim.borodavko@gmail.com>
*/
class IgnorePropertyDummy
{
/**
* @GroupsAnnotation({"a"})
*/
#[Groups(['a'])]
public $visibleProperty;
/**
* @GroupsAnnotation({"a"})
* @IgnoreAnnotation
*/
#[Groups(['a']), Ignore]
private $ignoredProperty;
}

View File

@@ -0,0 +1,30 @@
<?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;
class IntRangeDummy
{
/**
* @var int<0, 100>
*/
public $a;
/**
* @var int<min, 100>|null
*/
public $b;
/**
* @var int<50, max>
*/
public $c;
}

View File

@@ -0,0 +1,50 @@
<?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 Martin Rademacher <mano@radebatz.net>
*/
class InvalidDummy
{
/**
* @var
*/
public $pub;
/**
* @return
*/
public static function getStat()
{
return 'stat';
}
/**
* Foo.
*
* @param
*/
public function setFoo($foo)
{
}
/**
* Bar.
*
* @return
*/
public function getBar()
{
return 'bar';
}
}

View File

@@ -0,0 +1,19 @@
<?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 NoProperties
{
}

View File

@@ -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)
{
}
}

View File

@@ -0,0 +1,85 @@
<?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;
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;
/**
* Not able to guess anything.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class NullExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface
{
public function getShortDescription($class, $property, array $context = []): ?string
{
$this->assertIsString($class);
$this->assertIsString($property);
return null;
}
public function getLongDescription($class, $property, array $context = []): ?string
{
$this->assertIsString($class);
$this->assertIsString($property);
return null;
}
public function getTypes($class, $property, array $context = []): ?array
{
$this->assertIsString($class);
$this->assertIsString($property);
return null;
}
public function isReadable($class, $property, array $context = []): ?bool
{
$this->assertIsString($class);
$this->assertIsString($property);
return null;
}
public function isWritable($class, $property, array $context = []): ?bool
{
$this->assertIsString($class);
$this->assertIsString($property);
return null;
}
public function getProperties($class, array $context = []): ?array
{
$this->assertIsString($class);
return null;
}
public function isInitializable(string $class, string $property, array $context = []): ?bool
{
return null;
}
private function assertIsString($string)
{
if (!\is_string($string)) {
throw new \InvalidArgumentException(sprintf('"%s" expects strings, given "%s".', __CLASS__, get_debug_type($string)));
}
}
}

View File

@@ -0,0 +1,128 @@
<?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;
use Symfony\Component\PropertyInfo\Tests\Fixtures\RootDummy\RootDummyItem;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ParentDummy
{
/**
* Short description.
*
* Long description.
*/
public $foo;
/**
* @var float
*/
public $foo2;
/**
* @var callable
*/
public $foo3;
/**
* @var void
*/
public $foo4;
/**
* @var mixed
*/
public $foo5;
/**
* @var \SplFileInfo[]|resource
*/
public $files;
/**
* @var static
*/
public $propertyTypeStatic;
/**
* @var parent
*/
public $parentAnnotationNoParent;
/**
* @var RootDummyItem[]
*/
public $rootDummyItems;
/**
* @var \Symfony\Component\PropertyInfo\Tests\Fixtures\RootDummy\RootDummyItem
*/
public $rootDummyItem;
/**
* @return bool|null
*/
public function isC()
{
}
/**
* @return true|null
*/
public function isCt()
{
}
/**
* @return false|null
*/
public function isCf()
{
}
/**
* @return bool
*/
public function canD()
{
}
/**
* @return true
*/
public function canDt()
{
}
/**
* @return false
*/
public function canDf()
{
}
/**
* @param resource $e
*/
public function addE($e)
{
}
/**
* @param \DateTimeImmutable $f
*/
public function removeF(\DateTimeImmutable $f)
{
}
}

View File

@@ -0,0 +1,57 @@
<?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 Teoh Han Hui <teohhanhui@gmail.com>
*/
class Php71Dummy
{
public function __construct(string $string, int $intPrivate)
{
}
public function getFoo(): ?array
{
}
public function getBuz(): void
{
}
public function setBar(?int $bar)
{
}
public function addBaz(string $baz)
{
}
public function removeBaz(string $baz)
{
}
}
class Php71DummyExtended extends Php71Dummy
{
}
class Php71DummyExtended2 extends Php71Dummy
{
public function __construct(int $intWithAccessor)
{
}
public function getIntWithAccessor()
{
}
}

View File

@@ -0,0 +1,40 @@
<?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 Php74Dummy
{
public Dummy $dummy;
private ?bool $nullableBoolProp;
/** @var string[] */
private array $stringCollection;
private ?int $nullableWithDefault = 1;
public array $collection = [];
/** @var Dummy[]|null */
public ?array $nullableTypedCollection = null;
public function addStringCollection(string $string): void
{
}
public function removeStringCollection(string $string): void
{
}
public function addNullableTypedCollection(Dummy $dummy): void
{
}
}

View File

@@ -0,0 +1,38 @@
<?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 Php7Dummy extends Php7ParentDummy
{
public function getFoo(): array
{
}
public function setBar(int $bar)
{
}
public function addBaz(string $baz)
{
}
public function getBuz(): self
{
}
public function getBiz(): parent
{
}
}

View File

@@ -0,0 +1,19 @@
<?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;
class Php7ParentDummy extends \stdClass
{
public function getParent(): parent
{
}
}

View File

@@ -0,0 +1,67 @@
<?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;
class Php80Dummy
{
public mixed $mixedProperty;
/**
* @param string $promotedAndMutated
* @param string $promotedWithDocComment
* @param string $promotedWithDocCommentAndType
* @param array<string> $collection
*/
public function __construct(
private mixed $promoted,
private mixed $promotedAndMutated,
/**
* Comment without @var.
*/
private mixed $promotedWithDocComment,
/**
* @var int
*/
private mixed $promotedWithDocCommentAndType,
private array $collection,
)
{
}
public function getFoo(): array|null
{
}
public function setBar(int|null $bar)
{
}
public function setTimeout(int|float $timeout)
{
}
public function getOptional(): int|float|null
{
}
public function setString(string|\Stringable $string)
{
}
public function setPayload(mixed $payload)
{
}
public function getData(): mixed
{
}
}

View File

@@ -0,0 +1,24 @@
<?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;
class Php80PromotedDummy
{
public function __construct(private string $promoted)
{
}
public function getPromoted(): string
{
return $this->promoted;
}
}

View File

@@ -0,0 +1,28 @@
<?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;
class Php81Dummy
{
public function __construct(public readonly string $foo)
{
}
public function getNothing(): never
{
throw new \Exception('Oops');
}
public function getCollection(): \Traversable&\Countable
{
}
}

View File

@@ -0,0 +1,23 @@
<?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;
class Php82Dummy
{
public null $nil = null;
public false $false = false;
public true $true = true;
public (\Traversable&\Countable)|null $someCollection = null;
}

View File

@@ -0,0 +1,45 @@
<?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 Emil Masiakowski <emil.masiakowski@gmail.com>
*/
class PhpStanPseudoTypesDummy extends PseudoTypesDummy
{
/** @var negative-int */
public $negativeInt;
/** @var non-empty-array */
public $nonEmptyArray;
/** @var non-empty-list */
public $nonEmptyList;
/** @var interface-string */
public $interfaceString;
/** @var scalar */
public $scalar;
/** @var array-key */
public $arrayKey;
/** @var number */
public $number;
/** @var numeric */
public $numeric;
/** @var double */
public $double;
}

View File

@@ -0,0 +1,20 @@
<?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;
class PseudoTypeDummy
{
/**
* @var scalar
*/
public $unknownPseudoType;
}

View File

@@ -0,0 +1,48 @@
<?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 Emil Masiakowski <emil.masiakowski@gmail.com>
*/
class PseudoTypesDummy
{
/** @var class-string */
public $classString;
/** @var class-string<\stdClass> */
public $classStringGeneric;
/** @var html-escaped-string */
public $htmlEscapedString;
/** @var lowercase-string */
public $lowercaseString;
/** @var non-empty-lowercase-string */
public $nonEmptyLowercaseString;
/** @var non-empty-string */
public $nonEmptyString;
/** @var numeric-string */
public $numericString;
/** @var trait-string */
public $traitString;
/** @var positive-int */
public $positiveInt;
/** @var literal-string */
public $literalString;
}

View File

@@ -0,0 +1,16 @@
<?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\RootDummy;
class RootDummyItem
{
}

View File

@@ -0,0 +1,39 @@
<?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;
class SnakeCaseDummy
{
private string $snake_property;
private string $snake_readOnly;
private string $snake_method;
public function getSnakeProperty()
{
return $this->snake_property;
}
public function getSnakeReadOnly()
{
return $this->snake_readOnly;
}
public function setSnakeProperty($snake_property)
{
$this->snake_property = $snake_property;
}
public function setSnake_method($snake_method)
{
$this->snake_method = $snake_method;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\AnotherNamespace;
class DummyInAnotherNamespace
{
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\AnotherNamespace;
trait DummyTraitInAnotherNamespace
{
/**
* @var DummyInAnotherNamespace
*/
public $dummyInAnotherNamespace;
}

View File

@@ -0,0 +1,59 @@
<?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\TraitUsage;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyTraitExternal;
trait DummyTrait
{
use DummyTraitExternal;
/**
* @var string
*/
private $propertyInTraitPrimitiveType;
/**
* @var DummyUsedInTrait
*/
private $propertyInTraitObjectSameNamespace;
/**
* @var Dummy
*/
private $propertyInTraitObjectDifferentNamespace;
/**
* @return string
*/
public function getMethodInTraitPrimitiveType()
{
return 'value';
}
/**
* @return DummyUsedInTrait
*/
public function getMethodInTraitObjectSameNamespace()
{
return new DummyUsedInTrait();
}
/**
* @return Dummy
*/
public function getMethodInTraitObjectDifferentNamespace()
{
return new Dummy();
}
}

View File

@@ -0,0 +1,16 @@
<?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\TraitUsage;
class DummyUsedInTrait
{
}

View File

@@ -0,0 +1,20 @@
<?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\TraitUsage;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\AnotherNamespace\DummyTraitInAnotherNamespace;
class DummyUsingTrait
{
use DummyTrait;
use DummyTraitInAnotherNamespace;
}

View File

@@ -0,0 +1,19 @@
<?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;
class VirtualProperties
{
public bool $virtualNoSetHook { get => true; }
public bool $virtualSetHookOnly { set => $value; }
public bool $virtualHook { get => true; set => $value; }
}

View File

@@ -0,0 +1,70 @@
<?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;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoCacheExtractorTest extends AbstractPropertyInfoExtractorTest
{
protected function setUp(): void
{
parent::setUp();
$this->propertyInfo = new PropertyInfoCacheExtractor($this->propertyInfo, new ArrayAdapter());
}
public function testGetShortDescription()
{
parent::testGetShortDescription();
parent::testGetShortDescription();
}
public function testGetLongDescription()
{
parent::testGetLongDescription();
parent::testGetLongDescription();
}
public function testGetTypes()
{
parent::testGetTypes();
parent::testGetTypes();
}
public function testIsReadable()
{
parent::testIsReadable();
parent::testIsReadable();
}
public function testIsWritable()
{
parent::testIsWritable();
parent::testIsWritable();
}
public function testGetProperties()
{
parent::testGetProperties();
parent::testGetProperties();
}
public function testIsInitializable()
{
parent::testIsInitializable();
parent::testIsInitializable();
}
}

View File

@@ -0,0 +1,19 @@
<?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;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoExtractorTest extends AbstractPropertyInfoExtractorTest
{
}

View File

@@ -0,0 +1,88 @@
<?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;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class TypeTest extends TestCase
{
public function testConstruct()
{
$type = new Type('object', true, 'ArrayObject', true, new Type('int'), new Type('string'));
$this->assertEquals(Type::BUILTIN_TYPE_OBJECT, $type->getBuiltinType());
$this->assertTrue($type->isNullable());
$this->assertEquals('ArrayObject', $type->getClassName());
$this->assertTrue($type->isCollection());
$collectionKeyTypes = $type->getCollectionKeyTypes();
$this->assertIsArray($collectionKeyTypes);
$this->assertContainsOnlyInstancesOf('Symfony\Component\PropertyInfo\Type', $collectionKeyTypes);
$this->assertEquals(Type::BUILTIN_TYPE_INT, $collectionKeyTypes[0]->getBuiltinType());
$collectionValueTypes = $type->getCollectionValueTypes();
$this->assertIsArray($collectionValueTypes);
$this->assertContainsOnlyInstancesOf('Symfony\Component\PropertyInfo\Type', $collectionValueTypes);
$this->assertEquals(Type::BUILTIN_TYPE_STRING, $collectionValueTypes[0]->getBuiltinType());
}
public function testIterable()
{
$type = new Type('iterable');
$this->assertSame('iterable', $type->getBuiltinType());
}
public function testInvalidType()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('"foo" is not a valid PHP type.');
new Type('foo');
}
public function testArrayCollection()
{
$type = new Type('array', false, null, true, [new Type('int'), new Type('string')], [new Type('object', false, \ArrayObject::class, true), new Type('array', false, null, true)]);
$this->assertEquals(Type::BUILTIN_TYPE_ARRAY, $type->getBuiltinType());
$this->assertFalse($type->isNullable());
$this->assertTrue($type->isCollection());
[$firstKeyType, $secondKeyType] = $type->getCollectionKeyTypes();
$this->assertEquals(Type::BUILTIN_TYPE_INT, $firstKeyType->getBuiltinType());
$this->assertFalse($firstKeyType->isNullable());
$this->assertFalse($firstKeyType->isCollection());
$this->assertEquals(Type::BUILTIN_TYPE_STRING, $secondKeyType->getBuiltinType());
$this->assertFalse($secondKeyType->isNullable());
$this->assertFalse($secondKeyType->isCollection());
[$firstValueType, $secondValueType] = $type->getCollectionValueTypes();
$this->assertEquals(Type::BUILTIN_TYPE_OBJECT, $firstValueType->getBuiltinType());
$this->assertEquals(\ArrayObject::class, $firstValueType->getClassName());
$this->assertFalse($firstValueType->isNullable());
$this->assertTrue($firstValueType->isCollection());
$this->assertEquals(Type::BUILTIN_TYPE_ARRAY, $secondValueType->getBuiltinType());
$this->assertFalse($secondValueType->isNullable());
$this->assertTrue($secondValueType->isCollection());
}
public function testInvalidCollectionValueArgument()
{
$this->expectException(\TypeError::class);
$this->expectExceptionMessage('"Symfony\Component\PropertyInfo\Type::validateCollectionArgument()": Argument #5 ($collectionKeyType) must be of type "Symfony\Component\PropertyInfo\Type[]", "Symfony\Component\PropertyInfo\Type" or "null", array value "array" given.');
new Type('array', false, null, true, [new \stdClass()], [new Type('string')]);
}
}