Example #1
0
 /**
  * finds the method used to append values to the named property
  *
  * @param mixed  $object
  * @param string $property
  */
 private function findAdderMethod($object, $property)
 {
     if (method_exists($object, $method = 'add' . $property)) {
         return $method;
     }
     if (class_exists('Symfony\\Component\\PropertyAccess\\StringUtil') && method_exists('Symfony\\Component\\PropertyAccess\\StringUtil', 'singularify')) {
         foreach ((array) StringUtil::singularify($property) as $singularForm) {
             if (method_exists($object, $method = 'add' . $singularForm)) {
                 return $method;
             }
         }
     } elseif (class_exists('Symfony\\Component\\Form\\Util\\FormUtil') && method_exists('Symfony\\Component\\Form\\Util\\FormUtil', 'singularify')) {
         foreach ((array) FormUtil::singularify($property) as $singularForm) {
             if (method_exists($object, $method = 'add' . $singularForm)) {
                 return $method;
             }
         }
     }
     if (method_exists($object, $method = 'add' . rtrim($property, 's'))) {
         return $method;
     }
     if (substr($property, -3) === 'ies' && method_exists($object, $method = 'add' . substr($key, 0, -3) . 'y')) {
         return $method;
     }
 }
 /**
  * @param  string $plural
  * @return string
  */
 public static function singularify($plural)
 {
     $singular = OrigStringUtil::singularify($plural);
     if (is_array($singular)) {
         return reset($singular);
     }
     return $singular;
 }
 /**
  * @dataProvider singularifyProvider
  */
 public function testSingularify($plural, $singular)
 {
     $single = StringUtil::singularify($plural);
     if (is_string($singular) && is_array($single)) {
         $this->fail("--- Expected\n`string`: " . $singular . "\n+++ Actual\n`array`: " . implode(', ', $single));
     } elseif (is_array($singular) && is_string($single)) {
         $this->fail("--- Expected\n`array`: " . implode(', ', $singular) . "\n+++ Actual\n`string`: " . $single);
     }
     $this->assertEquals($singular, $single);
 }
 /**
  * Guess the method name for the given $prefix and $dataname.
  * $dataName should be the camelcase name of relation.
  * If $singularify is set to true, method name will be singularized.
  * Examples:
  *
  * guessProductValueMethodName('set', 'colors', true)
  *      => 'setColor'
  * guessProductValueMethodName('get', 'smoothFabrics')
  *      => 'getSmoothFabrics'
  *
  * @param string $prefix
  * @param string $dataName
  * @param bool   $singularify
  *
  * @throws \LogicException If it can't singularify a word.
  *
  * @return string
  */
 public static function guess($prefix, $dataName, $singularify = false)
 {
     $name = $dataName;
     if ($singularify) {
         $name = StringUtil::singularify($dataName);
         if (is_array($name)) {
             throw new \LogicException(sprintf('Error while guessing the method name for "%s"', $dataName));
         }
     }
     $name = ucfirst($name);
     return sprintf('%s%s', $prefix, $name);
 }
