Files
Sentri/vendor/symfony/property-info/Tests/Extractor/ReflectionExtractorTest.php
2026-01-01 10:54:18 +01:00

794 lines
35 KiB
PHP

<?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];
}
}