Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
+6
View File
@@ -1,6 +1,12 @@
CHANGELOG
=========
3.1.0
-----
* deprecated the `StringUtil` class, use `Symfony\Component\Inflector\Inflector`
instead
2.7.0
------
@@ -25,25 +25,15 @@ class UnexpectedTypeException extends RuntimeException
* @param PropertyPathInterface $path The property path
* @param int $pathIndex The property path index when the unexpected value was found
*/
public function __construct($value, $path, $pathIndex = null)
public function __construct($value, PropertyPathInterface $path, $pathIndex)
{
if (3 === \func_num_args() && $path instanceof PropertyPathInterface) {
$message = sprintf(
'PropertyAccessor requires a graph of objects or arrays to operate on, '.
'but it found type "%s" while trying to traverse path "%s" at property "%s".',
\gettype($value),
(string) $path,
$path->getElement($pathIndex)
);
} else {
@trigger_error('The '.__CLASS__.' constructor now expects 3 arguments: the invalid property value, the '.__NAMESPACE__.'\PropertyPathInterface object and the current index of the property path.', E_USER_DEPRECATED);
$message = sprintf(
'Expected argument of type "%s", "%s" given',
$path,
\is_object($value) ? \get_class($value) : \gettype($value)
);
}
$message = sprintf(
'PropertyAccessor requires a graph of objects or arrays to operate on, '.
'but it found type "%s" while trying to traverse path "%s" at property "%s".',
\gettype($value),
(string) $path,
$path->getElement($pathIndex)
);
parent::__construct($message);
}
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2004-2018 Fabien Potencier
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
-15
View File
@@ -38,21 +38,6 @@ final class PropertyAccess
return new PropertyAccessorBuilder();
}
/**
* Alias of {@link createPropertyAccessor}.
*
* @return PropertyAccessor
*
* @deprecated since version 2.3, to be removed in 3.0.
* Use {@link createPropertyAccessor()} instead.
*/
public static function getPropertyAccessor()
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0. Use the createPropertyAccessor() method instead.', E_USER_DEPRECATED);
return self::createPropertyAccessor();
}
/**
* This class cannot be instantiated.
*/
+357 -197
View File
@@ -11,6 +11,13 @@
namespace Symfony\Component\PropertyAccess;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Inflector\Inflector;
use Symfony\Component\PropertyAccess\Exception\AccessException;
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
@@ -96,26 +103,53 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
const ACCESS_TYPE_NOT_FOUND = 4;
/**
* @internal
*/
const CACHE_PREFIX_READ = 'r';
/**
* @internal
*/
const CACHE_PREFIX_WRITE = 'w';
/**
* @internal
*/
const CACHE_PREFIX_PROPERTY_PATH = 'p';
/**
* @var bool
*/
private $magicCall;
private $ignoreInvalidIndices;
private $readPropertyCache = array();
private $writePropertyCache = array();
/**
* @var CacheItemPoolInterface
*/
private $cacheItemPool;
private $readPropertyCache = [];
private $writePropertyCache = [];
private $propertyPathCache = [];
private static $previousErrorHandler = false;
private static $errorHandler = array(__CLASS__, 'handleError');
private static $resultProto = array(self::VALUE => null);
private static $errorHandler = [__CLASS__, 'handleError'];
private static $resultProto = [self::VALUE => null];
/**
* Should not be used by application code. Use
* {@link PropertyAccess::createPropertyAccessor()} instead.
*
* @param bool $magicCall
* @param bool $throwExceptionOnInvalidIndex
* @param bool $magicCall
* @param bool $throwExceptionOnInvalidIndex
* @param CacheItemPoolInterface $cacheItemPool
*/
public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false)
public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null)
{
$this->magicCall = $magicCall;
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
}
/**
@@ -123,13 +157,11 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
public function getValue($objectOrArray, $propertyPath)
{
if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
}
$propertyPath = $this->getPropertyPath($propertyPath);
$zval = array(
$zval = [
self::VALUE => $objectOrArray,
);
];
$propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
return $propertyValues[\count($propertyValues) - 1][self::VALUE];
@@ -140,14 +172,12 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
public function setValue(&$objectOrArray, $propertyPath, $value)
{
if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
}
$propertyPath = $this->getPropertyPath($propertyPath);
$zval = array(
$zval = [
self::VALUE => $objectOrArray,
self::REF => &$objectOrArray,
);
];
$propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
$overwrite = true;
@@ -199,49 +229,54 @@ class PropertyAccessor implements PropertyAccessorInterface
$value = $zval[self::VALUE];
}
} catch (\TypeError $e) {
try {
self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0);
} catch (InvalidArgumentException $e) {
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $e);
if (\PHP_VERSION_ID < 70000 && false !== self::$previousErrorHandler) {
restore_error_handler();
self::$previousErrorHandler = false;
}
if (isset($e)) {
// It wasn't thrown in this class so rethrow it
throw $e;
} finally {
if (\PHP_VERSION_ID < 70000 && false !== self::$previousErrorHandler) {
restore_error_handler();
self::$previousErrorHandler = false;
}
}
}
/**
* @internal
*/
public static function handleError($type, $message, $file, $line, $context)
public static function handleError($type, $message, $file, $line, $context = [])
{
if (E_RECOVERABLE_ERROR === $type) {
if (\E_RECOVERABLE_ERROR === $type) {
self::throwInvalidArgumentException($message, debug_backtrace(false), 1);
}
return null !== self::$previousErrorHandler && false !== \call_user_func(self::$previousErrorHandler, $type, $message, $file, $line, $context);
}
private static function throwInvalidArgumentException($message, $trace, $i)
private static function throwInvalidArgumentException($message, $trace, $i, $previous = null)
{
// the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method)
if (0 !== strpos($message, 'Argument ')) {
if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) {
return;
}
if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && array_key_exists(0, $trace[$i]['args'])) {
if (\PHP_VERSION_ID < 80000) {
if (0 !== strpos($message, 'Argument ')) {
return;
}
$pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface '));
$pos += \strlen($delim);
$type = $trace[$i]['args'][0];
$type = \is_object($type) ? \get_class($type) : \gettype($type);
$j = strpos($message, ',', $pos);
$type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2);
$message = substr($message, $pos, $j - $pos);
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given', substr($message, $pos, strpos($message, ',', $pos) - $pos), $type));
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given.', $message, 'NULL' === $type ? 'null' : $type), 0, $previous);
}
if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) {
list(, $expectedType, $actualType) = $matches;
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given.', $expectedType, 'NULL' === $actualType ? 'null' : $actualType), 0, $previous);
}
}
@@ -255,9 +290,9 @@ class PropertyAccessor implements PropertyAccessorInterface
}
try {
$zval = array(
$zval = [
self::VALUE => $objectOrArray,
);
];
$this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
return true;
@@ -273,14 +308,12 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
public function isWritable($objectOrArray, $propertyPath)
{
if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
}
$propertyPath = $this->getPropertyPath($propertyPath);
try {
$zval = array(
$zval = [
self::VALUE => $objectOrArray,
);
];
$propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) {
@@ -330,7 +363,7 @@ class PropertyAccessor implements PropertyAccessorInterface
}
// Add the root object to the list
$propertyValues = array($zval);
$propertyValues = [$zval];
for ($i = 0; $i < $lastIndex; ++$i) {
$property = $propertyPath->getElement($i);
@@ -339,7 +372,7 @@ class PropertyAccessor implements PropertyAccessorInterface
if ($isIndex) {
// Create missing nested arrays on demand
if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) ||
(\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !array_key_exists($property, $zval[self::VALUE]))
(\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property, $zval[self::VALUE]))
) {
if (!$ignoreInvalidIndices) {
if (!\is_array($zval[self::VALUE])) {
@@ -355,10 +388,10 @@ class PropertyAccessor implements PropertyAccessorInterface
if ($i + 1 < $propertyPath->getLength()) {
if (isset($zval[self::REF])) {
$zval[self::VALUE][$property] = array();
$zval[self::VALUE][$property] = [];
$zval[self::REF] = $zval[self::VALUE];
} else {
$zval[self::VALUE] = array($property => array());
$zval[self::VALUE] = [$property => []];
}
}
}
@@ -441,30 +474,57 @@ class PropertyAccessor implements PropertyAccessorInterface
$object = $zval[self::VALUE];
$access = $this->getReadAccessInfo(\get_class($object), $property);
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
try {
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
try {
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} catch (\TypeError $e) {
list($trace) = $e->getTrace();
if ($access[self::ACCESS_REF] && isset($zval[self::REF])) {
$result[self::REF] = &$object->{$access[self::ACCESS_NAME]};
}
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
// Needed to support \stdClass instances. We need to explicitly
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
// a *protected* property was found on the class, property_exists()
// returns true, consequently the following line will result in a
// fatal error.
// handle uninitialized properties in PHP >= 7
if (__FILE__ === $trace['file']
&& $access[self::ACCESS_NAME] === $trace['function']
&& $object instanceof $trace['class']
&& preg_match((sprintf('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/')), $e->getMessage(), $matches)
) {
throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', false === strpos(\get_class($object), "@anonymous\0") ? \get_class($object) : (get_parent_class($object) ?: 'class').'@anonymous', $access[self::ACCESS_NAME], $matches[1]), 0, $e);
}
$result[self::VALUE] = $object->$property;
if (isset($zval[self::REF])) {
$result[self::REF] = &$object->$property;
throw $e;
}
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
if ($access[self::ACCESS_REF] && isset($zval[self::REF])) {
$result[self::REF] = &$object->{$access[self::ACCESS_NAME]};
}
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
// Needed to support \stdClass instances. We need to explicitly
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
// a *protected* property was found on the class, property_exists()
// returns true, consequently the following line will result in a
// fatal error.
$result[self::VALUE] = $object->$property;
if (isset($zval[self::REF])) {
$result[self::REF] = &$object->$property;
}
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
// we call the getter and hope the __call do the job
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} else {
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
}
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
// we call the getter and hope the __call do the job
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} else {
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
} catch (\Error $e) {
// handle uninitialized properties in PHP >= 7.4
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
$r = new \ReflectionProperty($matches[1], $matches[2]);
$type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $r->getDeclaringClass()->getName(), $r->getName(), $type), 0, $e);
}
throw $e;
}
// Objects are always passed around by reference
@@ -485,65 +545,74 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
private function getReadAccessInfo($class, $property)
{
$key = $class.'::'.$property;
$key = str_replace('\\', '.', $class).'..'.$property;
if (isset($this->readPropertyCache[$key])) {
$access = $this->readPropertyCache[$key];
} else {
$access = array();
$reflClass = new \ReflectionClass($class);
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
$camelProp = $this->camelize($property);
$getter = 'get'.$camelProp;
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
$isser = 'is'.$camelProp;
$hasser = 'has'.$camelProp;
if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $getter;
} elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $getsetter;
} elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $isser;
} elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $hasser;
} elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
$access[self::ACCESS_NAME] = $property;
$access[self::ACCESS_REF] = false;
} elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
$access[self::ACCESS_NAME] = $property;
$access[self::ACCESS_REF] = true;
} 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] = $getter;
} else {
$methods = array($getter, $getsetter, $isser, $hasser, '__get');
if ($this->magicCall) {
$methods[] = '__call';
}
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
$access[self::ACCESS_NAME] = sprintf(
'Neither the property "%s" nor one of the methods "%s()" '.
'exist and have public access in class "%s".',
$property,
implode('()", "', $methods),
$reflClass->name
);
}
$this->readPropertyCache[$key] = $access;
return $this->readPropertyCache[$key];
}
return $access;
if ($this->cacheItemPool) {
$item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.rawurlencode($key));
if ($item->isHit()) {
return $this->readPropertyCache[$key] = $item->get();
}
}
$access = [];
$reflClass = new \ReflectionClass($class);
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
$camelProp = $this->camelize($property);
$getter = 'get'.$camelProp;
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
$isser = 'is'.$camelProp;
$hasser = 'has'.$camelProp;
if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $getter;
} elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $getsetter;
} elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $isser;
} elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $hasser;
} elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
$access[self::ACCESS_NAME] = $property;
$access[self::ACCESS_REF] = false;
} elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
$access[self::ACCESS_NAME] = $property;
$access[self::ACCESS_REF] = true;
} 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] = $getter;
} else {
$methods = [$getter, $getsetter, $isser, $hasser, '__get'];
if ($this->magicCall) {
$methods[] = '__call';
}
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
$access[self::ACCESS_NAME] = sprintf(
'Neither the property "%s" nor one of the methods "%s()" '.
'exist and have public access in class "%s".',
$property,
implode('()", "', $methods),
$reflClass->name
);
}
if (isset($item)) {
$this->cacheItemPool->save($item->set($access));
}
return $this->readPropertyCache[$key] = $access;
}
/**
@@ -558,7 +627,7 @@ class PropertyAccessor implements PropertyAccessorInterface
private function writeIndex($zval, $index, $value)
{
if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
throw new NoSuchIndexException(sprintf('Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \ArrayAccess', $index, \get_class($zval[self::VALUE])));
throw new NoSuchIndexException(sprintf('Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, \get_class($zval[self::VALUE])));
}
$zval[self::REF][$index] = $value;
@@ -598,6 +667,8 @@ class PropertyAccessor implements PropertyAccessorInterface
$object->$property = $value;
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
$object->{$access[self::ACCESS_NAME]}($value);
} elseif (self::ACCESS_TYPE_NOT_FOUND === $access[self::ACCESS_TYPE]) {
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, \get_class($object)));
} else {
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
}
@@ -653,79 +724,89 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
private function getWriteAccessInfo($class, $property, $value)
{
$key = $class.'::'.$property;
$useAdderAndRemover = \is_array($value) || $value instanceof \Traversable;
$key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover;
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;
} elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
$access[self::ACCESS_NAME] = sprintf(
'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
'the new value must be an array or an instance of \Traversable, '.
'"%s" given.',
$property,
$reflClass->name,
implode('()", "', $methods),
\is_object($value) ? \get_class($value) : \gettype($value)
);
} 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 $this->writePropertyCache[$key];
}
return $access;
if ($this->cacheItemPool) {
$item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.rawurlencode($key));
if ($item->isHit()) {
return $this->writePropertyCache[$key] = $item->get();
}
}
$access = [];
$reflClass = new \ReflectionClass($class);
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
$camelized = $this->camelize($property);
$singulars = (array) Inflector::singularize($camelized);
if ($useAdderAndRemover) {
$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;
} elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
$access[self::ACCESS_NAME] = sprintf(
'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
'the new value must be an array or an instance of \Traversable, '.
'"%s" given.',
$property,
$reflClass->name,
implode('()", "', $methods),
\is_object($value) ? \get_class($value) : \gettype($value)
);
} 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
);
}
}
if (isset($item)) {
$this->cacheItemPool->save($item->set($access));
}
return $this->writePropertyCache[$key] = $access;
}
/**
@@ -742,7 +823,19 @@ class PropertyAccessor implements PropertyAccessorInterface
return false;
}
$access = $this->getWriteAccessInfo(\get_class($object), $property, array());
$access = $this->getWriteAccessInfo(\get_class($object), $property, []);
$isWritable = self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]
|| (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property))
|| self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE];
if ($isWritable) {
return true;
}
$access = $this->getWriteAccessInfo(\get_class($object), $property, '');
return self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]
@@ -781,9 +874,11 @@ class PropertyAccessor implements PropertyAccessorInterface
$removeMethodFound = $this->isMethodAccessible($reflClass, $removeMethod, 1);
if ($addMethodFound && $removeMethodFound) {
return array($addMethod, $removeMethod);
return [$addMethod, $removeMethod];
}
}
return null;
}
/**
@@ -809,4 +904,69 @@ class PropertyAccessor implements PropertyAccessorInterface
return false;
}
/**
* Gets a PropertyPath instance and caches it.
*
* @param string|PropertyPath $propertyPath
*
* @return PropertyPath
*/
private function getPropertyPath($propertyPath)
{
if ($propertyPath instanceof PropertyPathInterface) {
// Don't call the copy constructor has it is not needed here
return $propertyPath;
}
if (isset($this->propertyPathCache[$propertyPath])) {
return $this->propertyPathCache[$propertyPath];
}
if ($this->cacheItemPool) {
$item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH.rawurlencode($propertyPath));
if ($item->isHit()) {
return $this->propertyPathCache[$propertyPath] = $item->get();
}
}
$propertyPathInstance = new PropertyPath($propertyPath);
if (isset($item)) {
$item->set($propertyPathInstance);
$this->cacheItemPool->save($item);
}
return $this->propertyPathCache[$propertyPath] = $propertyPathInstance;
}
/**
* Creates the APCu adapter if applicable.
*
* @param string $namespace
* @param int $defaultLifetime
* @param string $version
*
* @return AdapterInterface
*
* @throws RuntimeException When the Cache Component isn't available
*/
public static function createCache($namespace, $defaultLifetime, $version, LoggerInterface $logger = null)
{
if (!class_exists('Symfony\Component\Cache\Adapter\ApcuAdapter')) {
throw new \RuntimeException(sprintf('The Symfony Cache component must be installed to use "%s()".', __METHOD__));
}
if (!ApcuAdapter::isSupported()) {
return new NullAdapter();
}
$apcu = new ApcuAdapter($namespace, $defaultLifetime / 5, $version);
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
$apcu->setLogger(new NullLogger());
} elseif (null !== $logger) {
$apcu->setLogger($logger);
}
return $apcu;
}
}
+30 -1
View File
@@ -11,6 +11,8 @@
namespace Symfony\Component\PropertyAccess;
use Psr\Cache\CacheItemPoolInterface;
/**
* A configurable builder to create a PropertyAccessor.
*
@@ -21,6 +23,11 @@ class PropertyAccessorBuilder
private $magicCall = false;
private $throwExceptionOnInvalidIndex = false;
/**
* @var CacheItemPoolInterface|null
*/
private $cacheItemPool;
/**
* Enables the use of "__call" by the PropertyAccessor.
*
@@ -90,6 +97,28 @@ class PropertyAccessorBuilder
return $this->throwExceptionOnInvalidIndex;
}
/**
* Sets a cache system.
*
* @return PropertyAccessorBuilder The builder object
*/
public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool = null)
{
$this->cacheItemPool = $cacheItemPool;
return $this;
}
/**
* Gets the used cache system.
*
* @return CacheItemPoolInterface|null
*/
public function getCacheItemPool()
{
return $this->cacheItemPool;
}
/**
* Builds and returns a new PropertyAccessor object.
*
@@ -97,6 +126,6 @@ class PropertyAccessorBuilder
*/
public function getPropertyAccessor()
{
return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex);
return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool);
}
}
@@ -58,7 +58,7 @@ interface PropertyAccessorInterface
*
* $propertyAccessor = PropertyAccess::createPropertyAccessor();
*
* echo $propertyAccessor->getValue($object, 'child.name);
* echo $propertyAccessor->getValue($object, 'child.name');
* // equals echo $object->getChild()->getName();
*
* This method first tries to find a public getter for each property in the
+8 -8
View File
@@ -32,7 +32,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
*
* @var array
*/
private $elements = array();
private $elements = [];
/**
* The number of elements in the property path.
@@ -47,7 +47,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
*
* @var array
*/
private $isIndex = array();
private $isIndex = [];
/**
* String representation of the path.
@@ -77,7 +77,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
return;
}
if (!\is_string($propertyPath)) {
throw new InvalidArgumentException(sprintf('The property path constructor needs a string or an instance of "Symfony\Component\PropertyAccess\PropertyPath". Got: "%s"', \is_object($propertyPath) ? \get_class($propertyPath) : \gettype($propertyPath)));
throw new InvalidArgumentException(sprintf('The property path constructor needs a string or an instance of "Symfony\Component\PropertyAccess\PropertyPath". Got: "%s".', \is_object($propertyPath) ? \get_class($propertyPath) : \gettype($propertyPath)));
}
if ('' === $propertyPath) {
@@ -108,7 +108,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
}
if ('' !== $remaining) {
throw new InvalidPropertyPathException(sprintf('Could not parse property path "%s". Unexpected token "%s" at position %d', $propertyPath, $remaining[0], $position));
throw new InvalidPropertyPathException(sprintf('Could not parse property path "%s". Unexpected token "%s" at position %d.', $propertyPath, $remaining[0], $position));
}
$this->length = \count($this->elements);
@@ -136,7 +136,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
public function getParent()
{
if ($this->length <= 1) {
return;
return null;
}
$parent = clone $this;
@@ -173,7 +173,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
public function getElement($index)
{
if (!isset($this->elements[$index])) {
throw new OutOfBoundsException(sprintf('The index %s is not within the property path', $index));
throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index));
}
return $this->elements[$index];
@@ -185,7 +185,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
public function isProperty($index)
{
if (!isset($this->isIndex[$index])) {
throw new OutOfBoundsException(sprintf('The index %s is not within the property path', $index));
throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index));
}
return !$this->isIndex[$index];
@@ -197,7 +197,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
public function isIndex($index)
{
if (!isset($this->isIndex[$index])) {
throw new OutOfBoundsException(sprintf('The index %s is not within the property path', $index));
throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index));
}
return $this->isIndex[$index];
+8 -9
View File
@@ -18,8 +18,8 @@ use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException;
*/
class PropertyPathBuilder
{
private $elements = array();
private $isIndex = array();
private $elements = [];
private $isIndex = [];
/**
* Creates a new property path builder.
@@ -94,7 +94,7 @@ class PropertyPathBuilder
public function remove($offset, $length = 1)
{
if (!isset($this->elements[$offset])) {
throw new OutOfBoundsException(sprintf('The offset %s is not within the property path', $offset));
throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset));
}
$this->resize($offset, $length, 0);
@@ -149,7 +149,7 @@ class PropertyPathBuilder
public function replaceByIndex($offset, $name = null)
{
if (!isset($this->elements[$offset])) {
throw new OutOfBoundsException(sprintf('The offset %s is not within the property path', $offset));
throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset));
}
if (null !== $name) {
@@ -170,7 +170,7 @@ class PropertyPathBuilder
public function replaceByProperty($offset, $name = null)
{
if (!isset($this->elements[$offset])) {
throw new OutOfBoundsException(sprintf('The offset %s is not within the property path', $offset));
throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset));
}
if (null !== $name) {
@@ -193,7 +193,7 @@ class PropertyPathBuilder
/**
* Returns the current property path.
*
* @return PropertyPathInterface The constructed property path
* @return PropertyPathInterface|null The constructed property path
*/
public function getPropertyPath()
{
@@ -257,9 +257,8 @@ class PropertyPathBuilder
}
// All remaining elements should be removed
for (; $i < $length; ++$i) {
unset($this->elements[$i], $this->isIndex[$i]);
}
$this->elements = \array_slice($this->elements, 0, $i);
$this->isIndex = \array_slice($this->isIndex, 0, $i);
} else {
$diff = $insertionLength - $cutLength;
+1 -1
View File
@@ -40,7 +40,7 @@ interface PropertyPathInterface extends \Traversable
*
* If this property path only contains one item, null is returned.
*
* @return PropertyPath The parent path or null
* @return self|null The parent path or null
*/
public function getParent();
+1 -1
View File
@@ -7,7 +7,7 @@ object or array using a simple string notation.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/property_access/index.html)
* [Documentation](https://symfony.com/doc/current/components/property_access.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
+8 -183
View File
@@ -11,130 +11,17 @@
namespace Symfony\Component\PropertyAccess;
use Symfony\Component\Inflector\Inflector;
/**
* Creates singulars from plurals.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.1, to be removed in 4.0. Use {@see Symfony\Component\Inflector\Inflector} instead.
*/
class StringUtil
{
/**
* Map english plural to singular suffixes.
*
* @see http://english-zone.com/spelling/plurals.html
*/
private static $pluralMap = array(
// First entry: plural suffix, reversed
// Second entry: length of plural suffix
// Third entry: Whether the suffix may succeed a vocal
// Fourth entry: Whether the suffix may succeed a consonant
// Fifth entry: singular suffix, normal
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
array('a', 1, true, true, array('on', 'um')),
// nebulae (nebula)
array('ea', 2, true, true, 'a'),
// services (service)
array('secivres', 8, true, true, 'service'),
// mice (mouse), lice (louse)
array('eci', 3, false, true, 'ouse'),
// geese (goose)
array('esee', 4, false, true, 'oose'),
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
array('i', 1, true, true, 'us'),
// men (man), women (woman)
array('nem', 3, true, true, 'man'),
// children (child)
array('nerdlihc', 8, true, true, 'child'),
// oxen (ox)
array('nexo', 4, false, false, 'ox'),
// indices (index), appendices (appendix), prices (price)
array('seci', 4, false, true, array('ex', 'ix', 'ice')),
// selfies (selfie)
array('seifles', 7, true, true, 'selfie'),
// movies (movie)
array('seivom', 6, true, true, 'movie'),
// feet (foot)
array('teef', 4, true, true, 'foot'),
// geese (goose)
array('eseeg', 5, true, true, 'goose'),
// teeth (tooth)
array('hteet', 5, true, true, 'tooth'),
// news (news)
array('swen', 4, true, true, 'news'),
// series (series)
array('seires', 6, true, true, 'series'),
// babies (baby)
array('sei', 3, false, true, 'y'),
// accesses (access), addresses (address), kisses (kiss)
array('sess', 4, true, false, 'ss'),
// analyses (analysis), ellipses (ellipsis), funguses (fungus),
// neuroses (neurosis), theses (thesis), emphases (emphasis),
// oases (oasis), crises (crisis), houses (house), bases (base),
// atlases (atlas)
array('ses', 3, true, true, array('s', 'se', 'sis')),
// objectives (objective), alternative (alternatives)
array('sevit', 5, true, true, 'tive'),
// drives (drive)
array('sevird', 6, false, true, 'drive'),
// lives (life), wives (wife)
array('sevi', 4, false, true, 'ife'),
// moves (move)
array('sevom', 5, true, true, 'move'),
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
array('sev', 3, true, true, array('f', 've', 'ff')),
// axes (axis), axes (ax), axes (axe)
array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
// indexes (index), matrixes (matrix)
array('sex', 3, true, false, 'x'),
// quizzes (quiz)
array('sezz', 4, true, false, 'z'),
// bureaus (bureau)
array('suae', 4, false, true, 'eau'),
// roses (rose), garages (garage), cassettes (cassette),
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
// shoes (shoe)
array('se', 2, true, true, array('', 'e')),
// tags (tag)
array('s', 1, true, true, ''),
// chateaux (chateau)
array('xuae', 4, false, true, 'eau'),
// people (person)
array('elpoep', 6, true, true, 'person'),
);
/**
* This class should not be instantiated.
*/
@@ -152,75 +39,13 @@ class StringUtil
*
* @return string|array The singular form or an array of possible singular
* forms
*
* @deprecated since version 3.1, to be removed in 4.0. Use {@see Symfony\Component\Inflector\Inflector::singularize} instead.
*/
public static function singularify($plural)
{
$pluralRev = strrev($plural);
$lowerPluralRev = strtolower($pluralRev);
$pluralLength = \strlen($lowerPluralRev);
@trigger_error('StringUtil::singularify() is deprecated since Symfony 3.1 and will be removed in 4.0. Use Symfony\Component\Inflector\Inflector::singularize instead.', \E_USER_DEPRECATED);
// The outer loop iterates over the entries of the plural table
// The inner loop $j iterates over the characters of the plural suffix
// in the plural table to compare them with the characters of the actual
// given plural suffix
foreach (self::$pluralMap as $map) {
$suffix = $map[0];
$suffixLength = $map[1];
$j = 0;
// Compare characters in the plural table and of the suffix of the
// given plural one by one
while ($suffix[$j] === $lowerPluralRev[$j]) {
// Let $j point to the next character
++$j;
// Successfully compared the last character
// Add an entry with the singular suffix to the singular array
if ($j === $suffixLength) {
// Is there any character preceding the suffix in the plural string?
if ($j < $pluralLength) {
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
if (!$map[2] && $nextIsVocal) {
// suffix may not succeed a vocal but next char is one
break;
}
if (!$map[3] && !$nextIsVocal) {
// suffix may not succeed a consonant but next char is one
break;
}
}
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
$newSuffix = $map[4];
// Check whether the first character in the plural suffix
// is uppercased. If yes, uppercase the first character in
// the singular suffix too
$firstUpper = ctype_upper($pluralRev[$j - 1]);
if (\is_array($newSuffix)) {
$singulars = array();
foreach ($newSuffix as $newSuffixEntry) {
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
}
return $singulars;
}
return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
}
// Suffix is longer than word
if ($j === $pluralLength) {
break;
}
}
}
// Assume that plural and singular is identical
return $plural;
return Inflector::singularize($plural);
}
}
@@ -21,12 +21,12 @@ class NonTraversableArrayObject implements \ArrayAccess, \Countable, \Serializab
public function __construct(array $array = null)
{
$this->array = $array ?: array();
$this->array = $array ?: [];
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->array);
return \array_key_exists($offset, $this->array);
}
public function offsetGet($offset)
@@ -26,6 +26,7 @@ class TestClass
private $publicIsAccessor;
private $publicHasAccessor;
private $publicGetter;
private $date;
public function __construct($value)
{
@@ -173,4 +174,14 @@ class TestClass
{
return $this->publicGetter;
}
public function setDate(\DateTimeInterface $date)
{
$this->date = $date;
}
public function getDate()
{
return $this->date;
}
}
@@ -33,5 +33,7 @@ class TestClassMagicCall
if ('setMagicCallProperty' === $method) {
$this->magicCallProperty = reset($args);
}
return null;
}
}
@@ -21,12 +21,12 @@ class TraversableArrayObject implements \ArrayAccess, \IteratorAggregate, \Count
public function __construct(array $array = null)
{
$this->array = $array ?: array();
$this->array = $array ?: [];
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->array);
return \array_key_exists($offset, $this->array);
}
public function offsetGet($offset)
@@ -31,10 +31,10 @@ abstract class PropertyAccessorArrayAccessTest extends TestCase
public function getValidPropertyPaths()
{
return array(
array($this->getContainer(array('firstName' => 'Bernhard')), '[firstName]', 'Bernhard'),
array($this->getContainer(array('person' => $this->getContainer(array('firstName' => 'Bernhard')))), '[person][firstName]', 'Bernhard'),
);
return [
[$this->getContainer(['firstName' => 'Bernhard']), '[firstName]', 'Bernhard'],
[$this->getContainer(['person' => $this->getContainer(['firstName' => 'Bernhard'])]), '[person][firstName]', 'Bernhard'],
];
}
/**
@@ -45,16 +45,14 @@ abstract class PropertyAccessorArrayAccessTest extends TestCase
$this->assertSame($value, $this->propertyAccessor->getValue($collection, $path));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
*/
public function testGetValueFailsIfNoSuchIndex()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
$this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->enableExceptionOnInvalidIndex()
->getPropertyAccessor();
$object = $this->getContainer(array('firstName' => 'Bernhard'));
$object = $this->getContainer(['firstName' => 'Bernhard']);
$this->propertyAccessor->getValue($object, '[lastName]');
}
@@ -12,6 +12,8 @@
namespace Symfony\Component\PropertyAccess\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
class PropertyAccessorBuilderTest extends TestCase
@@ -50,7 +52,15 @@ class PropertyAccessorBuilderTest extends TestCase
public function testGetPropertyAccessor()
{
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->getPropertyAccessor());
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->enableMagicCall()->getPropertyAccessor());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->enableMagicCall()->getPropertyAccessor());
}
public function testUseCache()
{
$cacheItemPool = new ArrayAdapter();
$this->builder->setCacheItemPool($cacheItemPool);
$this->assertEquals($cacheItemPool, $this->builder->getCacheItemPool());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
}
}
@@ -102,9 +102,9 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces
{
public function testSetValueCallsAdderAndRemoverForCollections()
{
$axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth', 4 => 'fifth'));
$axesMerged = $this->getContainer(array(1 => 'first', 2 => 'second', 3 => 'third'));
$axesAfter = $this->getContainer(array(1 => 'second', 5 => 'first', 6 => 'third'));
$axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth', 4 => 'fifth']);
$axesMerged = $this->getContainer([1 => 'first', 2 => 'second', 3 => 'third']);
$axesAfter = $this->getContainer([1 => 'second', 5 => 'first', 6 => 'third']);
$axesMergedCopy = \is_object($axesMerged) ? clone $axesMerged : $axesMerged;
// Don't use a mock in order to test whether the collections are
@@ -123,77 +123,73 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces
{
$car = $this->getMockBuilder(__CLASS__.'_CompositeCar')->getMock();
$structure = $this->getMockBuilder(__CLASS__.'_CarStructure')->getMock();
$axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth'));
$axesAfter = $this->getContainer(array(0 => 'first', 1 => 'second', 2 => 'third'));
$axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth']);
$axesAfter = $this->getContainer([0 => 'first', 1 => 'second', 2 => 'third']);
$car->expects($this->any())
->method('getStructure')
->will($this->returnValue($structure));
->willReturn($structure);
$structure->expects($this->at(0))
$structure->expects($this->once())
->method('getAxes')
->will($this->returnValue($axesBefore));
$structure->expects($this->at(1))
->willReturn($axesBefore);
$structure->expects($this->once())
->method('removeAxis')
->with('fourth');
$structure->expects($this->at(2))
$structure->expects($this->exactly(2))
->method('addAxis')
->with('first');
$structure->expects($this->at(3))
->method('addAxis')
->with('third');
->withConsecutive(
['first'],
['third']
);
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
* @expectedExceptionMessage Neither the property "axes" nor one of the methods "addAx()"/"removeAx()", "addAxe()"/"removeAxe()", "addAxis()"/"removeAxis()", "setAxes()", "axes()", "__set()" or "__call()" exist and have public access in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover
*/
public function testSetValueFailsIfNoAdderNorRemoverFound()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->expectExceptionMessageMatches('/Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover_[^"]*"./');
$car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock();
$axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth'));
$axesAfter = $this->getContainer(array(0 => 'first', 1 => 'second', 2 => 'third'));
$axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth']);
$axesAfter = $this->getContainer([0 => 'first', 1 => 'second', 2 => 'third']);
$car->expects($this->any())
->method('getAxes')
->will($this->returnValue($axesBefore));
->willReturn($axesBefore);
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
}
public function testIsWritableReturnsTrueIfAdderAndRemoverExists()
{
$car = $this->getMockBuilder(__CLASS__.'_Car')->getMock();
$car = new PropertyAccessorCollectionTest_Car();
$this->assertTrue($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfOnlyAdderExists()
{
$car = $this->getMockBuilder(__CLASS__.'_CarOnlyAdder')->getMock();
$car = new PropertyAccessorCollectionTest_CarOnlyAdder();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfOnlyRemoverExists()
{
$car = $this->getMockBuilder(__CLASS__.'_CarOnlyRemover')->getMock();
$car = new PropertyAccessorCollectionTest_CarOnlyRemover();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists()
{
$car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock();
$car = new PropertyAccessorCollectionTest_CarNoAdderAndRemover();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
* expectedExceptionMessageRegExp /The property "axes" in class "Mock_PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis()", "removeAxis()" but the new value must be an array or an instance of \Traversable, "string" given./
*/
public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable()
{
$car = $this->getMockBuilder(__CLASS__.'_Car')->getMock();
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->expectExceptionMessage('Could not determine access type for property "axes" in class "Symfony\Component\PropertyAccess\Tests\PropertyAccessorCollectionTest_Car".');
$car = new PropertyAccessorCollectionTest_Car();
$this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable');
}
+324 -125
View File
@@ -12,6 +12,7 @@
namespace Symfony\Component\PropertyAccess\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
@@ -20,8 +21,12 @@ use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassTypeErrorInsideCall;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestSingularAndPluralProps;
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty;
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty;
class PropertyAccessorTest extends TestCase
{
@@ -37,47 +42,47 @@ class PropertyAccessorTest extends TestCase
public function getPathsWithUnexpectedType()
{
return array(
array('', 'foobar'),
array('foo', 'foobar'),
array(null, 'foobar'),
array(123, 'foobar'),
array((object) array('prop' => null), 'prop.foobar'),
array((object) array('prop' => (object) array('subProp' => null)), 'prop.subProp.foobar'),
array(array('index' => null), '[index][foobar]'),
array(array('index' => array('subIndex' => null)), '[index][subIndex][foobar]'),
);
return [
['', 'foobar'],
['foo', 'foobar'],
[null, 'foobar'],
[123, 'foobar'],
[(object) ['prop' => null], 'prop.foobar'],
[(object) ['prop' => (object) ['subProp' => null]], 'prop.subProp.foobar'],
[['index' => null], '[index][foobar]'],
[['index' => ['subIndex' => null]], '[index][subIndex][foobar]'],
];
}
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'),
return [
[(object) ['firstName' => 'Bernhard'], 'lastName'],
[(object) ['property' => (object) ['firstName' => 'Bernhard']], 'property.lastName'],
[['index' => (object) ['firstName' => 'Bernhard']], '[index].lastName'],
[new TestClass('Bernhard'), 'protectedProperty'],
[new TestClass('Bernhard'), 'privateProperty'],
[new TestClass('Bernhard'), 'protectedAccessor'],
[new TestClass('Bernhard'), 'protectedIsAccessor'],
[new TestClass('Bernhard'), 'protectedHasAccessor'],
[new TestClass('Bernhard'), 'privateAccessor'],
[new TestClass('Bernhard'), 'privateIsAccessor'],
[new TestClass('Bernhard'), 'privateHasAccessor'],
// Properties are not camelized
array(new TestClass('Bernhard'), 'public_property'),
);
[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]'),
);
return [
[['firstName' => 'Bernhard'], '[lastName]'],
[[], '[index][lastName]'],
[['index' => []], '[index][lastName]'],
[['index' => ['firstName' => 'Bernhard']], '[index][lastName]'],
[(object) ['property' => ['firstName' => 'Bernhard']], 'property[lastName]'],
];
}
/**
@@ -90,10 +95,10 @@ class PropertyAccessorTest extends TestCase
/**
* @dataProvider getPathsWithMissingProperty
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testGetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->propertyAccessor->getValue($objectOrArray, $path);
}
@@ -107,19 +112,92 @@ class PropertyAccessorTest extends TestCase
/**
* @dataProvider getPathsWithMissingIndex
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
*/
public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
$this->propertyAccessor = new PropertyAccessor(false, true);
$this->propertyAccessor->getValue($objectOrArray, $path);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
* @requires PHP 7.4
*/
public function testGetValueThrowsExceptionIfUninitializedProperty()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should initialize it or declare a default value instead.');
$this->propertyAccessor->getValue(new UninitializedProperty(), 'uninitialized');
}
/**
* @requires PHP 7
*/
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?');
$this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized');
}
/**
* @requires PHP 7
*/
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousClass()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The method "class@anonymous::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?');
$object = eval('return new class() {
private $uninitialized;
public function getUninitialized(): array
{
return $this->uninitialized;
}
};');
$this->propertyAccessor->getValue($object, 'uninitialized');
}
/**
* @requires PHP 7
*/
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousStdClass()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The method "stdClass@anonymous::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?');
$object = eval('return new class() extends \stdClass {
private $uninitialized;
public function getUninitialized(): array
{
return $this->uninitialized;
}
};');
$this->propertyAccessor->getValue($object, 'uninitialized');
}
/**
* @requires PHP 7
*/
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousChildClass()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty@anonymous::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?');
$object = eval('return new class() extends \Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty {};');
$this->propertyAccessor->getValue($object, 'uninitialized');
}
public function testGetValueThrowsExceptionIfNotArrayAccess()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
$this->propertyAccessor->getValue(new \stdClass(), '[index]');
}
@@ -131,10 +209,10 @@ class PropertyAccessorTest extends TestCase
public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
{
$object = new \ArrayObject();
$array = array('child' => array('index' => $object));
$array = ['child' => ['index' => $object]];
$this->assertNull($this->propertyAccessor->getValue($array, '[child][index][foo][bar]'));
$this->assertSame(array(), $object->getArrayCopy());
$this->assertSame([], $object->getArrayCopy());
}
// https://github.com/symfony/symfony/pull/4450
@@ -146,31 +224,29 @@ class PropertyAccessorTest extends TestCase
public function testGetValueNotModifyObject()
{
$object = new \stdClass();
$object->firstName = array('Bernhard');
$object->firstName = ['Bernhard'];
$this->assertNull($this->propertyAccessor->getValue($object, 'firstName[1]'));
$this->assertSame(array('Bernhard'), $object->firstName);
$this->assertSame(['Bernhard'], $object->firstName);
}
public function testGetValueNotModifyObjectException()
{
$propertyAccessor = new PropertyAccessor(false, true);
$object = new \stdClass();
$object->firstName = array('Bernhard');
$object->firstName = ['Bernhard'];
try {
$propertyAccessor->getValue($object, 'firstName[1]');
} catch (NoSuchIndexException $e) {
}
$this->assertSame(array('Bernhard'), $object->firstName);
$this->assertSame(['Bernhard'], $object->firstName);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testGetValueDoesNotReadMagicCallByDefault()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty');
}
@@ -191,11 +267,11 @@ class PropertyAccessorTest extends TestCase
/**
* @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
* @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/
public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException');
$this->expectExceptionMessage('PropertyAccessor requires a graph of objects or arrays to operate on');
$this->propertyAccessor->getValue($objectOrArray, $path);
}
@@ -211,10 +287,10 @@ class PropertyAccessorTest extends TestCase
/**
* @dataProvider getPathsWithMissingProperty
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testSetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
}
@@ -239,11 +315,9 @@ class PropertyAccessorTest extends TestCase
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
*/
public function testSetValueThrowsExceptionIfNotArrayAccess()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
$object = new \stdClass();
$this->propertyAccessor->setValue($object, '[index]', 'Updated');
@@ -258,21 +332,17 @@ class PropertyAccessorTest extends TestCase
$this->assertEquals('Updated', $author->__get('magicProperty'));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testSetValueThrowsExceptionIfThereAreMissingParameters()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$object = new TestClass('Bernhard');
$this->propertyAccessor->setValue($object, 'publicAccessorWithMoreRequiredParameters', 'Updated');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testSetValueDoesNotUpdateMagicCallByDefault()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$author = new TestClassMagicCall('Bernhard');
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
@@ -286,23 +356,23 @@ class PropertyAccessorTest extends TestCase
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
$this->assertEquals('Updated', $author->__call('getMagicCallProperty', array()));
$this->assertEquals('Updated', $author->__call('getMagicCallProperty', []));
}
/**
* @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
* @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/
public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException');
$this->expectExceptionMessage('PropertyAccessor requires a graph of objects or arrays to operate on');
$this->propertyAccessor->setValue($objectOrArray, $path, 'value');
}
public function testGetValueWhenArrayValueIsNull()
{
$this->propertyAccessor = new PropertyAccessor(false, true);
$this->assertNull($this->propertyAccessor->getValue(array('index' => array('nullable' => null)), '[index][nullable]'));
$this->assertNull($this->propertyAccessor->getValue(['index' => ['nullable' => null]], '[index][nullable]'));
}
/**
@@ -429,47 +499,47 @@ class PropertyAccessorTest extends TestCase
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'),
return [
[['Bernhard', 'Schussek'], '[0]', 'Bernhard'],
[['Bernhard', 'Schussek'], '[1]', 'Schussek'],
[['firstName' => 'Bernhard'], '[firstName]', 'Bernhard'],
[['index' => ['firstName' => 'Bernhard']], '[index][firstName]', 'Bernhard'],
[(object) ['firstName' => 'Bernhard'], 'firstName', 'Bernhard'],
[(object) ['property' => ['firstName' => 'Bernhard']], 'property[firstName]', 'Bernhard'],
[['index' => (object) ['firstName' => 'Bernhard']], '[index].firstName', 'Bernhard'],
[(object) ['property' => (object) ['firstName' => 'Bernhard']], 'property.firstName', 'Bernhard'],
// Accessor methods
array(new TestClass('Bernhard'), 'publicProperty', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicAccessorWithDefaultValue', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicAccessorWithRequiredAndDefaultValue', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicGetSetter', 'Bernhard'),
[new TestClass('Bernhard'), 'publicProperty', 'Bernhard'],
[new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'],
[new TestClass('Bernhard'), 'publicAccessorWithDefaultValue', 'Bernhard'],
[new TestClass('Bernhard'), 'publicAccessorWithRequiredAndDefaultValue', 'Bernhard'],
[new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'],
[new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'],
[new TestClass('Bernhard'), 'publicGetSetter', 'Bernhard'],
// Methods are camelized
array(new TestClass('Bernhard'), 'public_accessor', 'Bernhard'),
array(new TestClass('Bernhard'), '_public_accessor', 'Bernhard'),
[new TestClass('Bernhard'), 'public_accessor', 'Bernhard'],
[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),
[['index' => []], '[index][firstName]', null],
[['root' => ['index' => []]], '[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'),
[['%!@$§.' => 'Bernhard'], '[%!@$§.]', 'Bernhard'],
[['index' => ['%!@$§.' => 'Bernhard']], '[index][%!@$§.]', 'Bernhard'],
[(object) ['%!@$§' => 'Bernhard'], '%!@$§', 'Bernhard'],
[(object) ['property' => (object) ['%!@$§' => 'Bernhard']], 'property.%!@$§', 'Bernhard'],
// nested objects and arrays
array(array('foo' => new TestClass('bar')), '[foo].publicGetSetter', 'bar'),
array(new TestClass(array('foo' => 'bar')), 'publicGetSetter[foo]', 'bar'),
array(new TestClass(new TestClass('bar')), 'publicGetter.publicGetSetter', 'bar'),
array(new TestClass(array('foo' => new TestClass('bar'))), 'publicGetter[foo].publicGetSetter', 'bar'),
array(new TestClass(new TestClass(new TestClass('bar'))), 'publicGetter.publicGetter.publicGetSetter', 'bar'),
array(new TestClass(array('foo' => array('baz' => new TestClass('bar')))), 'publicGetter[foo][baz].publicGetSetter', 'bar'),
);
[['foo' => new TestClass('bar')], '[foo].publicGetSetter', 'bar'],
[new TestClass(['foo' => 'bar']), 'publicGetSetter[foo]', 'bar'],
[new TestClass(new TestClass('bar')), 'publicGetter.publicGetSetter', 'bar'],
[new TestClass(['foo' => new TestClass('bar')]), 'publicGetter[foo].publicGetSetter', 'bar'],
[new TestClass(new TestClass(new TestClass('bar'))), 'publicGetter.publicGetter.publicGetSetter', 'bar'],
[new TestClass(['foo' => ['baz' => new TestClass('bar')]]), 'publicGetter[foo][baz].publicGetSetter', 'bar'],
];
}
public function testTicket5755()
@@ -484,20 +554,20 @@ class PropertyAccessorTest extends TestCase
public function testSetValueDeepWithMagicGetter()
{
$obj = new TestClassMagicGet('foo');
$obj->publicProperty = array('foo' => array('bar' => 'some_value'));
$obj->publicProperty = ['foo' => ['bar' => 'some_value']];
$this->propertyAccessor->setValue($obj, 'publicProperty[foo][bar]', 'Updated');
$this->assertSame('Updated', $obj->publicProperty['foo']['bar']);
}
public function getReferenceChainObjectsForSetValue()
{
return array(
array(array('a' => array('b' => array('c' => 'old-value'))), '[a][b][c]', 'new-value'),
array(new TestClassSetValue(new TestClassSetValue('old-value')), 'value.value', 'new-value'),
array(new TestClassSetValue(array('a' => array('b' => array('c' => new TestClassSetValue('old-value'))))), 'value[a][b][c].value', 'new-value'),
array(new TestClassSetValue(array('a' => array('b' => 'old-value'))), 'value[a][b]', 'new-value'),
array(new \ArrayIterator(array('a' => array('b' => array('c' => 'old-value')))), '[a][b][c]', 'new-value'),
);
return [
[['a' => ['b' => ['c' => 'old-value']]], '[a][b][c]', 'new-value'],
[new TestClassSetValue(new TestClassSetValue('old-value')), 'value.value', 'new-value'],
[new TestClassSetValue(['a' => ['b' => ['c' => new TestClassSetValue('old-value')]]]), 'value[a][b][c].value', 'new-value'],
[new TestClassSetValue(['a' => ['b' => 'old-value']]), 'value[a][b]', 'new-value'],
[new \ArrayIterator(['a' => ['b' => ['c' => 'old-value']]]), '[a][b][c]', 'new-value'],
];
}
/**
@@ -512,11 +582,11 @@ class PropertyAccessorTest extends TestCase
public function getReferenceChainObjectsForIsWritable()
{
return array(
array(new TestClassIsWritable(array('a' => array('b' => 'old-value'))), 'value[a][b]', false),
array(new TestClassIsWritable(new \ArrayIterator(array('a' => array('b' => 'old-value')))), 'value[a][b]', true),
array(new TestClassIsWritable(array('a' => array('b' => array('c' => new TestClassSetValue('old-value'))))), 'value[a][b][c].value', true),
);
return [
[new TestClassIsWritable(['a' => ['b' => 'old-value']]), 'value[a][b]', false],
[new TestClassIsWritable(new \ArrayIterator(['a' => ['b' => 'old-value']])), 'value[a][b]', true],
[new TestClassIsWritable(['a' => ['b' => ['c' => new TestClassSetValue('old-value')]]]), 'value[a][b][c].value', true],
];
}
/**
@@ -527,23 +597,19 @@ class PropertyAccessorTest extends TestCase
$this->assertEquals($value, $this->propertyAccessor->isWritable($object, $path));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
* @expectedExceptionMessage Expected argument of type "DateTime", "string" given
*/
public function testThrowTypeError()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Expected argument of type "DateTime", "string" given');
$object = new TypeHinted();
$this->propertyAccessor->setValue($object, 'date', 'This is a string, \DateTime expected.');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
* @expectedExceptionMessage Expected argument of type "DateTime", "NULL" given
*/
public function testThrowTypeErrorWithNullArgument()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Expected argument of type "DateTime", "null" given');
$object = new TypeHinted();
$this->propertyAccessor->setValue($object, 'date', null);
@@ -560,46 +626,179 @@ class PropertyAccessorTest extends TestCase
public function testArrayNotBeeingOverwritten()
{
$value = array('value1' => 'foo', 'value2' => 'bar');
$value = ['value1' => 'foo', 'value2' => 'bar'];
$object = new TestClass($value);
$this->propertyAccessor->setValue($object, 'publicAccessor[value2]', 'baz');
$this->assertSame('baz', $this->propertyAccessor->getValue($object, 'publicAccessor[value2]'));
$this->assertSame(array('value1' => 'foo', 'value2' => 'baz'), $object->getPublicAccessor());
$this->assertSame(['value1' => 'foo', 'value2' => 'baz'], $object->getPublicAccessor());
}
public function testCacheReadAccess()
{
$obj = new TestClass('foo');
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$this->assertEquals('foo', $propertyAccessor->getValue($obj, 'publicGetSetter'));
$propertyAccessor->setValue($obj, 'publicGetSetter', 'bar');
$propertyAccessor->setValue($obj, 'publicGetSetter', 'baz');
$this->assertEquals('baz', $propertyAccessor->getValue($obj, 'publicGetSetter'));
}
public function testAttributeWithSpecialChars()
{
$obj = new \stdClass();
$obj->{'@foo'} = 'bar';
$obj->{'a/b'} = '1';
$obj->{'a%2Fb'} = '2';
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$this->assertSame('bar', $propertyAccessor->getValue($obj, '@foo'));
$this->assertSame('1', $propertyAccessor->getValue($obj, 'a/b'));
$this->assertSame('2', $propertyAccessor->getValue($obj, 'a%2Fb'));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
* @expectedExceptionMessage Expected argument of type "Countable", "string" given
*/
public function testThrowTypeErrorWithInterface()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Expected argument of type "Countable", "string" given');
$object = new TypeHinted();
$this->propertyAccessor->setValue($object, 'countable', 'This is a string, \Countable expected.');
}
/**
* @requires PHP 7
*
* @expectedException \TypeError
* @requires PHP 7.0
*/
public function testDoNotDiscardReturnTypeError()
public function testAnonymousClassRead()
{
$object = new ReturnTyped();
$value = 'bar';
$this->propertyAccessor->setValue($object, 'foos', array(new \DateTime()));
$obj = $this->generateAnonymousClass($value);
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
}
/**
* @requires PHP 7.0
*/
public function testAnonymousClassWrite()
{
$value = 'bar';
$obj = $this->generateAnonymousClass('');
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$propertyAccessor->setValue($obj, 'foo', $value);
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
}
private function generateAnonymousClass($value)
{
$obj = eval('return new class($value)
{
private $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
/**
* @return mixed
*/
public function getFoo()
{
return $this->foo;
}
/**
* @param mixed $foo
*/
public function setFoo($foo)
{
$this->foo = $foo;
}
};');
return $obj;
}
/**
* @requires PHP 7.0
*/
public function testThrowTypeErrorInsideSetterCall()
{
$this->expectException('TypeError');
$object = new TestClassTypeErrorInsideCall();
$this->propertyAccessor->setValue($object, 'property', 'foo');
}
/**
* @requires PHP 7
*/
public function testDoNotDiscardReturnTypeError()
{
$this->expectException('TypeError');
$object = new ReturnTyped();
$this->propertyAccessor->setValue($object, 'foos', [new \DateTime()]);
}
/**
* @requires PHP 7
*
* @expectedException \TypeError
*/
public function testDoNotDiscardReturnTypeErrorWhenWriterMethodIsMisconfigured()
{
$this->expectException('TypeError');
$object = new ReturnTyped();
$this->propertyAccessor->setValue($object, 'name', 'foo');
}
public function testWriteToSingularPropertyWhilePluralOneExists()
{
$object = new TestSingularAndPluralProps();
$this->propertyAccessor->isWritable($object, 'email'); //cache access info
$this->propertyAccessor->setValue($object, 'email', 'test@email.com');
self::assertEquals('test@email.com', $object->getEmail());
self::assertEmpty($object->getEmails());
}
public function testWriteToPluralPropertyWhileSingularOneExists()
{
$object = new TestSingularAndPluralProps();
$this->propertyAccessor->isWritable($object, 'emails'); //cache access info
$this->propertyAccessor->setValue($object, 'emails', ['test@email.com']);
$this->assertEquals(['test@email.com'], $object->getEmails());
$this->assertNull($object->getEmail());
}
public function testAdderAndRemoverArePreferredOverSetter()
{
$object = new TestPluralAdderRemoverAndSetter();
$this->propertyAccessor->isWritable($object, 'emails'); //cache access info
$this->propertyAccessor->setValue($object, 'emails', ['test@email.com']);
$this->assertEquals(['test@email.com'], $object->getEmails());
}
public function testAdderAndRemoverArePreferredOverSetterForSameSingularAndPlural()
{
$object = new TestPluralAdderRemoverAndSetterSameSingularAndPlural();
$this->propertyAccessor->isWritable($object, 'aircraft'); //cache access info
$this->propertyAccessor->setValue($object, 'aircraft', ['aeroplane']);
$this->assertEquals(['aeroplane'], $object->getAircraft());
}
}
@@ -116,19 +116,15 @@ class PropertyPathBuilderTest extends TestCase
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByIndexDoesNotAllowInvalidOffsets()
{
$this->expectException('OutOfBoundsException');
$this->builder->replaceByIndex(6, 'new1');
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByIndexDoesNotAllowNegativeOffsets()
{
$this->expectException('OutOfBoundsException');
$this->builder->replaceByIndex(-1, 'new1');
}
@@ -150,19 +146,15 @@ class PropertyPathBuilderTest extends TestCase
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByPropertyDoesNotAllowInvalidOffsets()
{
$this->expectException('OutOfBoundsException');
$this->builder->replaceByProperty(6, 'new1');
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByPropertyDoesNotAllowNegativeOffsets()
{
$this->expectException('OutOfBoundsException');
$this->builder->replaceByProperty(-1, 'new1');
}
@@ -195,19 +187,19 @@ class PropertyPathBuilderTest extends TestCase
/**
* @dataProvider provideInvalidOffsets
* @expectedException \OutOfBoundsException
*/
public function testReplaceDoesNotAllowInvalidOffsets($offset)
{
$this->expectException('OutOfBoundsException');
$this->builder->replace($offset, 1, new PropertyPath('new1[new2].new3'));
}
public function provideInvalidOffsets()
{
return array(
array(6),
array(-7),
);
return [
[6],
[-7],
];
}
public function testReplaceWithLengthGreaterOne()
@@ -270,19 +262,36 @@ class PropertyPathBuilderTest extends TestCase
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @expectedException \OutOfBoundsException
*/
public function testRemoveDoesNotAllowInvalidOffsets()
{
$this->expectException('OutOfBoundsException');
$this->builder->remove(6);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testRemoveDoesNotAllowNegativeOffsets()
{
$this->expectException('OutOfBoundsException');
$this->builder->remove(-1);
}
public function testRemoveAndAppendAtTheEnd()
{
$this->builder->remove($this->builder->getLength() - 1);
$path = new PropertyPath('old1[old2].old3[old4][old5]');
$this->assertEquals($path, $this->builder->getPropertyPath());
$this->builder->appendProperty('old7');
$path = new PropertyPath('old1[old2].old3[old4][old5].old7');
$this->assertEquals($path, $this->builder->getPropertyPath());
$this->builder->remove($this->builder->getLength() - 1);
$path = new PropertyPath('old1[old2].old3[old4][old5]');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
}
+21 -43
View File
@@ -23,65 +23,55 @@ class PropertyPathTest extends TestCase
$this->assertEquals('reference.traversable[index].property', $path->__toString());
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testDotIsRequiredBeforeProperty()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException');
new PropertyPath('[index]property');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testDotCannotBePresentAtTheBeginning()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException');
new PropertyPath('.property');
}
public function providePathsContainingUnexpectedCharacters()
{
return array(
array('property.'),
array('property.['),
array('property..'),
array('property['),
array('property[['),
array('property[.'),
array('property[]'),
);
return [
['property.'],
['property.['],
['property..'],
['property['],
['property[['],
['property[.'],
['property[]'],
];
}
/**
* @dataProvider providePathsContainingUnexpectedCharacters
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testUnexpectedCharacters($path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException');
new PropertyPath($path);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testPathCannotBeEmpty()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException');
new PropertyPath('');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
*/
public function testPathCannotBeNull()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidArgumentException');
new PropertyPath(null);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
*/
public function testPathCannotBeFalse()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\InvalidArgumentException');
new PropertyPath(false);
}
@@ -128,21 +118,17 @@ class PropertyPathTest extends TestCase
$this->assertEquals('child', $propertyPath->getElement(2));
}
/**
* @expectedException \OutOfBoundsException
*/
public function testGetElementDoesNotAcceptInvalidIndices()
{
$this->expectException('OutOfBoundsException');
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->getElement(3);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testGetElementDoesNotAcceptNegativeIndices()
{
$this->expectException('OutOfBoundsException');
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->getElement(-1);
@@ -156,21 +142,17 @@ class PropertyPathTest extends TestCase
$this->assertFalse($propertyPath->isProperty(2));
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsPropertyDoesNotAcceptInvalidIndices()
{
$this->expectException('OutOfBoundsException');
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isProperty(3);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsPropertyDoesNotAcceptNegativeIndices()
{
$this->expectException('OutOfBoundsException');
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isProperty(-1);
@@ -184,21 +166,17 @@ class PropertyPathTest extends TestCase
$this->assertTrue($propertyPath->isIndex(2));
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsIndexDoesNotAcceptInvalidIndices()
{
$this->expectException('OutOfBoundsException');
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isIndex(3);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsIndexDoesNotAcceptNegativeIndices()
{
$this->expectException('OutOfBoundsException');
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isIndex(-1);
+8 -135
View File
@@ -14,145 +14,18 @@ namespace Symfony\Component\PropertyAccess\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyAccess\StringUtil;
/**
* @group legacy
*/
class StringUtilTest extends TestCase
{
public function singularifyProvider()
{
// see http://english-zone.com/spelling/plurals.html
// see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
return array(
array('accesses', 'access'),
array('addresses', 'address'),
array('agendas', 'agenda'),
array('alumnae', 'alumna'),
array('alumni', 'alumnus'),
array('analyses', array('analys', 'analyse', 'analysis')),
array('antennae', 'antenna'),
array('antennas', 'antenna'),
array('appendices', array('appendex', 'appendix', 'appendice')),
array('arches', array('arch', 'arche')),
array('atlases', array('atlas', 'atlase', 'atlasis')),
array('axes', array('ax', 'axe', 'axis')),
array('babies', 'baby'),
array('bacteria', array('bacterion', 'bacterium')),
array('bases', array('bas', 'base', 'basis')),
array('batches', array('batch', 'batche')),
array('beaux', 'beau'),
array('bees', array('be', 'bee')),
array('boxes', 'box'),
array('boys', 'boy'),
array('bureaus', 'bureau'),
array('bureaux', 'bureau'),
array('buses', array('bus', 'buse', 'busis')),
array('bushes', array('bush', 'bushe')),
array('calves', array('calf', 'calve', 'calff')),
array('cars', 'car'),
array('cassettes', array('cassett', 'cassette')),
array('caves', array('caf', 'cave', 'caff')),
array('chateaux', 'chateau'),
array('cheeses', array('chees', 'cheese', 'cheesis')),
array('children', 'child'),
array('circuses', array('circus', 'circuse', 'circusis')),
array('cliffs', 'cliff'),
array('committee', 'committee'),
array('crises', array('cris', 'crise', 'crisis')),
array('criteria', array('criterion', 'criterium')),
array('cups', 'cup'),
array('data', array('daton', 'datum')),
array('days', 'day'),
array('discos', 'disco'),
array('devices', array('devex', 'devix', 'device')),
array('drives', 'drive'),
array('drivers', 'driver'),
array('dwarves', array('dwarf', 'dwarve', 'dwarff')),
array('echoes', array('echo', 'echoe')),
array('elves', array('elf', 'elve', 'elff')),
array('emphases', array('emphas', 'emphase', 'emphasis')),
array('faxes', 'fax'),
array('feet', 'foot'),
array('feedback', 'feedback'),
array('foci', 'focus'),
array('focuses', array('focus', 'focuse', 'focusis')),
array('formulae', 'formula'),
array('formulas', 'formula'),
array('fungi', 'fungus'),
array('funguses', array('fungus', 'funguse', 'fungusis')),
array('garages', array('garag', 'garage')),
array('geese', 'goose'),
array('halves', array('half', 'halve', 'halff')),
array('hats', 'hat'),
array('heroes', array('hero', 'heroe')),
array('hippopotamuses', array('hippopotamus', 'hippopotamuse', 'hippopotamusis')), //hippopotami
array('hoaxes', 'hoax'),
array('hooves', array('hoof', 'hoove', 'hooff')),
array('houses', array('hous', 'house', 'housis')),
array('indexes', 'index'),
array('indices', array('index', 'indix', 'indice')),
array('ions', 'ion'),
array('irises', array('iris', 'irise', 'irisis')),
array('kisses', 'kiss'),
array('knives', 'knife'),
array('lamps', 'lamp'),
array('leaves', array('leaf', 'leave', 'leaff')),
array('lice', 'louse'),
array('lives', 'life'),
array('matrices', array('matrex', 'matrix', 'matrice')),
array('matrixes', 'matrix'),
array('men', 'man'),
array('mice', 'mouse'),
array('moves', 'move'),
array('movies', 'movie'),
array('nebulae', 'nebula'),
array('neuroses', array('neuros', 'neurose', 'neurosis')),
array('news', 'news'),
array('oases', array('oas', 'oase', 'oasis')),
array('objectives', 'objective'),
array('oxen', 'ox'),
array('parties', 'party'),
array('people', 'person'),
array('persons', 'person'),
array('phenomena', array('phenomenon', 'phenomenum')),
array('photos', 'photo'),
array('pianos', 'piano'),
array('plateaux', 'plateau'),
array('poppies', 'poppy'),
array('prices', array('prex', 'prix', 'price')),
array('quizzes', 'quiz'),
array('radii', 'radius'),
array('roofs', 'roof'),
array('roses', array('ros', 'rose', 'rosis')),
array('sandwiches', array('sandwich', 'sandwiche')),
array('scarves', array('scarf', 'scarve', 'scarff')),
array('schemas', 'schema'), //schemata
array('selfies', 'selfie'),
array('series', 'series'),
array('services', 'service'),
array('sheriffs', 'sheriff'),
array('shoes', array('sho', 'shoe')),
array('spies', 'spy'),
array('staves', array('staf', 'stave', 'staff')),
array('stories', 'story'),
array('strata', array('straton', 'stratum')),
array('suitcases', array('suitcas', 'suitcase', 'suitcasis')),
array('syllabi', 'syllabus'),
array('tags', 'tag'),
array('teeth', 'tooth'),
array('theses', array('thes', 'these', 'thesis')),
array('thieves', array('thief', 'thieve', 'thieff')),
array('trees', array('tre', 'tree')),
array('waltzes', array('waltz', 'waltze')),
array('wives', 'wife'),
// test casing: if the first letter was uppercase, it should remain so
array('Men', 'Man'),
array('GrandChildren', 'GrandChild'),
array('SubTrees', array('SubTre', 'SubTree')),
// Known issues
//array('insignia', 'insigne'),
//array('insignias', 'insigne'),
//array('rattles', 'rattle'),
);
// This is only a stub to make sure the BC layer works
// Actual tests are in the Symfony Inflector component
return [
['axes', ['ax', 'axe', 'axis']],
];
}
/**
+10 -8
View File
@@ -16,8 +16,15 @@
}
],
"require": {
"php": ">=5.3.9",
"symfony/polyfill-ctype": "~1.8"
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-php70": "~1.0",
"symfony/inflector": "~3.1|~4.0"
},
"require-dev": {
"symfony/cache": "~3.1|~4.0"
},
"suggest": {
"psr/cache-implementation": "To cache access methods."
},
"autoload": {
"psr-4": { "Symfony\\Component\\PropertyAccess\\": "" },
@@ -25,10 +32,5 @@
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
}
}
"minimum-stability": "dev"
}