feature #10570 [PropertyAccess] Added isReadable() and isWritable() (webmozart)
This PR was merged into the 2.5-dev branch. Discussion ---------- [PropertyAccess] Added isReadable() and isWritable() | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | yes | Deprecations? | no | Tests pass? | yes | Fixed tickets | #8659 | License | MIT | Doc PR | symfony/symfony-docs#3729 This PR introduces BC breaks that are described in detail in the UPGRADE file. The BC breaks conform to our policy. They shouldn't affect many people, so I think we can safely do them. Commits -------f7fb855
[PropertyAccess] Added missing exceptions to phpdoc9aee2ad
[PropertyAccess] Removed the argument $value from isWritable()4262707
[PropertyAccess] Fixed CS and added missing documentation6d2af21
[PropertyAccess] Added isReadable() and isWritable()20e6bf8
[PropertyAccess] Refactored PropertyAccessorCollectionTest0488389
[PropertyAccess] Refactored PropertyAccessorTest
This commit is contained in:
commit
c5a3008123
@ -45,6 +45,47 @@ Form
|
|||||||
{
|
{
|
||||||
```
|
```
|
||||||
|
|
||||||
|
PropertyAccess
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* The methods `isReadable()` and `isWritable()` were added to
|
||||||
|
`PropertyAccessorInterface`. If you implemented this interface in your own
|
||||||
|
code, you should add these two methods.
|
||||||
|
|
||||||
|
* The methods `getValue()` and `setValue()` now throw an
|
||||||
|
`NoSuchIndexException` instead of a `NoSuchPropertyException` when an index
|
||||||
|
is accessed on an object that does not implement `ArrayAccess`. If you catch
|
||||||
|
this exception in your code, you should adapt the catch statement:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$object = new \stdClass();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$propertyAccessor->getValue($object, '[index]');
|
||||||
|
$propertyAccessor->setValue($object, '[index]', 'New value');
|
||||||
|
} catch (NoSuchPropertyException $e) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$object = new \stdClass();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$propertyAccessor->getValue($object, '[index]');
|
||||||
|
$propertyAccessor->setValue($object, '[index]', 'New value');
|
||||||
|
} catch (NoSuchIndexException $e) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A `NoSuchPropertyException` is still thrown when a non-existing property is
|
||||||
|
accessed on an object or an array.
|
||||||
|
|
||||||
Validator
|
Validator
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@ CHANGELOG
|
|||||||
------
|
------
|
||||||
|
|
||||||
* allowed non alpha numeric characters in second level and deeper object properties names
|
* allowed non alpha numeric characters in second level and deeper object properties names
|
||||||
|
* [BC BREAK] when accessing an index on an object that does not implement
|
||||||
|
ArrayAccess, a NoSuchIndexException is now thrown instead of the
|
||||||
|
semantically wrong NoSuchPropertyException
|
||||||
|
* [BC BREAK] added isReadable() and isWritable() to PropertyAccessorInterface
|
||||||
|
|
||||||
2.3.0
|
2.3.0
|
||||||
------
|
------
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<?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\PropertyAccess\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base InvalidArgumentException for the PropertyAccess component.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\PropertyAccess;
|
namespace Symfony\Component\PropertyAccess;
|
||||||
|
|
||||||
|
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
||||||
@ -33,7 +34,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
/**
|
/**
|
||||||
* @var Boolean
|
* @var Boolean
|
||||||
*/
|
*/
|
||||||
private $throwExceptionOnInvalidIndex;
|
private $ignoreInvalidIndices;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should not be used by application code. Use
|
* Should not be used by application code. Use
|
||||||
@ -42,7 +43,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false)
|
public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false)
|
||||||
{
|
{
|
||||||
$this->magicCall = $magicCall;
|
$this->magicCall = $magicCall;
|
||||||
$this->throwExceptionOnInvalidIndex = $throwExceptionOnInvalidIndex;
|
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,10 +54,15 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
if (is_string($propertyPath)) {
|
if (is_string($propertyPath)) {
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
$propertyPath = new PropertyPath($propertyPath);
|
||||||
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
||||||
throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPathInterface');
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'The property path should be a string or an instance of '.
|
||||||
|
'"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
|
||||||
|
'Got: "%s"',
|
||||||
|
is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->throwExceptionOnInvalidIndex);
|
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
||||||
|
|
||||||
return $propertyValues[count($propertyValues) - 1][self::VALUE];
|
return $propertyValues[count($propertyValues) - 1][self::VALUE];
|
||||||
}
|
}
|
||||||
@ -69,7 +75,12 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
if (is_string($propertyPath)) {
|
if (is_string($propertyPath)) {
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
$propertyPath = new PropertyPath($propertyPath);
|
||||||
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
||||||
throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPathInterface');
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'The property path should be a string or an instance of '.
|
||||||
|
'"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
|
||||||
|
'Got: "%s"',
|
||||||
|
is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
||||||
@ -90,13 +101,11 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$property = $propertyPath->getElement($i);
|
$property = $propertyPath->getElement($i);
|
||||||
//$singular = $propertyPath->singulars[$i];
|
|
||||||
$singular = null;
|
|
||||||
|
|
||||||
if ($propertyPath->isIndex($i)) {
|
if ($propertyPath->isIndex($i)) {
|
||||||
$this->writeIndex($objectOrArray, $property, $value);
|
$this->writeIndex($objectOrArray, $property, $value);
|
||||||
} else {
|
} else {
|
||||||
$this->writeProperty($objectOrArray, $property, $singular, $value);
|
$this->writeProperty($objectOrArray, $property, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,18 +114,108 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isReadable($objectOrArray, $propertyPath)
|
||||||
|
{
|
||||||
|
if (is_string($propertyPath)) {
|
||||||
|
$propertyPath = new PropertyPath($propertyPath);
|
||||||
|
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'The property path should be a string or an instance of '.
|
||||||
|
'"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
|
||||||
|
'Got: "%s"',
|
||||||
|
is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchIndexException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (NoSuchPropertyException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (UnexpectedTypeException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isWritable($objectOrArray, $propertyPath)
|
||||||
|
{
|
||||||
|
if (is_string($propertyPath)) {
|
||||||
|
$propertyPath = new PropertyPath($propertyPath);
|
||||||
|
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'The property path should be a string or an instance of '.
|
||||||
|
'"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
|
||||||
|
'Got: "%s"',
|
||||||
|
is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$propertyValues = $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
||||||
|
$overwrite = true;
|
||||||
|
|
||||||
|
// Add the root object to the list
|
||||||
|
array_unshift($propertyValues, array(
|
||||||
|
self::VALUE => $objectOrArray,
|
||||||
|
self::IS_REF => true,
|
||||||
|
));
|
||||||
|
|
||||||
|
for ($i = count($propertyValues) - 1; $i >= 0; --$i) {
|
||||||
|
$objectOrArray = $propertyValues[$i][self::VALUE];
|
||||||
|
|
||||||
|
if ($overwrite) {
|
||||||
|
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$property = $propertyPath->getElement($i);
|
||||||
|
|
||||||
|
if ($propertyPath->isIndex($i)) {
|
||||||
|
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!$this->isPropertyWritable($objectOrArray, $property)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$overwrite = !$propertyValues[$i][self::IS_REF];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchIndexException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (NoSuchPropertyException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the path from an object up to a given path index.
|
* Reads the path from an object up to a given path index.
|
||||||
*
|
*
|
||||||
* @param object|array $objectOrArray The object or array to read from
|
* @param object|array $objectOrArray The object or array to read from
|
||||||
* @param PropertyPathInterface $propertyPath The property path to read
|
* @param PropertyPathInterface $propertyPath The property path to read
|
||||||
* @param integer $lastIndex The index up to which should be read
|
* @param integer $lastIndex The index up to which should be read
|
||||||
|
* @param Boolean $ignoreInvalidIndices Whether to ignore invalid indices
|
||||||
|
* or throw an exception
|
||||||
*
|
*
|
||||||
* @return array The values read in the path.
|
* @return array The values read in the path.
|
||||||
*
|
*
|
||||||
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
||||||
|
* @throws NoSuchIndexException If a non-existing index is accessed
|
||||||
*/
|
*/
|
||||||
private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $throwExceptionOnNonexistantIndex = false)
|
private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true)
|
||||||
{
|
{
|
||||||
$propertyValues = array();
|
$propertyValues = array();
|
||||||
|
|
||||||
@ -131,9 +230,10 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
|
|
||||||
// Create missing nested arrays on demand
|
// Create missing nested arrays on demand
|
||||||
if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) {
|
if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) {
|
||||||
if ($throwExceptionOnNonexistantIndex) {
|
if (!$ignoreInvalidIndices) {
|
||||||
throw new NoSuchIndexException(sprintf('Cannot read property "%s". Available properties are "%s"', $property, print_r(array_keys($objectOrArray), true)));
|
throw new NoSuchIndexException(sprintf('Cannot read property "%s". Available properties are "%s"', $property, print_r(array_keys($objectOrArray), true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null;
|
$objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,12 +259,12 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*
|
*
|
||||||
* @return mixed The value of the key
|
* @return mixed The value of the key
|
||||||
*
|
*
|
||||||
* @throws NoSuchPropertyException If the array does not implement \ArrayAccess or it is not an array
|
* @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
|
||||||
*/
|
*/
|
||||||
private function &readIndex(&$array, $index)
|
private function &readIndex(&$array, $index)
|
||||||
{
|
{
|
||||||
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
||||||
throw new NoSuchPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($array)));
|
throw new NoSuchIndexException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($array)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use an array instead of an object since performance is very crucial here
|
// Use an array instead of an object since performance is very crucial here
|
||||||
@ -264,101 +364,60 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of the property at the given index in the path
|
* Sets the value of an index in a given array-accessible value.
|
||||||
*
|
*
|
||||||
* @param \ArrayAccess|array $array An array or \ArrayAccess object to write to
|
* @param \ArrayAccess|array $array An array or \ArrayAccess object to write to
|
||||||
* @param string|integer $index The index to write at
|
* @param string|integer $index The index to write at
|
||||||
* @param mixed $value The value to write
|
* @param mixed $value The value to write
|
||||||
*
|
*
|
||||||
* @throws NoSuchPropertyException If the array does not implement \ArrayAccess or it is not an array
|
* @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
|
||||||
*/
|
*/
|
||||||
private function writeIndex(&$array, $index, $value)
|
private function writeIndex(&$array, $index, $value)
|
||||||
{
|
{
|
||||||
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
||||||
throw new NoSuchPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($array)));
|
throw new NoSuchIndexException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($array)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$array[$index] = $value;
|
$array[$index] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of the property at the given index in the path
|
* Sets the value of a property in the given object
|
||||||
*
|
*
|
||||||
* @param object|array $object The object or array to write to
|
* @param object $object The object to write to
|
||||||
* @param string $property The property to write
|
* @param string $property The property to write
|
||||||
* @param string|null $singular The singular form of the property name or null
|
* @param mixed $value The value to write
|
||||||
* @param mixed $value The value to write
|
|
||||||
*
|
*
|
||||||
* @throws NoSuchPropertyException If the property does not exist or is not
|
* @throws NoSuchPropertyException If the property does not exist or is not
|
||||||
* public.
|
* public.
|
||||||
*/
|
*/
|
||||||
private function writeProperty(&$object, $property, $singular, $value)
|
private function writeProperty(&$object, $property, $value)
|
||||||
{
|
{
|
||||||
$guessedAdders = '';
|
|
||||||
|
|
||||||
if (!is_object($object)) {
|
if (!is_object($object)) {
|
||||||
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
$reflClass = new \ReflectionClass($object);
|
$reflClass = new \ReflectionClass($object);
|
||||||
$plural = $this->camelize($property);
|
$plural = $this->camelize($property);
|
||||||
|
$singulars = (array) StringUtil::singularify($plural);
|
||||||
// Any of the two methods is required, but not yet known
|
|
||||||
$singulars = null !== $singular ? array($singular) : (array) StringUtil::singularify($plural);
|
|
||||||
|
|
||||||
if (is_array($value) || $value instanceof \Traversable) {
|
if (is_array($value) || $value instanceof \Traversable) {
|
||||||
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
||||||
|
|
||||||
|
// Use addXxx() and removeXxx() to write the collection
|
||||||
if (null !== $methods) {
|
if (null !== $methods) {
|
||||||
// At this point the add and remove methods have been found
|
$this->writeCollection($object, $property, $value, $methods[0], $methods[1]);
|
||||||
// Use iterator_to_array() instead of clone in order to prevent side effects
|
|
||||||
// see https://github.com/symfony/symfony/issues/4670
|
|
||||||
$itemsToAdd = is_object($value) ? iterator_to_array($value) : $value;
|
|
||||||
$itemToRemove = array();
|
|
||||||
$propertyValue = $this->readProperty($object, $property);
|
|
||||||
$previousValue = $propertyValue[self::VALUE];
|
|
||||||
|
|
||||||
if (is_array($previousValue) || $previousValue instanceof \Traversable) {
|
|
||||||
foreach ($previousValue as $previousItem) {
|
|
||||||
foreach ($value as $key => $item) {
|
|
||||||
if ($item === $previousItem) {
|
|
||||||
// Item found, don't add
|
|
||||||
unset($itemsToAdd[$key]);
|
|
||||||
|
|
||||||
// Next $previousItem
|
|
||||||
continue 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Item not found, add to remove list
|
|
||||||
$itemToRemove[] = $previousItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($itemToRemove as $item) {
|
|
||||||
call_user_func(array($object, $methods[1]), $item);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($itemsToAdd as $item) {
|
|
||||||
call_user_func(array($object, $methods[0]), $item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
// It is sufficient to include only the adders in the error
|
|
||||||
// message. If the user implements the adder but not the remover,
|
|
||||||
// an exception will be thrown in findAdderAndRemover() that
|
|
||||||
// the remover has to be implemented as well.
|
|
||||||
$guessedAdders = '"add'.implode('()", "add', $singulars).'()", ';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$setter = 'set'.$this->camelize($property);
|
$setter = 'set'.$this->camelize($property);
|
||||||
$classHasProperty = $reflClass->hasProperty($property);
|
$classHasProperty = $reflClass->hasProperty($property);
|
||||||
|
|
||||||
if ($reflClass->hasMethod($setter) && $reflClass->getMethod($setter)->isPublic()) {
|
if ($this->isMethodAccessible($reflClass, $setter, 1)) {
|
||||||
$object->$setter($value);
|
$object->$setter($value);
|
||||||
} elseif ($reflClass->hasMethod('__set') && $reflClass->getMethod('__set')->isPublic()) {
|
} elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
|
||||||
$object->$property = $value;
|
$object->$property = $value;
|
||||||
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
|
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
|
||||||
$object->$property = $value;
|
$object->$property = $value;
|
||||||
@ -369,7 +428,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
// returns true, consequently the following line will result in a
|
// returns true, consequently the following line will result in a
|
||||||
// fatal error.
|
// fatal error.
|
||||||
$object->$property = $value;
|
$object->$property = $value;
|
||||||
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
|
} elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
|
||||||
// we call the getter and hope the __call do the job
|
// we call the getter and hope the __call do the job
|
||||||
$object->$setter($value);
|
$object->$setter($value);
|
||||||
} else {
|
} else {
|
||||||
@ -377,13 +436,100 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
'Neither the property "%s" nor one of the methods %s"%s()", '.
|
'Neither the property "%s" nor one of the methods %s"%s()", '.
|
||||||
'"__set()" or "__call()" exist and have public access in class "%s".',
|
'"__set()" or "__call()" exist and have public access in class "%s".',
|
||||||
$property,
|
$property,
|
||||||
$guessedAdders,
|
implode('', array_map(function ($singular) {
|
||||||
|
return '"add'.$singular.'()"/"remove'.$singular.'()", ';
|
||||||
|
}, $singulars)),
|
||||||
$setter,
|
$setter,
|
||||||
$reflClass->name
|
$reflClass->name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts a collection-valued property by calling add*() and remove*()
|
||||||
|
* methods.
|
||||||
|
*
|
||||||
|
* @param object $object The object to write to
|
||||||
|
* @param string $property The property to write
|
||||||
|
* @param array|\Traversable $collection The collection to write
|
||||||
|
* @param string $addMethod The add*() method
|
||||||
|
* @param string $removeMethod The remove*() method
|
||||||
|
*/
|
||||||
|
private function writeCollection($object, $property, $collection, $addMethod, $removeMethod)
|
||||||
|
{
|
||||||
|
// At this point the add and remove methods have been found
|
||||||
|
// Use iterator_to_array() instead of clone in order to prevent side effects
|
||||||
|
// see https://github.com/symfony/symfony/issues/4670
|
||||||
|
$itemsToAdd = is_object($collection) ? iterator_to_array($collection) : $collection;
|
||||||
|
$itemToRemove = array();
|
||||||
|
$propertyValue = $this->readProperty($object, $property);
|
||||||
|
$previousValue = $propertyValue[self::VALUE];
|
||||||
|
|
||||||
|
if (is_array($previousValue) || $previousValue instanceof \Traversable) {
|
||||||
|
foreach ($previousValue as $previousItem) {
|
||||||
|
foreach ($collection as $key => $item) {
|
||||||
|
if ($item === $previousItem) {
|
||||||
|
// Item found, don't add
|
||||||
|
unset($itemsToAdd[$key]);
|
||||||
|
|
||||||
|
// Next $previousItem
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item not found, add to remove list
|
||||||
|
$itemToRemove[] = $previousItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($itemToRemove as $item) {
|
||||||
|
call_user_func(array($object, $removeMethod), $item);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($itemsToAdd as $item) {
|
||||||
|
call_user_func(array($object, $addMethod), $item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a property is writable in the given object.
|
||||||
|
*
|
||||||
|
* @param object $object The object to write to
|
||||||
|
* @param string $property The property to write
|
||||||
|
*
|
||||||
|
* @return Boolean Whether the property is writable
|
||||||
|
*/
|
||||||
|
private function isPropertyWritable($object, $property)
|
||||||
|
{
|
||||||
|
if (!is_object($object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$reflClass = new \ReflectionClass($object);
|
||||||
|
|
||||||
|
$setter = 'set'.$this->camelize($property);
|
||||||
|
$classHasProperty = $reflClass->hasProperty($property);
|
||||||
|
|
||||||
|
if ($this->isMethodAccessible($reflClass, $setter, 1)
|
||||||
|
|| $this->isMethodAccessible($reflClass, '__set', 2)
|
||||||
|
|| ($classHasProperty && $reflClass->getProperty($property)->isPublic())
|
||||||
|
|| (!$classHasProperty && property_exists($object, $property))
|
||||||
|
|| ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$plural = $this->camelize($property);
|
||||||
|
|
||||||
|
// Any of the two methods is required, but not yet known
|
||||||
|
$singulars = (array) StringUtil::singularify($plural);
|
||||||
|
|
||||||
|
if (null !== $this->findAdderAndRemover($reflClass, $singulars)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Camelizes a given string.
|
* Camelizes a given string.
|
||||||
*
|
*
|
||||||
@ -403,30 +549,21 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
* @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|null An array containing the adder and remover when found, null otherwise
|
||||||
*
|
|
||||||
* @throws NoSuchPropertyException If the property does not exist
|
|
||||||
*/
|
*/
|
||||||
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars)
|
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars)
|
||||||
{
|
{
|
||||||
|
$exception = null;
|
||||||
|
|
||||||
foreach ($singulars as $singular) {
|
foreach ($singulars as $singular) {
|
||||||
$addMethod = 'add'.$singular;
|
$addMethod = 'add'.$singular;
|
||||||
$removeMethod = 'remove'.$singular;
|
$removeMethod = 'remove'.$singular;
|
||||||
|
|
||||||
$addMethodFound = $this->isAccessible($reflClass, $addMethod, 1);
|
$addMethodFound = $this->isMethodAccessible($reflClass, $addMethod, 1);
|
||||||
$removeMethodFound = $this->isAccessible($reflClass, $removeMethod, 1);
|
$removeMethodFound = $this->isMethodAccessible($reflClass, $removeMethod, 1);
|
||||||
|
|
||||||
if ($addMethodFound && $removeMethodFound) {
|
if ($addMethodFound && $removeMethodFound) {
|
||||||
return array($addMethod, $removeMethod);
|
return array($addMethod, $removeMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($addMethodFound xor $removeMethodFound) {
|
|
||||||
throw new NoSuchPropertyException(sprintf(
|
|
||||||
'Found the public method "%s()", but did not find a public "%s()" on class %s',
|
|
||||||
$addMethodFound ? $addMethod : $removeMethod,
|
|
||||||
$addMethodFound ? $removeMethod : $addMethod,
|
|
||||||
$reflClass->name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -442,7 +579,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
* @return Boolean Whether the method is public and has $parameters
|
* @return Boolean Whether the method is public and has $parameters
|
||||||
* required parameters
|
* required parameters
|
||||||
*/
|
*/
|
||||||
private function isAccessible(\ReflectionClass $class, $methodName, $parameters)
|
private function isMethodAccessible(\ReflectionClass $class, $methodName, $parameters)
|
||||||
{
|
{
|
||||||
if ($class->hasMethod($methodName)) {
|
if ($class->hasMethod($methodName)) {
|
||||||
$method = $class->getMethod($methodName);
|
$method = $class->getMethod($methodName);
|
||||||
|
@ -19,7 +19,7 @@ namespace Symfony\Component\PropertyAccess;
|
|||||||
interface PropertyAccessorInterface
|
interface PropertyAccessorInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Sets the value at the end of the property path of the object
|
* Sets the value at the end of the property path of the object graph.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
*
|
*
|
||||||
@ -43,14 +43,15 @@ interface PropertyAccessorInterface
|
|||||||
* @param string|PropertyPathInterface $propertyPath The property path to modify
|
* @param string|PropertyPathInterface $propertyPath The property path to modify
|
||||||
* @param mixed $value The value to set at the end of the property path
|
* @param mixed $value The value to set at the end of the property path
|
||||||
*
|
*
|
||||||
* @throws Exception\NoSuchPropertyException If a property does not exist or is not public.
|
* @throws Exception\InvalidArgumentException If the property path is invalid
|
||||||
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
|
* @throws Exception\AccessException If a property/index does not exist or is not public
|
||||||
* nor array
|
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
|
||||||
|
* nor array
|
||||||
*/
|
*/
|
||||||
public function setValue(&$objectOrArray, $propertyPath, $value);
|
public function setValue(&$objectOrArray, $propertyPath, $value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value at the end of the property path of the object
|
* Returns the value at the end of the property path of the object graph.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
*
|
*
|
||||||
@ -75,7 +76,40 @@ interface PropertyAccessorInterface
|
|||||||
*
|
*
|
||||||
* @return mixed The value at the end of the property path
|
* @return mixed The value at the end of the property path
|
||||||
*
|
*
|
||||||
* @throws Exception\NoSuchPropertyException If a property does not exist or is not public.
|
* @throws Exception\InvalidArgumentException If the property path is invalid
|
||||||
|
* @throws Exception\AccessException If a property/index does not exist or is not public
|
||||||
|
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
|
||||||
|
* nor array
|
||||||
*/
|
*/
|
||||||
public function getValue($objectOrArray, $propertyPath);
|
public function getValue($objectOrArray, $propertyPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a value can be written at a given property path.
|
||||||
|
*
|
||||||
|
* Whenever this method returns true, {@link setValue()} is guaranteed not
|
||||||
|
* to throw an exception when called with the same arguments.
|
||||||
|
*
|
||||||
|
* @param object|array $objectOrArray The object or array to check
|
||||||
|
* @param string|PropertyPathInterface $propertyPath The property path to check
|
||||||
|
*
|
||||||
|
* @return Boolean Whether the value can be set
|
||||||
|
*
|
||||||
|
* @throws Exception\InvalidArgumentException If the property path is invalid
|
||||||
|
*/
|
||||||
|
public function isWritable($objectOrArray, $propertyPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a property path can be read from an object graph.
|
||||||
|
*
|
||||||
|
* Whenever this method returns true, {@link getValue()} is guaranteed not
|
||||||
|
* to throw an exception when called with the same arguments.
|
||||||
|
*
|
||||||
|
* @param object|array $objectOrArray The object or array to check
|
||||||
|
* @param string|PropertyPathInterface $propertyPath The property path to check
|
||||||
|
*
|
||||||
|
* @return Boolean Whether the property path can be read
|
||||||
|
*
|
||||||
|
* @throws Exception\InvalidArgumentException If the property path is invalid
|
||||||
|
*/
|
||||||
|
public function isReadable($objectOrArray, $propertyPath);
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
<?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\PropertyAccess\Tests\Fixtures;
|
|
||||||
|
|
||||||
class Author
|
|
||||||
{
|
|
||||||
public $firstName;
|
|
||||||
private $lastName;
|
|
||||||
private $australian;
|
|
||||||
public $child;
|
|
||||||
private $readPermissions;
|
|
||||||
|
|
||||||
private $privateProperty;
|
|
||||||
|
|
||||||
public function setLastName($lastName)
|
|
||||||
{
|
|
||||||
$this->lastName = $lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastName()
|
|
||||||
{
|
|
||||||
return $this->lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getPrivateGetter()
|
|
||||||
{
|
|
||||||
return 'foobar';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAustralian($australian)
|
|
||||||
{
|
|
||||||
$this->australian = $australian;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAustralian()
|
|
||||||
{
|
|
||||||
return $this->australian;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setReadPermissions($bool)
|
|
||||||
{
|
|
||||||
$this->readPermissions = $bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasReadPermissions()
|
|
||||||
{
|
|
||||||
return $this->readPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isPrivateIsser()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPrivateSetter()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setPrivateSetter($data)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
<?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\PropertyAccess\Tests\Fixtures;
|
|
||||||
|
|
||||||
class Magician
|
|
||||||
{
|
|
||||||
private $foobar;
|
|
||||||
|
|
||||||
public function __set($property, $value)
|
|
||||||
{
|
|
||||||
$this->$property = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __get($property)
|
|
||||||
{
|
|
||||||
return isset($this->$property) ? $this->$property : null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
<?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\PropertyAccess\Tests\Fixtures;
|
|
||||||
|
|
||||||
class MagicianCall
|
|
||||||
{
|
|
||||||
private $foobar;
|
|
||||||
|
|
||||||
public function __call($name, $args)
|
|
||||||
{
|
|
||||||
$property = lcfirst(substr($name, 3));
|
|
||||||
if ('get' === substr($name, 0, 3)) {
|
|
||||||
return isset($this->$property) ? $this->$property : null;
|
|
||||||
} elseif ('set' === substr($name, 0, 3)) {
|
|
||||||
$value = 1 == count($args) ? $args[0] : null;
|
|
||||||
$this->$property = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,115 @@
|
|||||||
|
<?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\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
public $publicProperty;
|
||||||
|
protected $protectedProperty;
|
||||||
|
private $privateProperty;
|
||||||
|
|
||||||
|
private $publicAccessor;
|
||||||
|
private $publicIsAccessor;
|
||||||
|
private $publicHasAccessor;
|
||||||
|
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->publicProperty = $value;
|
||||||
|
$this->publicAccessor = $value;
|
||||||
|
$this->publicIsAccessor = $value;
|
||||||
|
$this->publicHasAccessor = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPublicAccessor($value)
|
||||||
|
{
|
||||||
|
$this->publicAccessor = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPublicAccessor()
|
||||||
|
{
|
||||||
|
return $this->publicAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPublicIsAccessor($value)
|
||||||
|
{
|
||||||
|
$this->publicIsAccessor = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPublicIsAccessor()
|
||||||
|
{
|
||||||
|
return $this->publicIsAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPublicHasAccessor($value)
|
||||||
|
{
|
||||||
|
$this->publicHasAccessor = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPublicHasAccessor()
|
||||||
|
{
|
||||||
|
return $this->publicHasAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setProtectedAccessor($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getProtectedAccessor()
|
||||||
|
{
|
||||||
|
return 'foobar';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setProtectedIsAccessor($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isProtectedIsAccessor()
|
||||||
|
{
|
||||||
|
return 'foobar';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setProtectedHasAccessor($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasProtectedHasAccessor()
|
||||||
|
{
|
||||||
|
return 'foobar';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setPrivateAccessor($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPrivateAccessor()
|
||||||
|
{
|
||||||
|
return 'foobar';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setPrivateIsAccessor($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isPrivateIsAccessor()
|
||||||
|
{
|
||||||
|
return 'foobar';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setPrivateHasAccessor($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasPrivateHasAccessor()
|
||||||
|
{
|
||||||
|
return 'foobar';
|
||||||
|
}
|
||||||
|
}
|
@ -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\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
class TestClassMagicCall
|
||||||
|
{
|
||||||
|
private $magicCallProperty;
|
||||||
|
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->magicCallProperty = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call($method, array $args)
|
||||||
|
{
|
||||||
|
if ('getMagicCallProperty' === $method) {
|
||||||
|
return $this->magicCallProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('getConstantMagicCallProperty' === $method) {
|
||||||
|
return 'constant value';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('setMagicCallProperty' === $method) {
|
||||||
|
$this->magicCallProperty = reset($args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<?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\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
class TestClassMagicGet
|
||||||
|
{
|
||||||
|
private $magicProperty;
|
||||||
|
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->magicProperty = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __set($property, $value)
|
||||||
|
{
|
||||||
|
if ('magicProperty' === $property) {
|
||||||
|
$this->magicProperty = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($property)
|
||||||
|
{
|
||||||
|
if ('magicProperty' === $property) {
|
||||||
|
return $this->magicProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('constantMagicProperty' === $property) {
|
||||||
|
return 'constant value';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\PropertyAccess\Tests;
|
namespace Symfony\Component\PropertyAccess\Tests;
|
||||||
|
|
||||||
use Symfony\Component\PropertyAccess\Exception\ExceptionInterface;
|
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\PropertyAccess\StringUtil;
|
|
||||||
|
|
||||||
class PropertyAccessorCollectionTest_Car
|
class PropertyAccessorCollectionTest_Car
|
||||||
{
|
{
|
||||||
@ -47,19 +45,6 @@ class PropertyAccessorCollectionTest_Car
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyAccessorCollectionTest_CarCustomSingular
|
|
||||||
{
|
|
||||||
public function addFoo($axis) {}
|
|
||||||
|
|
||||||
public function removeFoo($axis) {}
|
|
||||||
|
|
||||||
public function getAxes() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PropertyAccessorCollectionTest_Engine
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
class PropertyAccessorCollectionTest_CarOnlyAdder
|
class PropertyAccessorCollectionTest_CarOnlyAdder
|
||||||
{
|
{
|
||||||
public function addAxis($axis) {}
|
public function addAxis($axis) {}
|
||||||
@ -79,13 +64,6 @@ class PropertyAccessorCollectionTest_CarNoAdderAndRemover
|
|||||||
public function getAxes() {}
|
public function getAxes() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyAccessorCollectionTest_CarNoAdderAndRemoverWithProperty
|
|
||||||
{
|
|
||||||
protected $axes = array();
|
|
||||||
|
|
||||||
public function getAxes() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PropertyAccessorCollectionTest_CompositeCar
|
class PropertyAccessorCollectionTest_CompositeCar
|
||||||
{
|
{
|
||||||
public function getStructure() {}
|
public function getStructure() {}
|
||||||
@ -116,52 +94,34 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas
|
|||||||
|
|
||||||
abstract protected function getCollection(array $array);
|
abstract protected function getCollection(array $array);
|
||||||
|
|
||||||
public function testGetValueReadsArrayAccess()
|
public function getValidPropertyPaths()
|
||||||
{
|
{
|
||||||
$object = $this->getCollection(array('firstName' => 'Bernhard'));
|
return array(
|
||||||
|
array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'),
|
||||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($object, '[firstName]'));
|
array(array('person' => array('firstName' => 'Bernhard')), '[person][firstName]', 'Bernhard'),
|
||||||
}
|
);
|
||||||
|
|
||||||
public function testGetValueReadsNestedArrayAccess()
|
|
||||||
{
|
|
||||||
$object = $this->getCollection(array('person' => array('firstName' => 'Bernhard')));
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($object, '[person][firstName]'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @dataProvider getValidPropertyPaths
|
||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfArrayAccessExpected()
|
public function testGetValue(array $array, $path, $value)
|
||||||
{
|
{
|
||||||
$this->propertyAccessor->getValue(new \stdClass(), '[firstName]');
|
$collection = $this->getCollection($array);
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueUpdatesArrayAccess()
|
$this->assertSame($value, $this->propertyAccessor->getValue($collection, $path));
|
||||||
{
|
|
||||||
$object = $this->getCollection(array());
|
|
||||||
|
|
||||||
$this->propertyAccessor->setValue($object, '[firstName]', 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $object['firstName']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueUpdatesNestedArrayAccess()
|
|
||||||
{
|
|
||||||
$object = $this->getCollection(array());
|
|
||||||
|
|
||||||
$this->propertyAccessor->setValue($object, '[person][firstName]', 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $object['person']['firstName']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @dataProvider getValidPropertyPaths
|
||||||
*/
|
*/
|
||||||
public function testSetValueThrowsExceptionIfArrayAccessExpected()
|
public function testSetValue(array $array, $path)
|
||||||
{
|
{
|
||||||
$this->propertyAccessor->setValue(new \stdClass(), '[firstName]', 'Bernhard');
|
$collection = $this->getCollection($array);
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($collection, $path, 'Updated');
|
||||||
|
|
||||||
|
$this->assertSame('Updated', $this->propertyAccessor->getValue($collection, $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetValueCallsAdderAndRemoverForCollections()
|
public function testSetValueCallsAdderAndRemoverForCollections()
|
||||||
@ -210,115 +170,67 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas
|
|||||||
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
|
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetValueCallsCustomAdderAndRemover()
|
|
||||||
{
|
|
||||||
$this->markTestSkipped('This feature is temporarily disabled as of 2.1');
|
|
||||||
|
|
||||||
$car = $this->getMock(__CLASS__.'_CarCustomSingular');
|
|
||||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
|
||||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
|
||||||
|
|
||||||
$car->expects($this->at(0))
|
|
||||||
->method('getAxes')
|
|
||||||
->will($this->returnValue($axesBefore));
|
|
||||||
$car->expects($this->at(1))
|
|
||||||
->method('removeFoo')
|
|
||||||
->with('fourth');
|
|
||||||
$car->expects($this->at(2))
|
|
||||||
->method('addFoo')
|
|
||||||
->with('first');
|
|
||||||
$car->expects($this->at(3))
|
|
||||||
->method('addFoo')
|
|
||||||
->with('third');
|
|
||||||
|
|
||||||
$this->propertyAccessor->setValue($car, 'axes|foo', $axesAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
|
* @expectedExceptionMessage Neither the property "axes" nor one of the methods "addAx()"/"removeAx()", "addAxe()"/"removeAxe()", "addAxis()"/"removeAxis()", "setAxes()", "__set()" or "__call()" exist and have public access in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover
|
||||||
*/
|
*/
|
||||||
public function testSetValueFailsIfOnlyAdderFound()
|
public function testSetValueFailsIfNoAdderNorRemoverFound()
|
||||||
{
|
|
||||||
$car = $this->getMock(__CLASS__.'_CarOnlyAdder');
|
|
||||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
|
||||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
|
||||||
|
|
||||||
$car->expects($this->any())
|
|
||||||
->method('getAxes')
|
|
||||||
->will($this->returnValue($axesBefore));
|
|
||||||
|
|
||||||
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
|
||||||
*/
|
|
||||||
public function testSetValueFailsIfOnlyRemoverFound()
|
|
||||||
{
|
|
||||||
$car = $this->getMock(__CLASS__.'_CarOnlyRemover');
|
|
||||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
|
||||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
|
||||||
|
|
||||||
$car->expects($this->any())
|
|
||||||
->method('getAxes')
|
|
||||||
->will($this->returnValue($axesBefore));
|
|
||||||
|
|
||||||
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider noAdderRemoverData
|
|
||||||
*/
|
|
||||||
public function testNoAdderAndRemoverThrowsSensibleError($car, $path, $message)
|
|
||||||
{
|
{
|
||||||
|
$car = $this->getMock(__CLASS__.'_CarNoAdderAndRemover');
|
||||||
$axes = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
$axes = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
||||||
|
|
||||||
try {
|
$this->propertyAccessor->setValue($car, 'axes', $axes);
|
||||||
$this->propertyAccessor->setValue($car, $path, $axes);
|
|
||||||
$this->fail('An expected exception was not thrown!');
|
|
||||||
} catch (ExceptionInterface $e) {
|
|
||||||
$this->assertEquals($message, $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function noAdderRemoverData()
|
/**
|
||||||
|
* @dataProvider getValidPropertyPaths
|
||||||
|
*/
|
||||||
|
public function testIsReadable(array $array, $path)
|
||||||
{
|
{
|
||||||
$data = array();
|
$collection = $this->getCollection($array);
|
||||||
|
|
||||||
|
$this->assertTrue($this->propertyAccessor->isReadable($collection, $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getValidPropertyPaths
|
||||||
|
*/
|
||||||
|
public function testIsWritable(array $array, $path)
|
||||||
|
{
|
||||||
|
$collection = $this->getCollection($array);
|
||||||
|
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable($collection, $path, 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableReturnsTrueIfAdderAndRemoverExists()
|
||||||
|
{
|
||||||
|
$car = $this->getMock(__CLASS__.'_Car');
|
||||||
|
$axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third'));
|
||||||
|
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable($car, 'axes', $axes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableReturnsFalseIfOnlyAdderExists()
|
||||||
|
{
|
||||||
|
$car = $this->getMock(__CLASS__.'_CarOnlyAdder');
|
||||||
|
$axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third'));
|
||||||
|
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes', $axes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableReturnsFalseIfOnlyRemoverExists()
|
||||||
|
{
|
||||||
|
$car = $this->getMock(__CLASS__.'_CarOnlyRemover');
|
||||||
|
$axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third'));
|
||||||
|
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes', $axes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists()
|
||||||
|
{
|
||||||
$car = $this->getMock(__CLASS__.'_CarNoAdderAndRemover');
|
$car = $this->getMock(__CLASS__.'_CarNoAdderAndRemover');
|
||||||
$propertyPath = 'axes';
|
$axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third'));
|
||||||
$expectedMessage = sprintf(
|
|
||||||
'Neither the property "axes" nor one of the methods "addAx()", '.
|
|
||||||
'"addAxe()", "addAxis()", "setAxes()", "__set()" or "__call()" exist and have '.
|
|
||||||
'public access in class "%s".',
|
|
||||||
get_class($car)
|
|
||||||
);
|
|
||||||
$data[] = array($car, $propertyPath, $expectedMessage);
|
|
||||||
|
|
||||||
/*
|
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes', $axes));
|
||||||
Temporarily disabled in 2.1
|
|
||||||
|
|
||||||
$propertyPath = new PropertyPath('axes|boo');
|
|
||||||
$expectedMessage = sprintf(
|
|
||||||
'Neither element "axes" nor method "setAxes()" exists in class '
|
|
||||||
.'"%s", nor could adders and removers be found based on the '
|
|
||||||
.'passed singular: %s',
|
|
||||||
get_class($car),
|
|
||||||
'boo'
|
|
||||||
);
|
|
||||||
$data[] = array($car, $propertyPath, $expectedMessage);
|
|
||||||
*/
|
|
||||||
|
|
||||||
$car = $this->getMock(__CLASS__.'_CarNoAdderAndRemoverWithProperty');
|
|
||||||
$propertyPath = 'axes';
|
|
||||||
$expectedMessage = sprintf(
|
|
||||||
'Neither the property "axes" nor one of the methods "addAx()", '.
|
|
||||||
'"addAxe()", "addAxis()", "setAxes()", "__set()" or "__call()" exist and have '.
|
|
||||||
'public access in class "%s".',
|
|
||||||
get_class($car)
|
|
||||||
);
|
|
||||||
$data[] = array($car, $propertyPath, $expectedMessage);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,228 +12,161 @@
|
|||||||
namespace Symfony\Component\PropertyAccess\Tests;
|
namespace Symfony\Component\PropertyAccess\Tests;
|
||||||
|
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\MagicianCall;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
|
|
||||||
|
|
||||||
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var PropertyAccessorBuilder
|
* @var PropertyAccessor
|
||||||
*/
|
*/
|
||||||
private $propertyAccessorBuilder;
|
private $propertyAccessor;
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->propertyAccessorBuilder = new PropertyAccessorBuilder();
|
$this->propertyAccessor = new PropertyAccessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValidPropertyPaths()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(array('Bernhard', 'Schussek'), '[0]', 'Bernhard'),
|
||||||
|
array(array('Bernhard', 'Schussek'), '[1]', 'Schussek'),
|
||||||
|
array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'),
|
||||||
|
array(array('index' => array('firstName' => 'Bernhard')), '[index][firstName]', 'Bernhard'),
|
||||||
|
array((object) array('firstName' => 'Bernhard'), 'firstName', 'Bernhard'),
|
||||||
|
array((object) array('property' => array('firstName' => 'Bernhard')), 'property[firstName]', 'Bernhard'),
|
||||||
|
array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].firstName', 'Bernhard'),
|
||||||
|
array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.firstName', 'Bernhard'),
|
||||||
|
|
||||||
|
// Accessor methods
|
||||||
|
array(new TestClass('Bernhard'), 'publicProperty', 'Bernhard'),
|
||||||
|
array(new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'),
|
||||||
|
array(new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'),
|
||||||
|
array(new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'),
|
||||||
|
|
||||||
|
// Methods are camelized
|
||||||
|
array(new TestClass('Bernhard'), 'public_accessor', 'Bernhard'),
|
||||||
|
|
||||||
|
// Missing indices
|
||||||
|
array(array('index' => array()), '[index][firstName]', null),
|
||||||
|
array(array('root' => array('index' => array())), '[root][index][firstName]', null),
|
||||||
|
|
||||||
|
// Special chars
|
||||||
|
array(array('%!@$§.' => 'Bernhard'), '[%!@$§.]', 'Bernhard'),
|
||||||
|
array(array('index' => array('%!@$§.' => 'Bernhard')), '[index][%!@$§.]', 'Bernhard'),
|
||||||
|
array((object) array('%!@$§' => 'Bernhard'), '%!@$§', 'Bernhard'),
|
||||||
|
array((object) array('property' => (object) array('%!@$§' => 'Bernhard')), 'property.%!@$§', 'Bernhard'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPathsWithMissingProperty()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array((object) array('firstName' => 'Bernhard'), 'lastName'),
|
||||||
|
array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.lastName'),
|
||||||
|
array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].lastName'),
|
||||||
|
array(new TestClass('Bernhard'), 'protectedProperty'),
|
||||||
|
array(new TestClass('Bernhard'), 'privateProperty'),
|
||||||
|
array(new TestClass('Bernhard'), 'protectedAccessor'),
|
||||||
|
array(new TestClass('Bernhard'), 'protectedIsAccessor'),
|
||||||
|
array(new TestClass('Bernhard'), 'protectedHasAccessor'),
|
||||||
|
array(new TestClass('Bernhard'), 'privateAccessor'),
|
||||||
|
array(new TestClass('Bernhard'), 'privateIsAccessor'),
|
||||||
|
array(new TestClass('Bernhard'), 'privateHasAccessor'),
|
||||||
|
|
||||||
|
// Properties are not camelized
|
||||||
|
array(new TestClass('Bernhard'), 'public_property'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPathsWithMissingIndex()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(array('firstName' => 'Bernhard'), '[lastName]'),
|
||||||
|
array(array(), '[index][lastName]'),
|
||||||
|
array(array('index' => array()), '[index][lastName]'),
|
||||||
|
array(array('index' => array('firstName' => 'Bernhard')), '[index][lastName]'),
|
||||||
|
array((object) array('property' => array('firstName' => 'Bernhard')), 'property[lastName]'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get PropertyAccessor configured
|
* @dataProvider getValidPropertyPaths
|
||||||
*
|
|
||||||
* @param string $withMagicCall
|
|
||||||
* @param string $throwExceptionOnInvalidIndex
|
|
||||||
* @return PropertyAccessorInterface
|
|
||||||
*/
|
*/
|
||||||
protected function getPropertyAccessor($withMagicCall = false, $throwExceptionOnInvalidIndex = false)
|
public function testGetValue($objectOrArray, $path, $value)
|
||||||
{
|
{
|
||||||
if ($withMagicCall) {
|
$this->assertSame($value, $this->propertyAccessor->getValue($objectOrArray, $path));
|
||||||
$this->propertyAccessorBuilder->enableMagicCall();
|
|
||||||
} else {
|
|
||||||
$this->propertyAccessorBuilder->disableMagicCall();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($throwExceptionOnInvalidIndex) {
|
|
||||||
$this->propertyAccessorBuilder->enableExceptionOnInvalidIndex();
|
|
||||||
} else {
|
|
||||||
$this->propertyAccessorBuilder->disableExceptionOnInvalidIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->propertyAccessorBuilder->getPropertyAccessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsArray()
|
|
||||||
{
|
|
||||||
$array = array('firstName' => 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($array, '[firstName]'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingProperty
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfIndexNotationExpected()
|
public function testGetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$array = array('firstName' => 'Bernhard');
|
$this->propertyAccessor->getValue($objectOrArray, $path);
|
||||||
|
|
||||||
$this->getPropertyAccessor()->getValue($array, 'firstName');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsZeroIndex()
|
|
||||||
{
|
|
||||||
$array = array('Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($array, '[0]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsIndexWithSpecialChars()
|
|
||||||
{
|
|
||||||
$array = array('%!@$§.' => 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($array, '[%!@$§.]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsNestedIndexWithSpecialChars()
|
|
||||||
{
|
|
||||||
$array = array('root' => array('%!@$§.' => 'Bernhard'));
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($array, '[root][%!@$§.]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsArrayWithCustomPropertyPath()
|
|
||||||
{
|
|
||||||
$array = array('child' => array('index' => array('firstName' => 'Bernhard')));
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($array, '[child][index][firstName]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
|
|
||||||
{
|
|
||||||
$array = array('child' => array('index' => array()));
|
|
||||||
|
|
||||||
// No BC break
|
|
||||||
$this->assertNull($this->getPropertyAccessor()->getValue($array, '[child][index][firstName]'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->getPropertyAccessor(false, true)->getValue($array, '[child][index][firstName]');
|
|
||||||
$this->fail('Getting value on a nonexistent path from array should throw a Symfony\Component\PropertyAccess\Exception\NoSuchIndexException exception');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->assertInstanceof('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException', $e, 'Getting value on a nonexistent path from array should throw a Symfony\Component\PropertyAccess\Exception\NoSuchIndexException exception');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsProperty()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
$object->firstName = 'Bernhard';
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($object, 'firstName'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueIgnoresSingular()
|
|
||||||
{
|
|
||||||
$this->markTestSkipped('This feature is temporarily disabled as of 2.1');
|
|
||||||
|
|
||||||
$object = (object) array('children' => 'Many');
|
|
||||||
|
|
||||||
$this->assertEquals('Many', $this->getPropertyAccessor()->getValue($object, 'children|child'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsPropertyWithSpecialCharsExceptDot()
|
|
||||||
{
|
|
||||||
$array = (object) array('%!@$§' => 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($array, '%!@$§'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsPropertyWithSpecialCharsExceptDotNested()
|
|
||||||
{
|
|
||||||
$object = (object) array('nested' => (object) array('@child' => 'foo'));
|
|
||||||
|
|
||||||
$this->assertEquals('foo', $this->getPropertyAccessor()->getValue($object, 'nested.@child'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsPropertyWithCustomPropertyPath()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
$object->child = array();
|
|
||||||
$object->child['index'] = new Author();
|
|
||||||
$object->child['index']->firstName = 'Bernhard';
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $this->getPropertyAccessor()->getValue($object, 'child[index].firstName'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @dataProvider getPathsWithMissingIndex
|
||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfPropertyIsNotPublic()
|
public function testGetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue(new Author(), 'privateProperty');
|
$this->assertNull($this->propertyAccessor->getValue($objectOrArray, $path));
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsGetters()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
$object->setLastName('Schussek');
|
|
||||||
|
|
||||||
$this->assertEquals('Schussek', $this->getPropertyAccessor()->getValue($object, 'lastName'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueCamelizesGetterNames()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
$object->setLastName('Schussek');
|
|
||||||
|
|
||||||
$this->assertEquals('Schussek', $this->getPropertyAccessor()->getValue($object, 'last_name'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
|
||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfGetterIsNotPublic()
|
public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue(new Author(), 'privateGetter');
|
$this->propertyAccessor = new PropertyAccessor(false, true);
|
||||||
|
$this->propertyAccessor->getValue($objectOrArray, $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueReadsIssers()
|
/**
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
|
||||||
|
*/
|
||||||
|
public function testGetValueThrowsExceptionIfNotArrayAccess()
|
||||||
{
|
{
|
||||||
$object = new Author();
|
$this->propertyAccessor->getValue(new \stdClass(), '[index]');
|
||||||
$object->setAustralian(false);
|
|
||||||
|
|
||||||
$this->assertFalse($this->getPropertyAccessor()->getValue($object, 'australian'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadHassers()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
$object->setReadPermissions(true);
|
|
||||||
|
|
||||||
$this->assertTrue($this->getPropertyAccessor()->getValue($object, 'read_permissions'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueReadsMagicGet()
|
public function testGetValueReadsMagicGet()
|
||||||
{
|
{
|
||||||
$object = new Magician();
|
$this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'magicProperty'));
|
||||||
$object->__set('magicProperty', 'foobar');
|
|
||||||
|
|
||||||
$this->assertSame('foobar', $this->getPropertyAccessor()->getValue($object, 'magicProperty'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// https://github.com/symfony/symfony/pull/4450
|
||||||
* https://github.com/symfony/symfony/pull/4450
|
|
||||||
*/
|
|
||||||
public function testGetValueReadsMagicGetThatReturnsConstant()
|
public function testGetValueReadsMagicGetThatReturnsConstant()
|
||||||
{
|
{
|
||||||
$object = new Magician();
|
$this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'constantMagicProperty'));
|
||||||
|
|
||||||
$this->assertNull($this->getPropertyAccessor()->getValue($object, 'magicProperty'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfIsserIsNotPublic()
|
public function testGetValueDoesNotReadMagicCallByDefault()
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue(new Author(), 'privateIsser');
|
$this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testGetValueReadsMagicCallIfEnabled()
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
|
||||||
*/
|
|
||||||
public function testGetValueThrowsExceptionIfPropertyDoesNotExist()
|
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue(new Author(), 'foobar');
|
$this->propertyAccessor = new PropertyAccessor(true);
|
||||||
|
|
||||||
|
$this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/symfony/symfony/pull/4450
|
||||||
|
public function testGetValueReadsMagicCallThatReturnsConstant()
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = new PropertyAccessor(true);
|
||||||
|
|
||||||
|
$this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'constantMagicCallProperty'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,7 +174,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfNotObjectOrArray()
|
public function testGetValueThrowsExceptionIfNotObjectOrArray()
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue('baz', 'foobar');
|
$this->propertyAccessor->getValue('baz', 'foobar');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,7 +182,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfNull()
|
public function testGetValueThrowsExceptionIfNull()
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue(null, 'foobar');
|
$this->propertyAccessor->getValue(null, 'foobar');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -257,101 +190,85 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetValueThrowsExceptionIfEmpty()
|
public function testGetValueThrowsExceptionIfEmpty()
|
||||||
{
|
{
|
||||||
$this->getPropertyAccessor()->getValue('', 'foobar');
|
$this->propertyAccessor->getValue('', 'foobar');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetValueUpdatesArrays()
|
/**
|
||||||
|
* @dataProvider getValidPropertyPaths
|
||||||
|
*/
|
||||||
|
public function testSetValue($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$array = array();
|
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($array, '[firstName]', 'Bernhard');
|
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertEquals(array('firstName' => 'Bernhard'), $array);
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingProperty
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
|
*/
|
||||||
|
public function testSetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
*/
|
||||||
|
public function testSetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
|
||||||
|
|
||||||
|
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
*/
|
||||||
|
public function testSetValueThrowsNoExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = new PropertyAccessor(false, true);
|
||||||
|
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
|
||||||
|
|
||||||
|
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
|
||||||
|
*/
|
||||||
|
public function testSetValueThrowsExceptionIfNotArrayAccess()
|
||||||
|
{
|
||||||
|
$this->propertyAccessor->setValue(new \stdClass(), '[index]', 'Updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetValueUpdatesMagicSet()
|
||||||
|
{
|
||||||
|
$author = new TestClassMagicGet('Bernhard');
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($author, 'magicProperty', 'Updated');
|
||||||
|
|
||||||
|
$this->assertEquals('Updated', $author->__get('magicProperty'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
*/
|
*/
|
||||||
public function testSetValueThrowsExceptionIfIndexNotationExpected()
|
public function testSetValueDoesNotUpdateMagicCallByDefault()
|
||||||
{
|
{
|
||||||
$array = array();
|
$author = new TestClassMagicCall('Bernhard');
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($array, 'firstName', 'Bernhard');
|
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetValueUpdatesArraysWithCustomPropertyPath()
|
public function testSetValueUpdatesMagicCallIfEnabled()
|
||||||
{
|
{
|
||||||
$array = array();
|
$this->propertyAccessor = new PropertyAccessor(true);
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($array, '[child][index][firstName]', 'Bernhard');
|
$author = new TestClassMagicCall('Bernhard');
|
||||||
|
|
||||||
$this->assertEquals(array('child' => array('index' => array('firstName' => 'Bernhard'))), $array);
|
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueUpdatesProperties()
|
$this->assertEquals('Updated', $author->__call('getMagicCallProperty', array()));
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($object, 'firstName', 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $object->firstName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueUpdatesPropertiesWithCustomPropertyPath()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
$object->child = array();
|
|
||||||
$object->child['index'] = new Author();
|
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($object, 'child[index].firstName', 'Bernhard');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $object->child['index']->firstName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueUpdateMagicSet()
|
|
||||||
{
|
|
||||||
$object = new Magician();
|
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($object, 'magicProperty', 'foobar');
|
|
||||||
|
|
||||||
$this->assertEquals('foobar', $object->__get('magicProperty'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueUpdatesSetters()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($object, 'lastName', 'Schussek');
|
|
||||||
|
|
||||||
$this->assertEquals('Schussek', $object->getLastName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueCamelizesSetterNames()
|
|
||||||
{
|
|
||||||
$object = new Author();
|
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($object, 'last_name', 'Schussek');
|
|
||||||
|
|
||||||
$this->assertEquals('Schussek', $object->getLastName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetValueWithSpecialCharsNested()
|
|
||||||
{
|
|
||||||
$object = new \stdClass();
|
|
||||||
$person = new \stdClass();
|
|
||||||
$person->{'@email'} = null;
|
|
||||||
$object->person = $person;
|
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($object, 'person.@email', 'bar');
|
|
||||||
$this->assertEquals('bar', $object->person->{'@email'});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
|
||||||
*/
|
|
||||||
public function testSetValueThrowsExceptionIfGetterIsNotPublic()
|
|
||||||
{
|
|
||||||
$this->getPropertyAccessor()->setValue(new Author(), 'privateSetter', 'foobar');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -361,7 +278,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$value = 'baz';
|
$value = 'baz';
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($value, 'foobar', 'bam');
|
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -371,7 +288,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$value = null;
|
$value = null;
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($value, 'foobar', 'bam');
|
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -381,54 +298,142 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$value = '';
|
$value = '';
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($value, 'foobar', 'bam');
|
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @dataProvider getValidPropertyPaths
|
||||||
*/
|
*/
|
||||||
public function testSetValueFailsIfMagicCallDisabled()
|
public function testIsReadable($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$value = new MagicianCall();
|
$this->assertTrue($this->propertyAccessor->isReadable($objectOrArray, $path));
|
||||||
|
|
||||||
$this->getPropertyAccessor()->setValue($value, 'foobar', 'bam');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @dataProvider getPathsWithMissingProperty
|
||||||
*/
|
*/
|
||||||
public function testGetValueFailsIfMagicCallDisabled()
|
public function testIsReadableReturnsFalseIfPropertyNotFound($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$value = new MagicianCall();
|
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
|
||||||
|
|
||||||
$this->getPropertyAccessor()->getValue($value, 'foobar', 'bam');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueReadsMagicCall()
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
*/
|
||||||
|
public function testIsReadableReturnsTrueIfIndexNotFound($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$propertyAccessor = new PropertyAccessor(true);
|
// Non-existing indices can be read. In this case, null is returned
|
||||||
$object = new MagicianCall();
|
$this->assertTrue($this->propertyAccessor->isReadable($objectOrArray, $path));
|
||||||
$object->setMagicProperty('foobar');
|
|
||||||
|
|
||||||
$this->assertSame('foobar', $propertyAccessor->getValue($object, 'magicProperty'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueReadsMagicCallThatReturnsConstant()
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
*/
|
||||||
|
public function testIsReadableReturnsFalseIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
|
||||||
{
|
{
|
||||||
$propertyAccessor = new PropertyAccessor(true);
|
$this->propertyAccessor = new PropertyAccessor(false, true);
|
||||||
$object = new MagicianCall();
|
|
||||||
|
|
||||||
$this->assertNull($propertyAccessor->getValue($object, 'MagicProperty'));
|
// When exceptions are enabled, non-existing indices cannot be read
|
||||||
|
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetValueUpdatesMagicCall()
|
public function testIsReadableRecognizesMagicGet()
|
||||||
{
|
{
|
||||||
$propertyAccessor = new PropertyAccessor(true);
|
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicGet('Bernhard'), 'magicProperty'));
|
||||||
$object = new MagicianCall();
|
|
||||||
|
|
||||||
$propertyAccessor->setValue($object, 'magicProperty', 'foobar');
|
|
||||||
|
|
||||||
$this->assertEquals('foobar', $object->getMagicProperty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIsReadableDoesNotRecognizeMagicCallByDefault()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsReadableRecognizesMagicCallIfEnabled()
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = new PropertyAccessor(true);
|
||||||
|
|
||||||
|
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsReadableThrowsExceptionIfNotObjectOrArray()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isReadable('baz', 'foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsReadableThrowsExceptionIfNull()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isReadable(null, 'foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsReadableThrowsExceptionIfEmpty()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isReadable('', 'foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getValidPropertyPaths
|
||||||
|
*/
|
||||||
|
public function testIsWritable($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path, 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingProperty
|
||||||
|
*/
|
||||||
|
public function testIsWritableReturnsFalseIfPropertyNotFound($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path, 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
*/
|
||||||
|
public function testIsWritableReturnsTrueIfIndexNotFound($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
// Non-existing indices can be written. Arrays are created on-demand.
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path, 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getPathsWithMissingIndex
|
||||||
|
*/
|
||||||
|
public function testIsWritableReturnsTrueIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = new PropertyAccessor(false, true);
|
||||||
|
|
||||||
|
// Non-existing indices can be written even if exceptions are enabled
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path, 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableRecognizesMagicSet()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicGet('Bernhard'), 'magicProperty', 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableDoesNotRecognizeMagicCallByDefault()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty', 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableRecognizesMagicCallIfEnabled()
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = new PropertyAccessor(true);
|
||||||
|
|
||||||
|
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty', 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableThrowsExceptionIfNotObjectOrArray()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable('baz', 'foobar', 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableThrowsExceptionIfNull()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable(null, 'foobar', 'Updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsWritableThrowsExceptionIfEmpty()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->propertyAccessor->isWritable('', 'foobar', 'Updated'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user