Rebase, fix tests, review & update CHANGELOG
This commit is contained in:
parent
fc250863a8
commit
0a92dab753
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
5.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Linking to PropertyInfo extractor to remove a lot of duplicate code
|
||||||
|
|
||||||
4.4.0
|
4.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -76,19 +76,14 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
* Should not be used by application code. Use
|
* Should not be used by application code. Use
|
||||||
* {@link PropertyAccess::createPropertyAccessor()} instead.
|
* {@link PropertyAccess::createPropertyAccessor()} instead.
|
||||||
*/
|
*/
|
||||||
public function __construct(bool $magicCall = false, bool $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null, bool $throwExceptionOnInvalidPropertyPath = true)
|
public function __construct(bool $magicCall = false, bool $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null, bool $throwExceptionOnInvalidPropertyPath = true, PropertyReadInfoExtractorInterface $readInfoExtractor = null, PropertyWriteInfoExtractorInterface $writeInfoExtractor = null)
|
||||||
{
|
{
|
||||||
$this->magicCall = $magicCall;
|
$this->magicCall = $magicCall;
|
||||||
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
|
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
|
||||||
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
|
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
|
||||||
$this->ignoreInvalidProperty = !$throwExceptionOnInvalidPropertyPath;
|
$this->ignoreInvalidProperty = !$throwExceptionOnInvalidPropertyPath;
|
||||||
$this->readInfoExtractor = $this->writeInfoExtractor = new ReflectionExtractor(
|
$this->readInfoExtractor = $readInfoExtractor ?? new ReflectionExtractor([], null, null, false);
|
||||||
['set'],
|
$this->writeInfoExtractor = $writeInfoExtractor ?? new ReflectionExtractor(['set'], null, null, false);
|
||||||
['get', 'is', 'has', 'can'],
|
|
||||||
['add', 'remove'],
|
|
||||||
false,
|
|
||||||
ReflectionExtractor::ALLOW_PUBLIC
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -391,34 +386,25 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
$access = $this->getReadInfo($class, $property);
|
$access = $this->getReadInfo($class, $property);
|
||||||
|
|
||||||
if (null !== $access) {
|
if (null !== $access) {
|
||||||
if (PropertyReadInfo::TYPE_METHOD === $access->getType()) {
|
$name = $access->getName();
|
||||||
$result[self::VALUE] = $object->{$access->getName()}();
|
$type = $access->getType();
|
||||||
}
|
|
||||||
|
|
||||||
if (PropertyReadInfo::TYPE_PROPERTY === $access->getType()) {
|
if (PropertyReadInfo::TYPE_METHOD === $type) {
|
||||||
$result[self::VALUE] = $object->{$access->getName()};
|
$result[self::VALUE] = $object->$name();
|
||||||
|
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
|
||||||
|
$result[self::VALUE] = $object->$name;
|
||||||
|
|
||||||
if (isset($zval[self::REF]) && $access->canBeReference()) {
|
if (isset($zval[self::REF]) && $access->canBeReference()) {
|
||||||
$result[self::REF] = &$object->{$access->getName()};
|
$result[self::REF] = &$object->$name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
|
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
|
||||||
// Needed to support \stdClass instances. We need to explicitly
|
|
||||||
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
|
|
||||||
// a *protected* property was found on the class, property_exists()
|
|
||||||
// returns true, consequently the following line will result in a
|
|
||||||
// fatal error.
|
|
||||||
|
|
||||||
$result[self::VALUE] = $object->$property;
|
$result[self::VALUE] = $object->$property;
|
||||||
if (isset($zval[self::REF])) {
|
if (isset($zval[self::REF])) {
|
||||||
$result[self::REF] = &$object->$property;
|
$result[self::REF] = &$object->$property;
|
||||||
}
|
}
|
||||||
} elseif (!$ignoreInvalidProperty) {
|
} elseif (!$ignoreInvalidProperty) {
|
||||||
throw new NoSuchPropertyException(sprintf(
|
throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class));
|
||||||
'Can get a way to read the property "%s" in class "%s".',
|
|
||||||
$property,
|
|
||||||
$class
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects are always passed around by reference
|
// Objects are always passed around by reference
|
||||||
@ -494,39 +480,29 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
$class = \get_class($object);
|
$class = \get_class($object);
|
||||||
$mutator = $this->getWriteInfo($class, $property, $value);
|
$mutator = $this->getWriteInfo($class, $property, $value);
|
||||||
|
|
||||||
if (null !== $mutator) {
|
if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
|
||||||
if (PropertyWriteInfo::TYPE_METHOD === $mutator->getType()) {
|
$type = $mutator->getType();
|
||||||
|
|
||||||
|
if (PropertyWriteInfo::TYPE_METHOD === $type) {
|
||||||
$object->{$mutator->getName()}($value);
|
$object->{$mutator->getName()}($value);
|
||||||
}
|
} elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
|
||||||
|
|
||||||
if (PropertyWriteInfo::TYPE_PROPERTY === $mutator->getType()) {
|
|
||||||
$object->{$mutator->getName()} = $value;
|
$object->{$mutator->getName()} = $value;
|
||||||
}
|
} elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
|
||||||
|
|
||||||
if (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $mutator->getType()) {
|
|
||||||
$this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo());
|
$this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo());
|
||||||
}
|
}
|
||||||
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
|
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
|
||||||
// Needed to support \stdClass instances. We need to explicitly
|
|
||||||
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
|
|
||||||
// a *protected* property was found on the class, property_exists()
|
|
||||||
// returns true, consequently the following line will result in a
|
|
||||||
// fatal error.
|
|
||||||
|
|
||||||
$object->$property = $value;
|
$object->$property = $value;
|
||||||
} else {
|
} elseif (!$this->ignoreInvalidProperty) {
|
||||||
|
if ($mutator->hasErrors()) {
|
||||||
|
throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.');
|
||||||
|
}
|
||||||
|
|
||||||
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, \get_class($object)));
|
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, \get_class($object)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts a collection-valued property by calling add*() and remove*() methods.
|
* Adjusts a collection-valued property by calling add*() and remove*() methods.
|
||||||
*
|
|
||||||
* @param array $zval The array containing the object to write to
|
|
||||||
* @param string $property The property to write
|
|
||||||
* @param iterable $collection The collection to write
|
|
||||||
* @param PropertyWriteInfo $addMethod The add*() method
|
|
||||||
* @param PropertyWriteInfo $removeMethod The remove*() method
|
|
||||||
*/
|
*/
|
||||||
private function writeCollection(array $zval, string $property, iterable $collection, PropertyWriteInfo $addMethod, PropertyWriteInfo $removeMethod)
|
private function writeCollection(array $zval, string $property, iterable $collection, PropertyWriteInfo $addMethod, PropertyWriteInfo $removeMethod)
|
||||||
{
|
{
|
||||||
@ -534,6 +510,9 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
$previousValue = $this->readProperty($zval, $property);
|
$previousValue = $this->readProperty($zval, $property);
|
||||||
$previousValue = $previousValue[self::VALUE];
|
$previousValue = $previousValue[self::VALUE];
|
||||||
|
|
||||||
|
$removeMethodName = $removeMethod->getName();
|
||||||
|
$addMethodName = $addMethod->getName();
|
||||||
|
|
||||||
if ($previousValue instanceof \Traversable) {
|
if ($previousValue instanceof \Traversable) {
|
||||||
$previousValue = iterator_to_array($previousValue);
|
$previousValue = iterator_to_array($previousValue);
|
||||||
}
|
}
|
||||||
@ -544,7 +523,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
foreach ($previousValue as $key => $item) {
|
foreach ($previousValue as $key => $item) {
|
||||||
if (!\in_array($item, $collection, true)) {
|
if (!\in_array($item, $collection, true)) {
|
||||||
unset($previousValue[$key]);
|
unset($previousValue[$key]);
|
||||||
$zval[self::VALUE]->{$removeMethod->getName()}($item);
|
$zval[self::VALUE]->$removeMethodName($item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -553,12 +532,12 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
|
|
||||||
foreach ($collection as $item) {
|
foreach ($collection as $item) {
|
||||||
if (!$previousValue || !\in_array($item, $previousValue, true)) {
|
if (!$previousValue || !\in_array($item, $previousValue, true)) {
|
||||||
$zval[self::VALUE]->{$addMethod->getName()}($item);
|
$zval[self::VALUE]->$addMethodName($item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getWriteInfo(string $class, string $property, $value): ?PropertyWriteInfo
|
private function getWriteInfo(string $class, string $property, $value): PropertyWriteInfo
|
||||||
{
|
{
|
||||||
$useAdderAndRemover = \is_array($value) || $value instanceof \Traversable;
|
$useAdderAndRemover = \is_array($value) || $value instanceof \Traversable;
|
||||||
$key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover;
|
$key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover;
|
||||||
@ -601,13 +580,13 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
|
|
||||||
$mutatorForArray = $this->getWriteInfo(\get_class($object), $property, []);
|
$mutatorForArray = $this->getWriteInfo(\get_class($object), $property, []);
|
||||||
|
|
||||||
if (null !== $mutatorForArray || ($object instanceof \stdClass && property_exists($object, $property))) {
|
if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || ($object instanceof \stdClass && property_exists($object, $property))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mutator = $this->getWriteInfo(\get_class($object), $property, '');
|
$mutator = $this->getWriteInfo(\get_class($object), $property, '');
|
||||||
|
|
||||||
return null !== $mutator || ($object instanceof \stdClass && property_exists($object, $property));
|
return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || ($object instanceof \stdClass && property_exists($object, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,7 +188,7 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces
|
|||||||
public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable()
|
public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
|
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
|
||||||
$this->expectExceptionMessageRegExp('/Could not determine access type for property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*": The property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\\Traversable, "string" given./');
|
$this->expectExceptionMessageRegExp('/The property "axes" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\PropertyAccessorCollectionTest_Car" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\Traversable\./');
|
||||||
$car = new PropertyAccessorCollectionTest_Car();
|
$car = new PropertyAccessorCollectionTest_Car();
|
||||||
|
|
||||||
$this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable');
|
$this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable');
|
||||||
|
@ -760,7 +760,7 @@ class PropertyAccessorTest extends TestCase
|
|||||||
public function testAdderAndRemoveNeedsTheExactParametersDefined()
|
public function testAdderAndRemoveNeedsTheExactParametersDefined()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
|
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
|
||||||
$this->expectExceptionMessageRegExp('/.*The method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\. The method "removeFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./');
|
$this->expectExceptionMessageRegExp('/.*The method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\./');
|
||||||
$object = new TestAdderRemoverInvalidArgumentLength();
|
$object = new TestAdderRemoverInvalidArgumentLength();
|
||||||
$this->propertyAccessor->setValue($object, 'foo', [1, 2]);
|
$this->propertyAccessor->setValue($object, 'foo', [1, 2]);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.5",
|
"php": "^7.2.5",
|
||||||
"symfony/inflector": "^4.4|^5.0",
|
"symfony/inflector": "^4.4|^5.0",
|
||||||
"symfony/property-info": "^4.4|^5.0"
|
"symfony/property-info": "^5.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/cache": "^4.4|^5.0"
|
"symfony/cache": "^4.4|^5.0"
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
5.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Add support for extracting accessor and mutator via PHP Reflection
|
||||||
|
|
||||||
4.3.0
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -233,28 +233,28 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
if ($reflClass->hasMethod($methodName) && ($reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags)) {
|
if ($reflClass->hasMethod($methodName) && ($reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags)) {
|
||||||
$method = $reflClass->getMethod($methodName);
|
$method = $reflClass->getMethod($methodName);
|
||||||
|
|
||||||
return PropertyReadInfo::forMethod($methodName, $this->getReadVisiblityForMethod($method), $method->isStatic());
|
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) {
|
if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) {
|
||||||
$method = $reflClass->getMethod($getsetter);
|
$method = $reflClass->getMethod($getsetter);
|
||||||
|
|
||||||
return PropertyReadInfo::forMethod($getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic());
|
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
|
if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
|
||||||
$reflProperty = $reflClass->getProperty($property);
|
$reflProperty = $reflClass->getProperty($property);
|
||||||
|
|
||||||
return PropertyReadInfo::forProperty($property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
|
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
|
if ($reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
|
||||||
return PropertyReadInfo::forProperty($property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
|
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
|
if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
|
||||||
return PropertyReadInfo::forMethod('get'.$camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, false);
|
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get'.$camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -263,7 +263,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo
|
public function getWriteInfo(string $class, string $property, array $context = []): PropertyWriteInfo
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$reflClass = new \ReflectionClass($class);
|
$reflClass = new \ReflectionClass($class);
|
||||||
@ -278,64 +278,92 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
|
|
||||||
$camelized = $this->camelize($property);
|
$camelized = $this->camelize($property);
|
||||||
$constructor = $reflClass->getConstructor();
|
$constructor = $reflClass->getConstructor();
|
||||||
|
$singulars = (array) Inflector::singularize($camelized);
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
if (null !== $constructor && $allowConstruct) {
|
if (null !== $constructor && $allowConstruct) {
|
||||||
foreach ($constructor->getParameters() as $parameter) {
|
foreach ($constructor->getParameters() as $parameter) {
|
||||||
if ($parameter->getName() === $property) {
|
if ($parameter->getName() === $property) {
|
||||||
return PropertyWriteInfo::forConstructor($property);
|
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allowAdderRemover && null !== $methods = $this->findAdderAndRemover($reflClass, (array) Inflector::singularize($camelized))) {
|
[$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars);
|
||||||
[$adderAccessName, $removerAccessName] = $methods;
|
if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
|
||||||
|
|
||||||
$adderMethod = $reflClass->getMethod($adderAccessName);
|
$adderMethod = $reflClass->getMethod($adderAccessName);
|
||||||
$removerMethod = $reflClass->getMethod($removerAccessName);
|
$removerMethod = $reflClass->getMethod($removerAccessName);
|
||||||
|
|
||||||
return PropertyWriteInfo::forAdderAndRemover(
|
$mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
|
||||||
PropertyWriteInfo::forMethod($adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()),
|
$mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()));
|
||||||
PropertyWriteInfo::forMethod($removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic())
|
$mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic()));
|
||||||
);
|
|
||||||
|
return $mutator;
|
||||||
|
} else {
|
||||||
|
$errors = array_merge($errors, $adderAndRemoverErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->mutatorPrefixes as $mutatorPrefix) {
|
foreach ($this->mutatorPrefixes as $mutatorPrefix) {
|
||||||
$methodName = $mutatorPrefix.$camelized;
|
$methodName = $mutatorPrefix.$camelized;
|
||||||
|
|
||||||
if (!$this->isMethodAccessible($reflClass, $methodName, 1)) {
|
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1);
|
||||||
|
if (!$accessible) {
|
||||||
|
$errors = array_merge($errors, $methodAccessibleErrors);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$method = $reflClass->getMethod($methodName);
|
$method = $reflClass->getMethod($methodName);
|
||||||
|
|
||||||
if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) {
|
if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) {
|
||||||
return PropertyWriteInfo::forMethod($methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic());
|
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$getsetter = lcfirst($camelized);
|
$getsetter = lcfirst($camelized);
|
||||||
|
|
||||||
if ($allowGetterSetter && $this->isMethodAccessible($reflClass, $getsetter, 1)) {
|
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1);
|
||||||
|
if ($allowGetterSetter && $accessible) {
|
||||||
$method = $reflClass->getMethod($getsetter);
|
$method = $reflClass->getMethod($getsetter);
|
||||||
|
|
||||||
return PropertyWriteInfo::forMethod($getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic());
|
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic());
|
||||||
|
} else {
|
||||||
|
$errors = array_merge($errors, $methodAccessibleErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
|
if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
|
||||||
$reflProperty = $reflClass->getProperty($property);
|
$reflProperty = $reflClass->getProperty($property);
|
||||||
|
|
||||||
return PropertyWriteInfo::forProperty($property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
|
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isMethodAccessible($reflClass, '__set', 2)) {
|
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
|
||||||
return PropertyWriteInfo::forProperty($property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
|
if ($accessible) {
|
||||||
|
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
|
||||||
|
} else {
|
||||||
|
$errors = array_merge($errors, $methodAccessibleErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allowMagicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
|
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2);
|
||||||
return PropertyWriteInfo::forMethod('set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
|
if ($allowMagicCall && $accessible) {
|
||||||
|
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
|
||||||
|
} else {
|
||||||
|
$errors = array_merge($errors, $methodAccessibleErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
|
||||||
|
$errors = array_merge($errors, [sprintf(
|
||||||
|
'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
|
||||||
|
'the new value must be an array or an instance of \Traversable',
|
||||||
|
$property,
|
||||||
|
$reflClass->getName(),
|
||||||
|
implode('()", "', [$adderAccessName, $removerAccessName])
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$noneProperty = new PropertyWriteInfo();
|
||||||
|
$noneProperty->setErrors($errors);
|
||||||
|
|
||||||
|
return $noneProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -575,45 +603,57 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
* @param \ReflectionClass $reflClass The reflection class for the given object
|
* @param \ReflectionClass $reflClass The reflection class for the given object
|
||||||
* @param array $singulars The singular form of the property name or null
|
* @param array $singulars The singular form of the property name or null
|
||||||
*
|
*
|
||||||
* @return array|null An array containing the adder and remover when found, null otherwise
|
* @return array An array containing the adder and remover when found and errors
|
||||||
*/
|
*/
|
||||||
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars)
|
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
|
||||||
{
|
{
|
||||||
if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) {
|
if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes;
|
[$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes;
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
foreach ($singulars as $singular) {
|
foreach ($singulars as $singular) {
|
||||||
$addMethod = $addPrefix.$singular;
|
$addMethod = $addPrefix.$singular;
|
||||||
$removeMethod = $removePrefix.$singular;
|
$removeMethod = $removePrefix.$singular;
|
||||||
|
|
||||||
$addMethodFound = $this->isMethodAccessible($reflClass, $addMethod, 1);
|
[$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1);
|
||||||
$removeMethodFound = $this->isMethodAccessible($reflClass, $removeMethod, 1);
|
[$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1);
|
||||||
|
$errors = array_merge($errors, $addMethodAccessibleErrors, $removeMethodAccessibleErrors);
|
||||||
|
|
||||||
if ($addMethodFound && $removeMethodFound) {
|
if ($addMethodFound && $removeMethodFound) {
|
||||||
return [$addMethod, $removeMethod];
|
return [$addMethod, $removeMethod, []];
|
||||||
|
} elseif ($addMethodFound && !$removeMethodFound) {
|
||||||
|
$errors[] = sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod);
|
||||||
|
} elseif (!$addMethodFound && $removeMethodFound) {
|
||||||
|
$errors[] = sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [null, null, $errors];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a method is public and has the number of required parameters.
|
* Returns whether a method is public and has the number of required parameters and errors.
|
||||||
*/
|
*/
|
||||||
private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): bool
|
private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): array
|
||||||
{
|
{
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
if ($class->hasMethod($methodName)) {
|
if ($class->hasMethod($methodName)) {
|
||||||
$method = $class->getMethod($methodName);
|
$method = $class->getMethod($methodName);
|
||||||
|
|
||||||
if (($method->getModifiers() & $this->methodReflectionFlags)
|
if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
|
||||||
&& $method->getNumberOfRequiredParameters() <= $parameters
|
$errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName());
|
||||||
&& $method->getNumberOfParameters() >= $parameters) {
|
} elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
|
||||||
return true;
|
$errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
|
||||||
|
} else {
|
||||||
|
return [true, $errors];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return [false, $errors];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,8 +37,13 @@ final class PropertyReadInfo
|
|||||||
|
|
||||||
private $byRef;
|
private $byRef;
|
||||||
|
|
||||||
private function __construct()
|
public function __construct(string $type, string $name, string $visibility, bool $static, bool $byRef)
|
||||||
{
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->visibility = $visibility;
|
||||||
|
$this->static = $static;
|
||||||
|
$this->byRef = $byRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,28 +79,4 @@ final class PropertyReadInfo
|
|||||||
{
|
{
|
||||||
return $this->byRef;
|
return $this->byRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function forProperty(string $propertyName, string $visibility, bool $static, bool $byRef): self
|
|
||||||
{
|
|
||||||
$accessor = new self();
|
|
||||||
$accessor->type = self::TYPE_PROPERTY;
|
|
||||||
$accessor->name = $propertyName;
|
|
||||||
$accessor->visibility = $visibility;
|
|
||||||
$accessor->static = $static;
|
|
||||||
$accessor->byRef = $byRef;
|
|
||||||
|
|
||||||
return $accessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function forMethod(string $methodName, string $visibility, bool $static): self
|
|
||||||
{
|
|
||||||
$accessor = new self();
|
|
||||||
$accessor->type = self::TYPE_METHOD;
|
|
||||||
$accessor->name = $methodName;
|
|
||||||
$accessor->visibility = $visibility;
|
|
||||||
$accessor->static = $static;
|
|
||||||
$accessor->byRef = false;
|
|
||||||
|
|
||||||
return $accessor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,11 @@ namespace Symfony\Component\PropertyInfo;
|
|||||||
* Extract read information for the property of a class.
|
* Extract read information for the property of a class.
|
||||||
*
|
*
|
||||||
* @author Joel Wurtz <jwurtz@jolicode.com>
|
* @author Joel Wurtz <jwurtz@jolicode.com>
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
interface PropertyReadInfoExtractorInterface
|
interface PropertyReadInfoExtractorInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get read information object for a given property of a class.
|
* Get read information object for a given property of a class.
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo;
|
public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ namespace Symfony\Component\PropertyInfo;
|
|||||||
*/
|
*/
|
||||||
final class PropertyWriteInfo
|
final class PropertyWriteInfo
|
||||||
{
|
{
|
||||||
|
public const TYPE_NONE = 'none';
|
||||||
public const TYPE_METHOD = 'method';
|
public const TYPE_METHOD = 'method';
|
||||||
public const TYPE_PROPERTY = 'property';
|
public const TYPE_PROPERTY = 'property';
|
||||||
public const TYPE_ADDER_AND_REMOVER = 'adder_and_remover';
|
public const TYPE_ADDER_AND_REMOVER = 'adder_and_remover';
|
||||||
@ -35,9 +36,14 @@ final class PropertyWriteInfo
|
|||||||
private $static;
|
private $static;
|
||||||
private $adderInfo;
|
private $adderInfo;
|
||||||
private $removerInfo;
|
private $removerInfo;
|
||||||
|
private $errors = [];
|
||||||
|
|
||||||
private function __construct()
|
public function __construct(string $type = self::TYPE_NONE, string $name = null, string $visibility = null, bool $static = null)
|
||||||
{
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->visibility = $visibility;
|
||||||
|
$this->static = $static;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getType(): string
|
public function getType(): string
|
||||||
@ -54,6 +60,11 @@ final class PropertyWriteInfo
|
|||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setAdderInfo(self $adderInfo): void
|
||||||
|
{
|
||||||
|
$this->adderInfo = $adderInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAdderInfo(): self
|
public function getAdderInfo(): self
|
||||||
{
|
{
|
||||||
if (null === $this->adderInfo) {
|
if (null === $this->adderInfo) {
|
||||||
@ -63,6 +74,11 @@ final class PropertyWriteInfo
|
|||||||
return $this->adderInfo;
|
return $this->adderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRemoverInfo(self $removerInfo): void
|
||||||
|
{
|
||||||
|
$this->removerInfo = $removerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public function getRemoverInfo(): self
|
public function getRemoverInfo(): self
|
||||||
{
|
{
|
||||||
if (null === $this->removerInfo) {
|
if (null === $this->removerInfo) {
|
||||||
@ -90,44 +106,18 @@ final class PropertyWriteInfo
|
|||||||
return $this->static;
|
return $this->static;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function forMethod(string $methodName, string $visibility, bool $static): self
|
public function setErrors(array $errors): void
|
||||||
{
|
{
|
||||||
$mutator = new self();
|
$this->errors = $errors;
|
||||||
$mutator->type = self::TYPE_METHOD;
|
|
||||||
$mutator->name = $methodName;
|
|
||||||
$mutator->visibility = $visibility;
|
|
||||||
$mutator->static = $static;
|
|
||||||
|
|
||||||
return $mutator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function forProperty(string $propertyName, string $visibility, bool $static): self
|
public function getErrors(): array
|
||||||
{
|
{
|
||||||
$mutator = new self();
|
return $this->errors;
|
||||||
$mutator->type = self::TYPE_PROPERTY;
|
|
||||||
$mutator->name = $propertyName;
|
|
||||||
$mutator->visibility = $visibility;
|
|
||||||
$mutator->static = $static;
|
|
||||||
|
|
||||||
return $mutator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function forAdderAndRemover(self $adder, self $remover): self
|
public function hasErrors(): bool
|
||||||
{
|
{
|
||||||
$mutator = new self();
|
return (bool) \count($this->errors);
|
||||||
$mutator->type = self::TYPE_ADDER_AND_REMOVER;
|
|
||||||
$mutator->adderInfo = $adder;
|
|
||||||
$mutator->removerInfo = $remover;
|
|
||||||
|
|
||||||
return $mutator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function forConstructor(string $propertyName): self
|
|
||||||
{
|
|
||||||
$mutator = new self();
|
|
||||||
$mutator->type = self::TYPE_CONSTRUCTOR;
|
|
||||||
$mutator->name = $propertyName;
|
|
||||||
|
|
||||||
return $mutator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,11 @@ namespace Symfony\Component\PropertyInfo;
|
|||||||
* Extract write information for the property of a class.
|
* Extract write information for the property of a class.
|
||||||
*
|
*
|
||||||
* @author Joel Wurtz <jwurtz@jolicode.com>
|
* @author Joel Wurtz <jwurtz@jolicode.com>
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
interface PropertyWriteInfoExtractorInterface
|
interface PropertyWriteInfoExtractorInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get write information object for a given property of a class.
|
* Get write information object for a given property of a class.
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo;
|
public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo;
|
||||||
}
|
}
|
||||||
|
@ -378,9 +378,9 @@ class ReflectionExtractorTest extends TestCase
|
|||||||
$bazMutator = $this->extractor->getWriteInfo(Dummy::class, 'baz');
|
$bazMutator = $this->extractor->getWriteInfo(Dummy::class, 'baz');
|
||||||
|
|
||||||
$this->assertNull($barAcessor);
|
$this->assertNull($barAcessor);
|
||||||
$this->assertNull($barMutator);
|
$this->assertEquals(PropertyWriteInfo::TYPE_NONE, $barMutator->getType());
|
||||||
$this->assertNull($bazAcessor);
|
$this->assertNull($bazAcessor);
|
||||||
$this->assertNull($bazMutator);
|
$this->assertEquals(PropertyWriteInfo::TYPE_NONE, $bazMutator->getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -439,7 +439,7 @@ class ReflectionExtractorTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
$this->assertNull($writeMutator);
|
$this->assertEquals(PropertyWriteInfo::TYPE_NONE, $writeMutator->getType());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user