2013-01-07 08:19:31 +00:00
< ? 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 ;
2015-02-21 12:49:08 +00:00
use Symfony\Component\PropertyAccess\Exception\AccessException ;
2013-01-07 08:19:31 +00:00
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException ;
2013-09-27 15:14:57 +01:00
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException ;
2013-01-07 08:19:31 +00:00
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException ;
/**
* Default implementation of { @ link PropertyAccessorInterface } .
*
* @ author Bernhard Schussek < bschussek @ gmail . com >
*/
class PropertyAccessor implements PropertyAccessorInterface
{
const VALUE = 0 ;
const IS_REF = 1 ;
2013-09-27 15:14:57 +01:00
/**
2014-04-16 11:34:42 +01:00
* @ var bool
2013-09-27 15:14:57 +01:00
*/
2013-03-04 22:59:45 +00:00
private $magicCall ;
2013-09-27 15:14:57 +01:00
/**
2014-04-16 11:34:42 +01:00
* @ var bool
2013-09-27 15:14:57 +01:00
*/
2014-03-28 18:21:04 +00:00
private $ignoreInvalidIndices ;
2013-09-27 15:14:57 +01:00
2013-01-07 08:19:31 +00:00
/**
* Should not be used by application code . Use
2013-08-24 23:47:14 +01:00
* { @ link PropertyAccess :: createPropertyAccessor ()} instead .
2013-01-07 08:19:31 +00:00
*/
2013-09-27 15:14:57 +01:00
public function __construct ( $magicCall = false , $throwExceptionOnInvalidIndex = false )
2013-01-07 08:19:31 +00:00
{
2013-03-04 22:59:45 +00:00
$this -> magicCall = $magicCall ;
2014-03-28 18:21:04 +00:00
$this -> ignoreInvalidIndices = ! $throwExceptionOnInvalidIndex ;
2013-01-07 08:19:31 +00:00
}
/**
* { @ inheritdoc }
*/
public function getValue ( $objectOrArray , $propertyPath )
{
2015-02-19 12:40:39 +00:00
if ( ! $propertyPath instanceof PropertyPathInterface ) {
2013-01-07 08:19:31 +00:00
$propertyPath = new PropertyPath ( $propertyPath );
}
2015-03-30 16:54:10 +01:00
$propertyValues = & $this -> readPropertiesUntil ( $objectOrArray , $propertyPath , $propertyPath -> getLength (), $this -> ignoreInvalidIndices );
2013-01-07 08:19:31 +00:00
return $propertyValues [ count ( $propertyValues ) - 1 ][ self :: VALUE ];
}
/**
* { @ inheritdoc }
*/
public function setValue ( & $objectOrArray , $propertyPath , $value )
{
2015-02-19 12:40:39 +00:00
if ( ! $propertyPath instanceof PropertyPathInterface ) {
2013-01-07 08:19:31 +00:00
$propertyPath = new PropertyPath ( $propertyPath );
}
2015-03-27 22:06:33 +00:00
$propertyValues = & $this -> readPropertiesUntil ( $objectOrArray , $propertyPath , $propertyPath -> getLength () - 1 );
2013-01-07 08:19:31 +00:00
// 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 ) {
2015-03-27 22:06:33 +00:00
$objectOrArray = & $propertyValues [ $i ][ self :: VALUE ];
2013-01-07 08:19:31 +00:00
2015-03-02 20:56:10 +00:00
$property = $propertyPath -> getElement ( $i );
2013-01-07 08:19:31 +00:00
2015-03-02 20:56:10 +00:00
if ( $propertyPath -> isIndex ( $i )) {
$this -> writeIndex ( $objectOrArray , $property , $value );
} else {
$this -> writeProperty ( $objectOrArray , $property , $value );
}
if ( $propertyValues [ $i ][ self :: IS_REF ]) {
return ;
2013-01-07 08:19:31 +00:00
}
2014-09-21 19:53:12 +01:00
$value = & $objectOrArray ;
2013-01-07 08:19:31 +00:00
}
}
2014-03-28 16:21:37 +00:00
/**
* { @ inheritdoc }
*/
public function isReadable ( $objectOrArray , $propertyPath )
{
2015-02-21 12:49:08 +00:00
if ( ! $propertyPath instanceof PropertyPathInterface ) {
2014-03-28 16:21:37 +00:00
$propertyPath = new PropertyPath ( $propertyPath );
}
try {
2014-03-28 18:21:04 +00:00
$this -> readPropertiesUntil ( $objectOrArray , $propertyPath , $propertyPath -> getLength (), $this -> ignoreInvalidIndices );
2014-03-28 16:21:37 +00:00
return true ;
2015-02-21 12:49:08 +00:00
} catch ( AccessException $e ) {
2014-03-28 16:21:37 +00:00
return false ;
} catch ( UnexpectedTypeException $e ) {
return false ;
}
}
/**
* { @ inheritdoc }
*/
2014-03-30 17:19:15 +01:00
public function isWritable ( $objectOrArray , $propertyPath )
2014-03-28 16:21:37 +00:00
{
2015-02-21 12:49:08 +00:00
if ( ! $propertyPath instanceof PropertyPathInterface ) {
2014-03-28 16:21:37 +00:00
$propertyPath = new PropertyPath ( $propertyPath );
}
try {
$propertyValues = $this -> readPropertiesUntil ( $objectOrArray , $propertyPath , $propertyPath -> getLength () - 1 );
// 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 ];
2015-03-02 20:56:10 +00:00
$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 ;
2014-03-28 16:21:37 +00:00
}
}
2015-03-02 20:56:10 +00:00
if ( $propertyValues [ $i ][ self :: IS_REF ]) {
return true ;
}
2014-03-28 16:21:37 +00:00
}
return true ;
2015-02-21 12:49:08 +00:00
} catch ( AccessException $e ) {
2014-03-28 16:21:37 +00:00
return false ;
2015-02-21 12:49:08 +00:00
} catch ( UnexpectedTypeException $e ) {
2014-03-28 16:21:37 +00:00
return false ;
}
}
2013-01-07 08:19:31 +00:00
/**
* Reads the path from an object up to a given path index .
*
2014-03-28 18:21:04 +00:00
* @ param object | array $objectOrArray The object or array to read from
* @ param PropertyPathInterface $propertyPath The property path to read
2014-04-16 09:08:40 +01:00
* @ param int $lastIndex The index up to which should be read
* @ param bool $ignoreInvalidIndices Whether to ignore invalid indices
2014-03-28 18:21:04 +00:00
* or throw an exception
2013-01-07 08:19:31 +00:00
*
* @ return array The values read in the path .
*
* @ throws UnexpectedTypeException If a value within the path is neither object nor array .
2014-12-04 20:26:11 +00:00
* @ throws NoSuchIndexException If a non - existing index is accessed
2013-01-07 08:19:31 +00:00
*/
2014-03-28 18:21:04 +00:00
private function & readPropertiesUntil ( & $objectOrArray , PropertyPathInterface $propertyPath , $lastIndex , $ignoreInvalidIndices = true )
2013-01-07 08:19:31 +00:00
{
2015-02-18 20:52:22 +00:00
if ( ! is_object ( $objectOrArray ) && ! is_array ( $objectOrArray )) {
throw new UnexpectedTypeException ( $objectOrArray , 'object or array' );
}
2013-01-07 08:19:31 +00:00
$propertyValues = array ();
2012-07-31 12:15:57 +01:00
for ( $i = 0 ; $i < $lastIndex ; ++ $i ) {
2013-01-07 08:19:31 +00:00
$property = $propertyPath -> getElement ( $i );
$isIndex = $propertyPath -> isIndex ( $i );
// Create missing nested arrays on demand
2015-01-09 18:07:12 +00:00
if ( $isIndex &&
2015-01-09 14:53:40 +00:00
(
( $objectOrArray instanceof \ArrayAccess && ! isset ( $objectOrArray [ $property ])) ||
( is_array ( $objectOrArray ) && ! array_key_exists ( $property , $objectOrArray ))
)
) {
2014-03-28 18:21:04 +00:00
if ( ! $ignoreInvalidIndices ) {
2014-05-19 16:05:32 +01:00
if ( ! is_array ( $objectOrArray )) {
if ( ! $objectOrArray instanceof \Traversable ) {
throw new NoSuchIndexException ( sprintf (
2015-02-21 15:36:02 +00:00
'Cannot read index "%s" while trying to traverse path "%s".' ,
$property ,
( string ) $propertyPath
2014-05-19 16:05:32 +01:00
));
}
$objectOrArray = iterator_to_array ( $objectOrArray );
}
throw new NoSuchIndexException ( sprintf (
2015-02-21 15:36:02 +00:00
'Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".' ,
2014-05-19 16:05:32 +01:00
$property ,
2015-02-21 15:36:02 +00:00
( string ) $propertyPath ,
2014-05-19 16:05:32 +01:00
print_r ( array_keys ( $objectOrArray ), true )
));
2013-09-27 15:14:57 +01:00
}
2014-03-28 14:50:34 +00:00
2013-01-07 08:19:31 +00:00
$objectOrArray [ $property ] = $i + 1 < $propertyPath -> getLength () ? array () : null ;
}
2012-07-31 12:15:57 +01:00
if ( $isIndex ) {
2015-03-27 22:06:33 +00:00
$propertyValue = & $this -> readIndex ( $objectOrArray , $property );
2012-07-31 12:15:57 +01:00
} else {
2015-03-27 22:06:33 +00:00
$propertyValue = & $this -> readProperty ( $objectOrArray , $property );
2012-07-31 12:15:57 +01:00
}
2015-03-27 22:06:33 +00:00
$objectOrArray = & $propertyValue [ self :: VALUE ];
2013-01-07 08:19:31 +00:00
2015-02-18 20:52:22 +00:00
// the final value of the path must not be validated
if ( $i + 1 < $propertyPath -> getLength () && ! is_object ( $objectOrArray ) && ! is_array ( $objectOrArray )) {
throw new UnexpectedTypeException ( $objectOrArray , 'object or array' );
}
2015-03-27 22:06:33 +00:00
$propertyValues [] = & $propertyValue ;
2013-01-07 08:19:31 +00:00
}
return $propertyValues ;
}
2012-07-31 12:15:57 +01:00
/**
* Reads a key from an array - like structure .
*
2013-03-27 22:40:36 +00:00
* @ param \ArrayAccess | array $array The array or \ArrayAccess object to read from
2014-04-16 07:51:57 +01:00
* @ param string | int $index The key to read
2012-07-31 12:15:57 +01:00
*
* @ return mixed The value of the key
*
2014-03-28 16:07:20 +00:00
* @ throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
2012-07-31 12:15:57 +01:00
*/
private function & readIndex ( & $array , $index )
{
if ( ! $array instanceof \ArrayAccess && ! is_array ( $array )) {
2015-02-21 15:36:02 +00:00
throw new NoSuchIndexException ( sprintf ( 'Cannot read index "%s" from object of type "%s" because it doesn\'t implement \ArrayAccess.' , $index , get_class ( $array )));
2012-07-31 12:15:57 +01:00
}
// Use an array instead of an object since performance is very crucial here
$result = array (
self :: VALUE => null ,
2014-09-21 19:53:12 +01:00
self :: IS_REF => false ,
2012-07-31 12:15:57 +01:00
);
if ( isset ( $array [ $index ])) {
if ( is_array ( $array )) {
2015-03-27 22:06:33 +00:00
$result [ self :: VALUE ] = & $array [ $index ];
2012-07-31 12:15:57 +01:00
$result [ self :: IS_REF ] = true ;
} else {
$result [ self :: VALUE ] = $array [ $index ];
// Objects are always passed around by reference
$result [ self :: IS_REF ] = is_object ( $array [ $index ]) ? true : false ;
}
}
return $result ;
}
2013-01-07 08:19:31 +00:00
/**
* Reads the a property from an object or array .
*
2012-07-31 12:15:57 +01:00
* @ param object $object The object to read from .
* @ param string $property The property to read .
2013-01-07 08:19:31 +00:00
*
* @ return mixed The value of the read property
*
2013-04-18 15:39:54 +01:00
* @ throws NoSuchPropertyException If the property does not exist or is not
* public .
2013-01-07 08:19:31 +00:00
*/
2012-07-31 12:15:57 +01:00
private function & readProperty ( & $object , $property )
2013-01-07 08:19:31 +00:00
{
// Use an array instead of an object since performance is
// very crucial here
$result = array (
self :: VALUE => null ,
2014-09-21 19:53:12 +01:00
self :: IS_REF => false ,
2013-01-07 08:19:31 +00:00
);
2012-07-31 12:15:57 +01:00
if ( ! is_object ( $object )) {
2015-02-21 15:36:02 +00:00
throw new NoSuchPropertyException ( sprintf ( 'Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%s]" instead.' , $property , $property ));
2012-07-31 12:15:57 +01:00
}
2013-01-07 08:19:31 +00:00
2014-10-22 18:11:55 +01:00
$camelized = $this -> camelize ( $property );
2012-07-31 12:15:57 +01:00
$reflClass = new \ReflectionClass ( $object );
2014-10-22 18:11:55 +01:00
$getter = 'get' . $camelized ;
$getsetter = lcfirst ( $camelized ); // jQuery style, e.g. read: last(), write: last($item)
$isser = 'is' . $camelized ;
$hasser = 'has' . $camelized ;
2013-04-18 15:39:54 +01:00
$classHasProperty = $reflClass -> hasProperty ( $property );
2013-01-07 08:19:31 +00:00
2013-04-18 15:39:54 +01:00
if ( $reflClass -> hasMethod ( $getter ) && $reflClass -> getMethod ( $getter ) -> isPublic ()) {
2012-07-31 12:15:57 +01:00
$result [ self :: VALUE ] = $object -> $getter ();
2014-10-22 18:11:55 +01:00
} elseif ( $this -> isMethodAccessible ( $reflClass , $getsetter , 0 )) {
$result [ self :: VALUE ] = $object -> $getsetter ();
2013-04-18 15:39:54 +01:00
} elseif ( $reflClass -> hasMethod ( $isser ) && $reflClass -> getMethod ( $isser ) -> isPublic ()) {
2012-07-31 12:15:57 +01:00
$result [ self :: VALUE ] = $object -> $isser ();
2013-04-18 15:39:54 +01:00
} elseif ( $reflClass -> hasMethod ( $hasser ) && $reflClass -> getMethod ( $hasser ) -> isPublic ()) {
2012-07-31 12:15:57 +01:00
$result [ self :: VALUE ] = $object -> $hasser ();
2013-04-18 15:39:54 +01:00
} elseif ( $reflClass -> hasMethod ( '__get' ) && $reflClass -> getMethod ( '__get' ) -> isPublic ()) {
2012-07-31 12:15:57 +01:00
$result [ self :: VALUE ] = $object -> $property ;
2013-04-18 15:39:54 +01:00
} elseif ( $classHasProperty && $reflClass -> getProperty ( $property ) -> isPublic ()) {
2015-03-27 22:06:33 +00:00
$result [ self :: VALUE ] = & $object -> $property ;
2012-07-31 12:15:57 +01:00
$result [ self :: IS_REF ] = true ;
2013-04-18 15:39:54 +01:00
} elseif ( ! $classHasProperty && property_exists ( $object , $property )) {
// Needed to support \stdClass instances. We need to explicitly
// exclude $classHasProperty, otherwise if in the previous clause
// a *protected* property was found on the class, property_exists()
// returns true, consequently the following line will result in a
// fatal error.
2015-03-27 22:06:33 +00:00
$result [ self :: VALUE ] = & $object -> $property ;
2012-07-31 12:15:57 +01:00
$result [ self :: IS_REF ] = true ;
2013-03-04 22:59:45 +00:00
} elseif ( $this -> magicCall && $reflClass -> hasMethod ( '__call' ) && $reflClass -> getMethod ( '__call' ) -> isPublic ()) {
// we call the getter and hope the __call do the job
$result [ self :: VALUE ] = $object -> $getter ();
2013-01-07 08:19:31 +00:00
} else {
2014-10-22 18:11:55 +01:00
$methods = array ( $getter , $getsetter , $isser , $hasser , '__get' );
2013-11-06 08:08:50 +00:00
if ( $this -> magicCall ) {
$methods [] = '__call' ;
}
2013-04-18 15:39:54 +01:00
throw new NoSuchPropertyException ( sprintf (
2013-11-06 09:08:18 +00:00
'Neither the property "%s" nor one of the methods "%s()" ' .
2013-11-06 08:08:50 +00:00
'exist and have public access in class "%s".' ,
2013-04-18 15:39:54 +01:00
$property ,
2013-11-06 09:08:18 +00:00
implode ( '()", "' , $methods ),
2013-04-18 15:39:54 +01:00
$reflClass -> name
));
2013-01-07 08:19:31 +00:00
}
// Objects are always passed around by reference
if ( is_object ( $result [ self :: VALUE ])) {
$result [ self :: IS_REF ] = true ;
}
return $result ;
}
/**
2014-03-30 17:19:15 +01:00
* Sets the value of an index in a given array - accessible value .
2013-01-07 08:19:31 +00:00
*
2013-03-27 22:40:36 +00:00
* @ param \ArrayAccess | array $array An array or \ArrayAccess object to write to
2014-04-16 07:51:57 +01:00
* @ param string | int $index The index to write at
2013-03-27 22:40:36 +00:00
* @ param mixed $value The value to write
2012-07-31 12:15:57 +01:00
*
2014-03-28 16:07:20 +00:00
* @ throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
2012-07-31 12:15:57 +01:00
*/
private function writeIndex ( & $array , $index , $value )
{
if ( ! $array instanceof \ArrayAccess && ! is_array ( $array )) {
2015-02-21 15:36:02 +00:00
throw new NoSuchIndexException ( sprintf ( 'Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \ArrayAccess' , $index , get_class ( $array )));
2012-07-31 12:15:57 +01:00
}
$array [ $index ] = $value ;
}
/**
2014-12-22 16:29:52 +00:00
* Sets the value of a property in the given object .
2012-07-31 12:15:57 +01:00
*
2014-03-30 17:19:15 +01:00
* @ param object $object The object to write to
* @ param string $property The property to write
* @ param mixed $value The value to write
2013-01-07 08:19:31 +00:00
*
2013-04-18 15:39:54 +01:00
* @ throws NoSuchPropertyException If the property does not exist or is not
* public .
2013-01-07 08:19:31 +00:00
*/
2014-03-30 17:19:15 +01:00
private function writeProperty ( & $object , $property , $value )
2013-01-07 08:19:31 +00:00
{
2012-07-31 12:15:57 +01:00
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 ));
}
2013-01-07 08:19:31 +00:00
2012-07-31 12:15:57 +01:00
$reflClass = new \ReflectionClass ( $object );
2014-10-22 18:11:55 +01:00
$camelized = $this -> camelize ( $property );
$singulars = ( array ) StringUtil :: singularify ( $camelized );
2012-07-31 12:15:57 +01:00
if ( is_array ( $value ) || $value instanceof \Traversable ) {
$methods = $this -> findAdderAndRemover ( $reflClass , $singulars );
2014-03-30 17:19:15 +01:00
// Use addXxx() and removeXxx() to write the collection
2012-07-31 12:15:57 +01:00
if ( null !== $methods ) {
2014-03-30 17:19:15 +01:00
$this -> writeCollection ( $object , $property , $value , $methods [ 0 ], $methods [ 1 ]);
2012-07-31 12:15:57 +01:00
return ;
2013-01-07 08:19:31 +00:00
}
2012-07-31 12:15:57 +01:00
}
2013-01-07 08:19:31 +00:00
2014-10-22 18:11:55 +01:00
$setter = 'set' . $camelized ;
$getsetter = lcfirst ( $camelized ); // jQuery style, e.g. read: last(), write: last($item)
2013-04-18 15:39:54 +01:00
$classHasProperty = $reflClass -> hasProperty ( $property );
2013-01-07 08:19:31 +00:00
2014-03-28 16:21:37 +00:00
if ( $this -> isMethodAccessible ( $reflClass , $setter , 1 )) {
2012-07-31 12:15:57 +01:00
$object -> $setter ( $value );
2014-10-22 18:11:55 +01:00
} elseif ( $this -> isMethodAccessible ( $reflClass , $getsetter , 1 )) {
$object -> $getsetter ( $value );
2014-03-28 16:21:37 +00:00
} elseif ( $this -> isMethodAccessible ( $reflClass , '__set' , 2 )) {
2012-07-31 12:15:57 +01:00
$object -> $property = $value ;
2013-04-18 15:39:54 +01:00
} elseif ( $classHasProperty && $reflClass -> getProperty ( $property ) -> isPublic ()) {
2012-07-31 12:15:57 +01:00
$object -> $property = $value ;
2013-04-18 15:39:54 +01:00
} elseif ( ! $classHasProperty && property_exists ( $object , $property )) {
// Needed to support \stdClass instances. We need to explicitly
// exclude $classHasProperty, otherwise if in the previous clause
// a *protected* property was found on the class, property_exists()
// returns true, consequently the following line will result in a
// fatal error.
2012-07-31 12:15:57 +01:00
$object -> $property = $value ;
2014-03-28 16:21:37 +00:00
} elseif ( $this -> magicCall && $this -> isMethodAccessible ( $reflClass , '__call' , 2 )) {
2013-03-04 22:59:45 +00:00
// we call the getter and hope the __call do the job
$object -> $setter ( $value );
2013-01-07 08:19:31 +00:00
} else {
2013-04-18 15:39:54 +01:00
throw new NoSuchPropertyException ( sprintf (
2014-05-09 16:28:08 +01:00
'Neither the property "%s" nor one of the methods %s"%s()", "%s()", ' .
2013-03-04 22:59:45 +00:00
'"__set()" or "__call()" exist and have public access in class "%s".' ,
2013-04-18 15:39:54 +01:00
$property ,
2014-03-30 17:19:15 +01:00
implode ( '' , array_map ( function ( $singular ) {
return '"add' . $singular . '()"/"remove' . $singular . '()", ' ;
}, $singulars )),
2013-04-18 15:39:54 +01:00
$setter ,
2014-10-22 18:11:55 +01:00
$getsetter ,
2013-04-18 15:39:54 +01:00
$reflClass -> name
));
2013-01-07 08:19:31 +00:00
}
}
2014-03-30 17:19:15 +01:00
/**
* 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 )
2014-03-28 16:21:37 +00:00
{
2014-03-30 17:19:15 +01:00
// 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 ;
}
2014-03-28 16:21:37 +00:00
}
2014-03-30 17:19:15 +01:00
foreach ( $itemToRemove as $item ) {
2014-11-21 09:14:57 +00:00
$object -> { $removeMethod }( $item );
2014-03-30 17:19:15 +01:00
}
2014-03-28 16:21:37 +00:00
2014-03-30 17:19:15 +01:00
foreach ( $itemsToAdd as $item ) {
2014-11-21 09:14:57 +00:00
$object -> { $addMethod }( $item );
2014-03-30 17:19:15 +01:00
}
}
2014-03-28 16:21:37 +00:00
2014-03-30 17:19:15 +01:00
/**
* 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
*
2014-12-04 20:26:11 +00:00
* @ return bool Whether the property is writable
2014-03-30 17:19:15 +01:00
*/
private function isPropertyWritable ( $object , $property )
{
if ( ! is_object ( $object )) {
return false ;
2014-03-28 16:21:37 +00:00
}
2014-03-30 17:19:15 +01:00
$reflClass = new \ReflectionClass ( $object );
2014-10-22 18:11:55 +01:00
$camelized = $this -> camelize ( $property );
$setter = 'set' . $camelized ;
$getsetter = lcfirst ( $camelized ); // jQuery style, e.g. read: last(), write: last($item)
2014-03-28 16:21:37 +00:00
$classHasProperty = $reflClass -> hasProperty ( $property );
2014-03-30 17:19:15 +01:00
if ( $this -> isMethodAccessible ( $reflClass , $setter , 1 )
2014-10-22 18:11:55 +01:00
|| $this -> isMethodAccessible ( $reflClass , $getsetter , 1 )
2014-03-28 16:21:37 +00:00
|| $this -> isMethodAccessible ( $reflClass , '__set' , 2 )
|| ( $classHasProperty && $reflClass -> getProperty ( $property ) -> isPublic ())
|| ( ! $classHasProperty && property_exists ( $object , $property ))
2014-03-30 17:19:15 +01:00
|| ( $this -> magicCall && $this -> isMethodAccessible ( $reflClass , '__call' , 2 ))) {
return true ;
}
2014-10-22 18:11:55 +01:00
$singulars = ( array ) StringUtil :: singularify ( $camelized );
2014-03-30 17:19:15 +01:00
// Any of the two methods is required, but not yet known
if ( null !== $this -> findAdderAndRemover ( $reflClass , $singulars )) {
return true ;
}
return false ;
2014-03-28 16:21:37 +00:00
}
2013-01-07 08:19:31 +00:00
/**
* Camelizes a given string .
*
2014-11-30 13:33:44 +00:00
* @ param string $string Some string
2013-01-07 08:19:31 +00:00
*
2013-03-27 22:40:36 +00:00
* @ return string The camelized version of the string
2013-01-07 08:19:31 +00:00
*/
private function camelize ( $string )
{
2014-10-22 18:11:55 +01:00
return strtr ( ucwords ( strtr ( $string , array ( '_' => ' ' ))), array ( ' ' => '' ));
2013-01-07 08:19:31 +00:00
}
/**
* Searches for add and remove methods .
*
* @ param \ReflectionClass $reflClass The reflection class for the given object
2013-03-27 22:40:36 +00:00
* @ param array $singulars The singular form of the property name or null
2013-01-07 08:19:31 +00:00
*
2013-03-27 22:40:36 +00:00
* @ return array | null An array containing the adder and remover when found , null otherwise
2013-01-07 08:19:31 +00:00
*/
private function findAdderAndRemover ( \ReflectionClass $reflClass , array $singulars )
{
foreach ( $singulars as $singular ) {
2013-04-02 10:39:57 +01:00
$addMethod = 'add' . $singular ;
$removeMethod = 'remove' . $singular ;
2013-01-07 08:19:31 +00:00
2014-03-28 14:50:34 +00:00
$addMethodFound = $this -> isMethodAccessible ( $reflClass , $addMethod , 1 );
$removeMethodFound = $this -> isMethodAccessible ( $reflClass , $removeMethod , 1 );
2013-01-07 08:19:31 +00:00
if ( $addMethodFound && $removeMethodFound ) {
return array ( $addMethod , $removeMethod );
}
2014-03-28 16:21:37 +00:00
}
2013-01-07 08:19:31 +00:00
}
/**
2014-04-01 10:04:09 +01:00
* Returns whether a method is public and has the number of required parameters .
2013-01-07 08:19:31 +00:00
*
2014-11-30 13:33:44 +00:00
* @ param \ReflectionClass $class The class of the method
* @ param string $methodName The method name
* @ param int $parameters The number of parameters
2013-01-07 08:19:31 +00:00
*
2014-11-30 13:33:44 +00:00
* @ return bool Whether the method is public and has $parameters
* required parameters
2013-01-07 08:19:31 +00:00
*/
2014-03-28 14:50:34 +00:00
private function isMethodAccessible ( \ReflectionClass $class , $methodName , $parameters )
2013-01-07 08:19:31 +00:00
{
if ( $class -> hasMethod ( $methodName )) {
$method = $class -> getMethod ( $methodName );
2014-04-01 10:04:09 +01:00
if ( $method -> isPublic ()
&& $method -> getNumberOfRequiredParameters () <= $parameters
&& $method -> getNumberOfParameters () >= $parameters ) {
2013-01-07 08:19:31 +00:00
return true ;
}
}
return false ;
}
}