Example #5
0
 /**
  * @Given /^there are no ([^"]*)$/
  */
 public function thereAreNoResources($type)
 {
     $type = str_replace(' ', '_', StringUtil::singularify($type));
     // Hacky hack for multiple singular forms.
     $type = is_array($type) ? $type[1] : $type;
     // Hacky hack again because we do not retrieve the right singular with the previous hack...
     $type = $type == 'addresse' ? 'address' : $type;
     $manager = $this->getEntityManager();
     foreach ($this->getRepository($type)->findAll() as $resource) {
         $manager->remove($resource);
     }
     $manager->flush();
 }
 /**
  * Get class name of entity
  * @param $namespaceAlias
  * @param $name
  * @return string
  * @throws \UnexpectedValueException
  */
 public function getEntityClassName($namespaceAlias, $name)
 {
     $configuration = $this->entityManager->getConfiguration();
     $namespace = $configuration->getEntityNamespace($namespaceAlias);
     $nameResults = StringUtil::singularify($name);
     if (is_string($nameResults)) {
         return $this->formatClassName($namespace, $nameResults);
     }
     if (is_array($nameResults)) {
         foreach ($nameResults as $nameResult) {
             $className = $this->formatClassName($namespace, $nameResult);
             if (class_exists($className)) {
                 return $className;
             }
         }
     }
     throw new UnexpectedValueException('Entity not found');
 }
 /**
  * finds the method used to append values to the named property
  *
  * @param mixed $object
  * @param string $property
  *
  * @return string|null
  */
 private function findAdderMethod($object, $property)
 {
     if (is_callable([$object, $method = 'add' . $property])) {
         return $method;
     }
     if (class_exists('Symfony\\Component\\PropertyAccess\\StringUtil') && method_exists('Symfony\\Component\\PropertyAccess\\StringUtil', 'singularify')) {
         foreach ((array) StringUtil::singularify($property) as $singularForm) {
             if (is_callable([$object, $method = 'add' . $singularForm])) {
                 return $method;
             }
         }
     }
     if (is_callable([$object, $method = 'add' . rtrim($property, 's')])) {
         return $method;
     }
     if (substr($property, -3) === 'ies' && is_callable([$object, $method = 'add' . substr($property, 0, -3) . 'y'])) {
         return $method;
     }
     return null;
 }
 /**
  * {@inheritdoc}
  *
  * Note: this method is greatly inspired from Symfony Property Accessor {@see \Symfony\Component\PropertyAccessor}
  * and looks for hasser, removers and setters. It works on most cases but it still possible to have some edge cases
  * not handled. Symfony property accessor cannot be used since the goal of this test is to test the returned value,
  * which does no do Symfony PropertyAccessor.
  *
  * @coversNothing
  * @dataProvider fluentDataProvider
  */
 public function testFluentImplementation(array $data = [])
 {
     $reflClass = new \ReflectionClass($this->getEntityClassName());
     $entity = $reflClass->newInstanceArgs();
     $results = [];
     foreach ($data as $property => $value) {
         $camelized = $this->camelize($property);
         $singulars = (array) StringUtil::singularify($camelized);
         $methods = $this->findAdderAndRemover($reflClass, $singulars);
         if (null !== $methods) {
             $this->assertEquals(2, count($methods));
             $results[$methods[0]] = $entity->{$methods}[0]($value);
             $results[$methods[1]] = $entity->{$methods}[1]($value);
         } else {
             $setter = 'set' . $camelized;
             if ($this->isMethodAccessible($reflClass, $setter, 1)) {
                 $results[$setter] = $entity->{$setter}($value);
             }
         }
     }
     foreach ($results as $method => $returnedValue) {
         $this->assertEquals($reflClass->name, get_class($returnedValue), sprintf('Expected %s to return a %s object, got %s instead.', $method, $reflClass->name, get_class($returnedValue)));
     }
 }
    /**
     * Sets the value of the property at the given index in the path
     *
     * @param object|array          $object   The object or array to write to.
     * @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.
     *
     * @throws NoSuchPropertyException       If the property does not exist.
     * @throws PropertyAccessDeniedException If the property cannot be accessed due to
     *                                       access restrictions (private or protected).
     */
    private function writeProperty(&$object, $property, $singular, $value)
    {
        $adderRemoverError = null;

        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));
        }

        $reflClass = new \ReflectionClass($object);
        $plural = $this->camelize($property);

        // 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) {
            $methods = $this->findAdderAndRemover($reflClass, $singulars);

            if (null !== $methods) {
                // 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($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;
            } else {
                $adderRemoverError = ', nor could adders and removers be found based on the ';
                if (null === $singular) {
                    // $adderRemoverError .= 'guessed singulars: '.implode(', ', $singulars).' (provide a singular by suffixing the property path with "|{singular}" to override the guesser)';
                    $adderRemoverError .= 'guessed singulars: '.implode(', ', $singulars);
                } else {
                    $adderRemoverError .= 'passed singular: '.$singular;
                }
            }
        }

        $setter = 'set'.$this->camelize($property);

        if ($reflClass->hasMethod($setter)) {
            if (!$reflClass->getMethod($setter)->isPublic()) {
                throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->name));
            }

            $object->$setter($value);
        } elseif ($reflClass->hasMethod('__set')) {
            // needed to support magic method __set
            $object->$property = $value;
        } elseif ($reflClass->hasProperty($property)) {
            if (!$reflClass->getProperty($property)->isPublic()) {
                throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s"%s. Maybe you should create the method "%s()"?', $property, $reflClass->name, $adderRemoverError, $setter));
            }

            $object->$property = $value;
        } elseif (property_exists($object, $property)) {
            // needed to support \stdClass instances
            $object->$property = $value;
        } else {
            throw new NoSuchPropertyException(sprintf('Neither element "%s" nor method "%s()" exists in class "%s"%s', $property, $setter, $reflClass->name, $adderRemoverError));
        }
    }
