/** * 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); }
/** * @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)); } }
/** * 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); }
/** * 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; }
/** * 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)); }
/** * 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; }
/** * 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; } } }