Example #10
0
 /**
  * Alias for {@link StringUtil::singularify()}
  *
  * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
  *             {@link StringUtil::singularify()} instead.
  */
 public static function singularify($plural)
 {
     trigger_error('\\Symfony\\Component\\Form\\Util\\FormUtil::singularify() is deprecated since version 2.2 and will be removed in 2.3. Use \\Symfony\\Component\\PropertyAccess\\StringUtil::singularify() in the PropertyAccess component instead.', E_USER_DEPRECATED);
     return StringUtil::singularify($plural);
 }
Example #11
0
 /**
  * Check whether or not a property is writable.
  *
  * Part of the Symfony package. Symfony license applies.
  *
  * @param string           $name
  * @param \ReflectionClass $class
  * @return bool
  */
 private function isPropertyAccessible($name, \ReflectionClass $class)
 {
     $camelized = $this->camelize($name);
     $setter = 'set' . $camelized;
     $getsetter = lcfirst($camelized);
     // jQuery style, e.g. read: last(), write: last($item)
     $classHasProperty = $class->hasProperty($name);
     if ($this->isMethodAccessible($class, $setter, 1) || $this->isMethodAccessible($class, $getsetter, 1) || $this->isMethodAccessible($class, '__set', 2) || $classHasProperty && $class->getProperty($name)->isPublic()) {
         return true;
     }
     $singulars = (array) StringUtil::singularify($camelized);
     // Any of the two methods is required, but not yet known
     if (null !== $this->findAdderAndRemover($class, $singulars)) {
         return true;
     }
     return false;
 }
 /**
  * Guesses how to write the property value.
  *
  * @param string $class
  * @param string $property
  * @param mixed  $value
  *
  * @return array
  */
 private function getWriteAccessInfo($class, $property, $value)
 {
     $key = $class . '::' . $property;
     if (isset($this->writePropertyCache[$key])) {
         $access = $this->writePropertyCache[$key];
     } else {
         $access = array();
         $reflClass = new \ReflectionClass($class);
         $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
         $camelized = $this->camelize($property);
         $singulars = (array) StringUtil::singularify($camelized);
         if (is_array($value) || $value instanceof \Traversable) {
             $methods = $this->findAdderAndRemover($reflClass, $singulars);
             if (null !== $methods) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
                 $access[self::ACCESS_ADDER] = $methods[0];
                 $access[self::ACCESS_REMOVER] = $methods[1];
             }
         }
         if (!isset($access[self::ACCESS_TYPE])) {
             $setter = 'set' . $camelized;
             $getsetter = lcfirst($camelized);
             // jQuery style, e.g. read: last(), write: last($item)
             if ($this->isMethodAccessible($reflClass, $setter, 1)) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
                 $access[self::ACCESS_NAME] = $setter;
             } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
                 $access[self::ACCESS_NAME] = $getsetter;
             } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
                 $access[self::ACCESS_NAME] = $property;
             } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
                 $access[self::ACCESS_NAME] = $property;
             } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
                 // we call the getter and hope the __call do the job
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
                 $access[self::ACCESS_NAME] = $setter;
             } else {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
                 $access[self::ACCESS_NAME] = sprintf('Neither the property "%s" nor one of the methods %s"%s()", "%s()", ' . '"__set()" or "__call()" exist and have public access in class "%s".', $property, implode('', array_map(function ($singular) {
                     return '"add' . $singular . '()"/"remove' . $singular . '()", ';
                 }, $singulars)), $setter, $getsetter, $reflClass->name);
             }
         }
         $this->writePropertyCache[$key] = $access;
     }
     return $access;
 }
 /**
  * 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 bool Whether the property is writable
  */
 private function isPropertyWritable($object, $property)
 {
     if (!is_object($object)) {
         return false;
     }
     $reflClass = new \ReflectionClass($object);
     $camelized = $this->camelize($property);
     $setter = 'set' . $camelized;
     $getsetter = lcfirst($camelized);
     // jQuery style, e.g. read: last(), write: last($item)
     $classHasProperty = $reflClass->hasProperty($property);
     if ($this->isMethodAccessible($reflClass, $setter, 1) || $this->isMethodAccessible($reflClass, $getsetter, 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;
     }
     $singulars = (array) StringUtil::singularify($camelized);
     // Any of the two methods is required, but not yet known
     if (null !== $this->findAdderAndRemover($reflClass, $singulars)) {
         return true;
     }
     return false;
 }
Example #14
0
 /**
  * Guesses how to write the property value.
  *
  * @param string      $object
  * @param string      $property
  * @param string|null $singular
  * @param mixed       $value
  *
  * @return array
  */
 private function getWriteAccessInfo($object, $property, $singular, $value)
 {
     $key = get_class($object) . '::' . $property;
     $guessedAdders = '';
     if (isset($this->writePropertyCache[$key])) {
         $access = $this->writePropertyCache[$key];
     } else {
         $access = array();
         $reflClass = new \ReflectionClass($object);
         $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
         $plural = $this->camelize($property);
         // 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) {
             $methods = $this->findAdderAndRemover($reflClass, $singulars);
             if (null === $methods) {
                 // 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) . '()", ';
             } else {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
                 $access[self::ACCESS_ADDER] = $methods[0];
                 $access[self::ACCESS_REMOVER] = $methods[1];
             }
         }
         if (!isset($access[self::ACCESS_TYPE])) {
             $setter = 'set' . $this->camelize($property);
             $classHasProperty = $reflClass->hasProperty($property);
             if ($reflClass->hasMethod($setter) && $reflClass->getMethod($setter)->isPublic()) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
                 $access[self::ACCESS_NAME] = $setter;
             } elseif ($reflClass->hasMethod('__set') && $reflClass->getMethod('__set')->isPublic()) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
                 $access[self::ACCESS_NAME] = $property;
             } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
                 $access[self::ACCESS_NAME] = $property;
             } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
                 // we call the getter and hope the __call do the job
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
                 $access[self::ACCESS_NAME] = $setter;
             } else {
                 $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
                 $access[self::ACCESS_NAME] = sprintf('Neither the property "%s" nor one of the methods %s"%s()", ' . '"__set()" or "__call()" exist and have public access in class "%s".', $property, $guessedAdders, $setter, $reflClass->name);
             }
         }
         $this->writePropertyCache[$key] = $access;
     }
     return $access;
 }
 /**
  * @dataProvider singularifyProvider
  */
 public function testSingularify($plural, $singular)
 {
     $this->assertEquals($singular, StringUtil::singularify($plural));
 }
Example #16
0
 /**
  * Sets the value of a property in the given object.
  *
  * @param array|object $object   The object or array to write to
  * @param mixed        $property The property or index to write
  * @param mixed        $value    The value to write
  *
  * @throws Exception\NoSuchPropertyException If the property does not exist or is not public.
  *
  * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  */
 protected function writeValue(&$object, $property, $value)
 {
     if ($object instanceof \ArrayAccess || is_array($object)) {
         $object[$property] = $value;
     } elseif (is_object($object)) {
         $reflClass = new \ReflectionClass($object);
         $camelized = $this->camelize($property);
         $singulars = (array) StringUtil::singularify($camelized);
         if (is_array($value) || $value instanceof \Traversable) {
             $methods = $this->findAdderAndRemover($reflClass, $singulars);
             // Use addXxx() and removeXxx() to write the collection
             if (null !== $methods) {
                 $this->writeCollection($object, $property, $value, $methods[0], $methods[1]);
                 return;
             }
         }
         $setter = 'set' . $camelized;
         $classHasProperty = $reflClass->hasProperty($property);
         if ($this->isMethodAccessible($reflClass, $setter, 1)) {
             $object->{$setter}($value);
         } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
             $object->{$property} = $value;
         } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
             $object->{$property} = $value;
         } 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.
             $object->{$property} = $value;
         } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
             // we call the getter and hope the __call do the job
             $object->{$setter}($value);
         } else {
             throw new Exception\NoSuchPropertyException(sprintf('Neither the property "%s" nor one of the methods %s"%s()", ' . '"__set()" or "__call()" exist and have public access in class "%s".', $property, implode('', array_map(function ($singular) {
                 return '"add' . $singular . '()"/"remove' . $singular . '()", ';
             }, $singulars)), $setter, $reflClass->name));
         }
     } else {
         throw new Exception\NoSuchPropertyException(sprintf('Unexpected object type. Expected "array or object", "%s" given.', is_object($object) ? get_class($object) : gettype($object)));
     }
 }
 /**
  * Finds an adder method for the given object.
  * Thanks to the alice library for this code snippet.
  *
  * @param mixed  $obj
  * @param string $key
  *
  * @return string|null
  */
 private function findAdderMethod($obj, $key)
 {
     if (method_exists($obj, $method = 'add' . $key)) {
         return $method;
     }
     foreach ((array) StringUtil::singularify($key) as $singularForm) {
         if (method_exists($obj, $method = 'add' . $singularForm)) {
             return $method;
         }
     }
     if (method_exists($obj, $method = 'add' . rtrim($key, 's'))) {
         return $method;
     }
     if (substr($key, -3) === 'ies' && method_exists($obj, $method = 'add' . substr($key, 0, -3) . 'y')) {
         return $method;
     }
 }
 public function noAdderRemoverData()
 {
     $data = array();
     $car = $this->getMock(__CLASS__ . '_CarNoAdderAndRemover');
     $propertyPath = 'axes';
     $expectedMessage = sprintf('Neither element "axes" nor method "setAxes()" exists in class ' . '"%s", nor could adders and removers be found based on the ' . 'guessed singulars: %s', get_class($car), implode(', ', (array) ($singulars = StringUtil::singularify('Axes'))));
     $data[] = array($car, $propertyPath, $expectedMessage);
     /*
             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('Property "axes" is not public in class "%s", nor could adders and ' . 'removers be found based on the guessed singulars: %s' . '. Maybe you should ' . 'create the method "setAxes()"?', get_class($car), implode(', ', (array) ($singulars = StringUtil::singularify('Axes'))));
     $data[] = array($car, $propertyPath, $expectedMessage);
     return $data;
 }
Example #19
0
 /**
  * Sets the value of the property at the given index in the path.
  *
  * @param object|array $object   The object or array to write to
  * @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
  *
  * @throws NoSuchPropertyException If the property does not exist or is not
  *                                 public.
  */
 private function writeProperty(&$object, $property, $singular, $value)
 {
     $guessedAdders = '';
     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));
     }
     $reflClass = new \ReflectionClass($object);
     $plural = $this->camelize($property);
     // 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) {
         $methods = $this->findAdderAndRemover($reflClass, $singulars);
         if (null !== $methods) {
             // 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($value) ? iterator_to_array($value) : $value;
             $itemToRemove = array();
             $propertyValue =& $this->readProperty($object, $property);
             $previousValue = $propertyValue[self::VALUE];
             // remove reference to avoid modifications
             unset($propertyValue);
             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;
         } 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);
     $classHasProperty = $reflClass->hasProperty($property);
     if ($reflClass->hasMethod($setter) && $reflClass->getMethod($setter)->isPublic()) {
         $object->{$setter}($value);
     } elseif ($reflClass->hasMethod('__set') && $reflClass->getMethod('__set')->isPublic()) {
         $object->{$property} = $value;
     } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
         $object->{$property} = $value;
     } 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.
         $object->{$property} = $value;
     } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
         // we call the getter and hope the __call do the job
         $object->{$setter}($value);
     } else {
         throw new NoSuchPropertyException(sprintf('Neither the property "%s" nor one of the methods %s"%s()", ' . '"__set()" or "__call()" exist and have public access in class "%s".', $property, $guessedAdders, $setter, $reflClass->name));
     }
 }
 /**
  * Searches add and remove methods
  *
  * Simplified version of {@link Symfony\Component\PropertyAccess\PropertyAccessor}.
  */
 private function findAdderAndRemover()
 {
     $reflClass = new \ReflectionClass($this->class);
     $singulars = (array) StringUtil::singularify($this->camelize($this->name));
     foreach ($singulars as $singular) {
         $addMethod = 'add' . $singular;
         $removeMethod = 'remove' . $singular;
         $addMethodFound = $this->isAccessible($reflClass, $addMethod, 1);
         $removeMethodFound = $this->isAccessible($reflClass, $removeMethod, 1);
         if ($addMethodFound && $removeMethodFound) {
             $this->adderRemover = $singular;
             return;
         }
     }
 }