Merge branch '2.3' into 2.7
* 2.3: [Validator] use correct term for a property in docblock (not "option") [PropertyAccess] Remove most ref mismatches to improve perf [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols [NumberFormatter] Fix invalid numeric literal on PHP 7 Use XML_ELEMENT_NODE in nodeType check [PropertyAccess] Reduce overhead of UnexpectedTypeException tracking [PropertyAccess] Throw an UnexpectedTypeException when the type do not match [FrameworkBundle] Add tests for the Controller class Conflicts: src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php src/Symfony/Component/PropertyAccess/PropertyAccessor.php src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php src/Symfony/Component/PropertyAccess/PropertyPath.php src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php src/Symfony/Component/Validator/Constraints/EmailValidator.php
This commit is contained in:
commit
86c0a17721
|
@ -209,4 +209,122 @@ class TestController extends Controller
|
||||||
{
|
{
|
||||||
return parent::isCsrfTokenValid($id, $token);
|
return parent::isCsrfTokenValid($id, $token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGenerateUrl()
|
||||||
|
{
|
||||||
|
$router = $this->getMock('Symfony\Component\Routing\RouterInterface');
|
||||||
|
$router->expects($this->once())->method('generate')->willReturn('/foo');
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('get')->will($this->returnValue($router));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertEquals('/foo', $controller->generateUrl('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRedirect()
|
||||||
|
{
|
||||||
|
$controller = new Controller();
|
||||||
|
$response = $controller->redirect('http://dunglas.fr', 301);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
|
||||||
|
$this->assertSame('http://dunglas.fr', $response->getTargetUrl());
|
||||||
|
$this->assertSame(301, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenderViewTemplating()
|
||||||
|
{
|
||||||
|
$templating = $this->getMock('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface');
|
||||||
|
$templating->expects($this->once())->method('render')->willReturn('bar');
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('get')->will($this->returnValue($templating));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertEquals('bar', $controller->renderView('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenderTemplating()
|
||||||
|
{
|
||||||
|
$templating = $this->getMock('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface');
|
||||||
|
$templating->expects($this->once())->method('renderResponse')->willReturn(new Response('bar'));
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('get')->will($this->returnValue($templating));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertEquals('bar', $controller->render('foo')->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStreamTemplating()
|
||||||
|
{
|
||||||
|
$templating = $this->getMock('Symfony\Component\Routing\RouterInterface');
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('get')->will($this->returnValue($templating));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $controller->stream('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateNotFoundException()
|
||||||
|
{
|
||||||
|
$controller = new Controller();
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Symfony\Component\HttpKernel\Exception\NotFoundHttpException', $controller->createNotFoundException());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateForm()
|
||||||
|
{
|
||||||
|
$form = $this->getMock('Symfony\Component\Form\FormInterface');
|
||||||
|
|
||||||
|
$formFactory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
|
||||||
|
$formFactory->expects($this->once())->method('create')->willReturn($form);
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('get')->will($this->returnValue($formFactory));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertEquals($form, $controller->createForm('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateFormBuilder()
|
||||||
|
{
|
||||||
|
$formBuilder = $this->getMock('Symfony\Component\Form\FormBuilderInterface');
|
||||||
|
|
||||||
|
$formFactory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
|
||||||
|
$formFactory->expects($this->once())->method('createBuilder')->willReturn($formBuilder);
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('get')->will($this->returnValue($formFactory));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertEquals($formBuilder, $controller->createFormBuilder('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetDoctrine()
|
||||||
|
{
|
||||||
|
$doctrine = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry');
|
||||||
|
|
||||||
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
|
$container->expects($this->at(0))->method('has')->will($this->returnValue(true));
|
||||||
|
$container->expects($this->at(1))->method('get')->will($this->returnValue($doctrine));
|
||||||
|
|
||||||
|
$controller = new Controller();
|
||||||
|
$controller->setContainer($container);
|
||||||
|
|
||||||
|
$this->assertEquals($doctrine, $controller->getDoctrine());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -482,7 +482,7 @@ class Crawler extends \SplObjectStorage
|
||||||
$nodes = array();
|
$nodes = array();
|
||||||
|
|
||||||
while ($node = $node->parentNode) {
|
while ($node = $node->parentNode) {
|
||||||
if (1 === $node->nodeType) {
|
if (XML_ELEMENT_NODE === $node->nodeType) {
|
||||||
$nodes[] = $node;
|
$nodes[] = $node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,24 +231,18 @@ class NumberFormatter
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum values of the integer type in 32 bit platforms.
|
* The maximum value of the integer type in 32 bit platforms.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var int
|
||||||
*/
|
*/
|
||||||
private static $int32Range = array(
|
private static $int32Max = 2147483647;
|
||||||
'positive' => 2147483647,
|
|
||||||
'negative' => -2147483648,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum values of the integer type in 64 bit platforms.
|
* The maximum value of the integer type in 64 bit platforms.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var int|float
|
||||||
*/
|
*/
|
||||||
private static $int64Range = array(
|
private static $int64Max = 9223372036854775807;
|
||||||
'positive' => 9223372036854775807,
|
|
||||||
'negative' => -9223372036854775808,
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $enSymbols = array(
|
private static $enSymbols = array(
|
||||||
self::DECIMAL => array('.', ',', ';', '%', '0', '#', '-', '+', '¤', '¤¤', '.', 'E', '‰', '*', '∞', 'NaN', '@', ','),
|
self::DECIMAL => array('.', ',', ';', '%', '0', '#', '-', '+', '¤', '¤¤', '.', 'E', '‰', '*', '∞', 'NaN', '@', ','),
|
||||||
|
@ -526,7 +520,7 @@ class NumberFormatter
|
||||||
* @param int $type Type of the formatting, one of the format type constants. NumberFormatter::TYPE_DOUBLE by default
|
* @param int $type Type of the formatting, one of the format type constants. NumberFormatter::TYPE_DOUBLE by default
|
||||||
* @param int $position Offset to begin the parsing on return this value will hold the offset at which the parsing ended
|
* @param int $position Offset to begin the parsing on return this value will hold the offset at which the parsing ended
|
||||||
*
|
*
|
||||||
* @return bool|string The parsed value of false on error
|
* @return int|float|false The parsed value of false on error
|
||||||
*
|
*
|
||||||
* @see http://www.php.net/manual/en/numberformatter.parse.php
|
* @see http://www.php.net/manual/en/numberformatter.parse.php
|
||||||
*/
|
*/
|
||||||
|
@ -835,7 +829,7 @@ class NumberFormatter
|
||||||
*/
|
*/
|
||||||
private function getInt32Value($value)
|
private function getInt32Value($value)
|
||||||
{
|
{
|
||||||
if ($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) {
|
if ($value > self::$int32Max || $value < -self::$int32Max - 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,20 +842,18 @@ class NumberFormatter
|
||||||
* @param mixed $value The value to be converted
|
* @param mixed $value The value to be converted
|
||||||
*
|
*
|
||||||
* @return int|float|false The converted value
|
* @return int|float|false The converted value
|
||||||
*
|
|
||||||
* @see https://bugs.php.net/bug.php?id=59597 Bug #59597
|
|
||||||
*/
|
*/
|
||||||
private function getInt64Value($value)
|
private function getInt64Value($value)
|
||||||
{
|
{
|
||||||
if ($value > self::$int64Range['positive'] || $value < self::$int64Range['negative']) {
|
if ($value > self::$int64Max || $value < -self::$int64Max - 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHP_INT_SIZE !== 8 && ($value > self::$int32Range['positive'] || $value <= self::$int32Range['negative'])) {
|
if (PHP_INT_SIZE !== 8 && ($value > self::$int32Max || $value <= -self::$int32Max - 1)) {
|
||||||
// Bug #59597 was fixed on PHP 5.3.14 and 5.4.4
|
// Bug #59597 was fixed on PHP 5.3.14 and 5.4.4
|
||||||
// The negative PHP_INT_MAX was being converted to float
|
// The negative PHP_INT_MAX was being converted to float
|
||||||
if (
|
if (
|
||||||
$value == self::$int32Range['negative'] &&
|
$value == -self::$int32Max - 1 &&
|
||||||
((PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50314) || PHP_VERSION_ID >= 50404 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone')))
|
((PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50314) || PHP_VERSION_ID >= 50404 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone')))
|
||||||
) {
|
) {
|
||||||
return (int) $value;
|
return (int) $value;
|
||||||
|
@ -874,7 +866,7 @@ class NumberFormatter
|
||||||
// Bug #59597 was fixed on PHP 5.3.14 and 5.4.4
|
// Bug #59597 was fixed on PHP 5.3.14 and 5.4.4
|
||||||
// A 32 bit integer was being generated instead of a 64 bit integer
|
// A 32 bit integer was being generated instead of a 64 bit integer
|
||||||
if (
|
if (
|
||||||
($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) &&
|
($value > self::$int32Max || $value < -self::$int32Max - 1) &&
|
||||||
(PHP_VERSION_ID < 50314 || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50404)) &&
|
(PHP_VERSION_ID < 50314 || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50404)) &&
|
||||||
!(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))
|
!(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
namespace Symfony\Component\PropertyAccess;
|
namespace Symfony\Component\PropertyAccess;
|
||||||
|
|
||||||
use Symfony\Component\PropertyAccess\Exception\AccessException;
|
use Symfony\Component\PropertyAccess\Exception\AccessException;
|
||||||
|
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
||||||
|
@ -21,6 +22,7 @@ use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
||||||
*
|
*
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*/
|
*/
|
||||||
class PropertyAccessor implements PropertyAccessorInterface
|
class PropertyAccessor implements PropertyAccessorInterface
|
||||||
{
|
{
|
||||||
|
@ -32,7 +34,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
const IS_REF = 1;
|
const REF = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -113,6 +115,9 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $writePropertyCache = array();
|
private $writePropertyCache = array();
|
||||||
|
private static $previousErrorHandler = false;
|
||||||
|
private static $errorHandler = array(__CLASS__, 'handleError');
|
||||||
|
private static $resultProto = array(self::VALUE => null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should not be used by application code. Use
|
* Should not be used by application code. Use
|
||||||
|
@ -136,7 +141,10 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
$propertyPath = new PropertyPath($propertyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyValues = &$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
$zval = array(
|
||||||
|
self::VALUE => $objectOrArray,
|
||||||
|
);
|
||||||
|
$propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
||||||
|
|
||||||
return $propertyValues[count($propertyValues) - 1][self::VALUE];
|
return $propertyValues[count($propertyValues) - 1][self::VALUE];
|
||||||
}
|
}
|
||||||
|
@ -150,51 +158,99 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
$propertyPath = new PropertyPath($propertyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyValues = &$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
$zval = array(
|
||||||
|
self::VALUE => $objectOrArray,
|
||||||
|
self::REF => &$objectOrArray,
|
||||||
|
);
|
||||||
|
$propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
|
||||||
|
$overwrite = true;
|
||||||
|
|
||||||
// Add the root object to the list
|
try {
|
||||||
array_unshift($propertyValues, array(
|
if (PHP_VERSION_ID < 70000 && false === self::$previousErrorHandler) {
|
||||||
self::VALUE => &$objectOrArray,
|
self::$previousErrorHandler = set_error_handler(self::$errorHandler);
|
||||||
self::IS_REF => true,
|
|
||||||
self::IS_REF_CHAINED => true,
|
|
||||||
));
|
|
||||||
|
|
||||||
$propertyMaxIndex = count($propertyValues) - 1;
|
|
||||||
|
|
||||||
for ($i = $propertyMaxIndex; $i >= 0; --$i) {
|
|
||||||
$objectOrArray = &$propertyValues[$i][self::VALUE];
|
|
||||||
|
|
||||||
$property = $propertyPath->getElement($i);
|
|
||||||
|
|
||||||
// You only need set value for current element if:
|
|
||||||
// 1. it's the parent of the last index element
|
|
||||||
// OR
|
|
||||||
// 2. its child is not passed by reference
|
|
||||||
//
|
|
||||||
// This may avoid uncessary value setting process for array elements.
|
|
||||||
// For example:
|
|
||||||
// '[a][b][c]' => 'old-value'
|
|
||||||
// If you want to change its value to 'new-value',
|
|
||||||
// you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]'
|
|
||||||
//
|
|
||||||
if ($i === $propertyMaxIndex || !$propertyValues[$i + 1][self::IS_REF]) {
|
|
||||||
if ($propertyPath->isIndex($i)) {
|
|
||||||
$this->writeIndex($objectOrArray, $property, $value);
|
|
||||||
} else {
|
|
||||||
$this->writeProperty($objectOrArray, $property, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if current element is an object
|
|
||||||
// OR
|
|
||||||
// if current element's reference chain is not broken - current element
|
|
||||||
// as well as all its ancients in the property path are all passed by reference,
|
|
||||||
// then there is no need to continue the value setting process
|
|
||||||
if (is_object($propertyValues[$i][self::VALUE]) || $propertyValues[$i][self::IS_REF_CHAINED]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = &$objectOrArray;
|
for ($i = count($propertyValues) - 1; 0 <= $i; --$i) {
|
||||||
|
$zval = $propertyValues[$i];
|
||||||
|
unset($propertyValues[$i]);
|
||||||
|
|
||||||
|
// You only need set value for current element if:
|
||||||
|
// 1. it's the parent of the last index element
|
||||||
|
// OR
|
||||||
|
// 2. its child is not passed by reference
|
||||||
|
//
|
||||||
|
// This may avoid uncessary value setting process for array elements.
|
||||||
|
// For example:
|
||||||
|
// '[a][b][c]' => 'old-value'
|
||||||
|
// If you want to change its value to 'new-value',
|
||||||
|
// you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]'
|
||||||
|
//
|
||||||
|
if ($overwrite) {
|
||||||
|
$property = $propertyPath->getElement($i);
|
||||||
|
|
||||||
|
if ($propertyPath->isIndex($i)) {
|
||||||
|
if ($overwrite = !isset($zval[self::REF])) {
|
||||||
|
$ref = &$zval[self::REF];
|
||||||
|
}
|
||||||
|
$this->writeIndex($zval, $property, $value);
|
||||||
|
if ($overwrite) {
|
||||||
|
$zval[self::VALUE] = $zval[self::REF];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->writeProperty($zval, $property, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if current element is an object
|
||||||
|
// OR
|
||||||
|
// if current element's reference chain is not broken - current element
|
||||||
|
// as well as all its ancients in the property path are all passed by reference,
|
||||||
|
// then there is no need to continue the value setting process
|
||||||
|
if (is_object($zval[self::VALUE]) || isset($zval[self::IS_REF_CHAINED])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $zval[self::VALUE];
|
||||||
|
}
|
||||||
|
} catch (\TypeError $e) {
|
||||||
|
try {
|
||||||
|
self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0);
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 70000 && false !== self::$previousErrorHandler) {
|
||||||
|
restore_error_handler();
|
||||||
|
self::$previousErrorHandler = false;
|
||||||
|
}
|
||||||
|
if (isset($e)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function handleError($type, $message, $file, $line, $context)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file']) {
|
||||||
|
$pos = strpos($message, $delim = 'must be of the type ') ?: strpos($message, $delim = 'must be an instance of ');
|
||||||
|
$pos += strlen($delim);
|
||||||
|
$type = $trace[$i]['args'][0];
|
||||||
|
$type = is_object($type) ? get_class($type) : gettype($type);
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given', substr($message, $pos, strpos($message, ',', $pos) - $pos), $type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +264,10 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
$zval = array(
|
||||||
|
self::VALUE => $objectOrArray,
|
||||||
|
);
|
||||||
|
$this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (AccessException $e) {
|
} catch (AccessException $e) {
|
||||||
|
@ -228,31 +287,26 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$propertyValues = $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
$zval = array(
|
||||||
|
|
||||||
// Add the root object to the list
|
|
||||||
array_unshift($propertyValues, array(
|
|
||||||
self::VALUE => $objectOrArray,
|
self::VALUE => $objectOrArray,
|
||||||
self::IS_REF => true,
|
);
|
||||||
self::IS_REF_CHAINED => true,
|
$propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
|
||||||
));
|
|
||||||
|
|
||||||
for ($i = count($propertyValues) - 1; $i >= 0; --$i) {
|
for ($i = count($propertyValues) - 1; 0 <= $i; --$i) {
|
||||||
$objectOrArray = $propertyValues[$i][self::VALUE];
|
$zval = $propertyValues[$i];
|
||||||
|
unset($propertyValues[$i]);
|
||||||
$property = $propertyPath->getElement($i);
|
|
||||||
|
|
||||||
if ($propertyPath->isIndex($i)) {
|
if ($propertyPath->isIndex($i)) {
|
||||||
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
if (!$zval[self::VALUE] instanceof \ArrayAccess && !is_array($zval[self::VALUE])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!$this->isPropertyWritable($objectOrArray, $property)) {
|
if (!$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_object($propertyValues[$i][self::VALUE]) || $propertyValues[$i][self::IS_REF_CHAINED]) {
|
if (is_object($zval[self::VALUE])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,83 +322,84 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
/**
|
/**
|
||||||
* Reads the path from an object up to a given path index.
|
* Reads the path from an object up to a given path index.
|
||||||
*
|
*
|
||||||
* @param object|array $objectOrArray The object or array to read from
|
* @param array $zval The array containing the object or array to read from
|
||||||
* @param PropertyPathInterface $propertyPath The property path to read
|
* @param PropertyPathInterface $propertyPath The property path to read
|
||||||
* @param int $lastIndex The index up to which should be read
|
* @param int $lastIndex The index up to which should be read
|
||||||
* @param bool $ignoreInvalidIndices Whether to ignore invalid indices
|
* @param bool $ignoreInvalidIndices Whether to ignore invalid indices or throw an exception
|
||||||
* or throw an exception
|
|
||||||
*
|
*
|
||||||
* @return array The values read in the path.
|
* @return array The values read in the path.
|
||||||
*
|
*
|
||||||
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
||||||
* @throws NoSuchIndexException If a non-existing index is accessed
|
* @throws NoSuchIndexException If a non-existing index is accessed
|
||||||
*/
|
*/
|
||||||
private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true)
|
private function readPropertiesUntil($zval, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true)
|
||||||
{
|
{
|
||||||
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
if (!is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) {
|
||||||
throw new UnexpectedTypeException($objectOrArray, $propertyPath, 0);
|
throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyValues = array();
|
// Add the root object to the list
|
||||||
|
$propertyValues = array($zval);
|
||||||
|
|
||||||
for ($i = 0; $i < $lastIndex; ++$i) {
|
for ($i = 0; $i < $lastIndex; ++$i) {
|
||||||
$property = $propertyPath->getElement($i);
|
$property = $propertyPath->getElement($i);
|
||||||
$isIndex = $propertyPath->isIndex($i);
|
$isIndex = $propertyPath->isIndex($i);
|
||||||
|
|
||||||
// Create missing nested arrays on demand
|
if ($isIndex) {
|
||||||
if ($isIndex &&
|
// Create missing nested arrays on demand
|
||||||
(
|
if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) ||
|
||||||
($objectOrArray instanceof \ArrayAccess && !isset($objectOrArray[$property])) ||
|
(is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !array_key_exists($property, $zval[self::VALUE]))
|
||||||
(is_array($objectOrArray) && !array_key_exists($property, $objectOrArray))
|
) {
|
||||||
)
|
if (!$ignoreInvalidIndices) {
|
||||||
) {
|
if (!is_array($zval[self::VALUE])) {
|
||||||
if (!$ignoreInvalidIndices) {
|
if (!$zval[self::VALUE] instanceof \Traversable) {
|
||||||
if (!is_array($objectOrArray)) {
|
throw new NoSuchIndexException(sprintf(
|
||||||
if (!$objectOrArray instanceof \Traversable) {
|
'Cannot read index "%s" while trying to traverse path "%s".',
|
||||||
throw new NoSuchIndexException(sprintf(
|
$property,
|
||||||
'Cannot read index "%s" while trying to traverse path "%s".',
|
(string) $propertyPath
|
||||||
$property,
|
));
|
||||||
(string) $propertyPath
|
}
|
||||||
));
|
|
||||||
|
$zval[self::VALUE] = iterator_to_array($zval[self::VALUE]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$objectOrArray = iterator_to_array($objectOrArray);
|
throw new NoSuchIndexException(sprintf(
|
||||||
|
'Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".',
|
||||||
|
$property,
|
||||||
|
(string) $propertyPath,
|
||||||
|
print_r(array_keys($zval[self::VALUE]), true)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NoSuchIndexException(sprintf(
|
if ($i + 1 < $propertyPath->getLength()) {
|
||||||
'Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".',
|
$zval[self::VALUE][$property] = array();
|
||||||
$property,
|
|
||||||
(string) $propertyPath,
|
if (isset($zval[self::REF])) {
|
||||||
print_r(array_keys($objectOrArray), true)
|
$zval[self::REF] = $zval[self::VALUE];
|
||||||
));
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($i + 1 < $propertyPath->getLength()) {
|
$zval = $this->readIndex($zval, $property);
|
||||||
$objectOrArray[$property] = array();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isIndex) {
|
|
||||||
$propertyValue = &$this->readIndex($objectOrArray, $property);
|
|
||||||
} else {
|
} else {
|
||||||
$propertyValue = &$this->readProperty($objectOrArray, $property);
|
$zval = $this->readProperty($zval, $property);
|
||||||
}
|
}
|
||||||
|
|
||||||
$objectOrArray = &$propertyValue[self::VALUE];
|
|
||||||
|
|
||||||
// the final value of the path must not be validated
|
// the final value of the path must not be validated
|
||||||
if ($i + 1 < $propertyPath->getLength() && !is_object($objectOrArray) && !is_array($objectOrArray)) {
|
if ($i + 1 < $propertyPath->getLength() && !is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) {
|
||||||
throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i + 1);
|
throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the IS_REF_CHAINED flag to true if:
|
if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) {
|
||||||
// current property is passed by reference and
|
// Set the IS_REF_CHAINED flag to true if:
|
||||||
// it is the first element in the property path or
|
// current property is passed by reference and
|
||||||
// the IS_REF_CHAINED flag of its parent element is true
|
// it is the first element in the property path or
|
||||||
// Basically, this flag is true only when the reference chain from the top element to current element is not broken
|
// the IS_REF_CHAINED flag of its parent element is true
|
||||||
$propertyValue[self::IS_REF_CHAINED] = $propertyValue[self::IS_REF] && (0 === $i || $propertyValues[$i - 1][self::IS_REF_CHAINED]);
|
// Basically, this flag is true only when the reference chain from the top element to current element is not broken
|
||||||
|
$zval[self::IS_REF_CHAINED] = true;
|
||||||
|
}
|
||||||
|
|
||||||
$propertyValues[] = &$propertyValue;
|
$propertyValues[] = $zval;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $propertyValues;
|
return $propertyValues;
|
||||||
|
@ -353,33 +408,30 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
/**
|
/**
|
||||||
* Reads a key from an array-like structure.
|
* Reads a key from an array-like structure.
|
||||||
*
|
*
|
||||||
* @param \ArrayAccess|array $array The array or \ArrayAccess object to read from
|
* @param array $zval The array containing the array or \ArrayAccess object to read from
|
||||||
* @param string|int $index The key to read
|
* @param string|int $index The key to read
|
||||||
*
|
*
|
||||||
* @return mixed The value of the key
|
* @return array The array containing the value of the key
|
||||||
*
|
*
|
||||||
* @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
|
* @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
|
||||||
*/
|
*/
|
||||||
private function &readIndex(&$array, $index)
|
private function readIndex($zval, $index)
|
||||||
{
|
{
|
||||||
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
if (!$zval[self::VALUE] instanceof \ArrayAccess && !is_array($zval[self::VALUE])) {
|
||||||
throw new NoSuchIndexException(sprintf('Cannot read index "%s" from object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_class($array)));
|
throw new NoSuchIndexException(sprintf('Cannot read index "%s" from object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_class($zval[self::VALUE])));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use an array instead of an object since performance is very crucial here
|
$result = self::$resultProto;
|
||||||
$result = array(
|
|
||||||
self::VALUE => null,
|
|
||||||
self::IS_REF => false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isset($array[$index])) {
|
if (isset($zval[self::VALUE][$index])) {
|
||||||
if (is_array($array)) {
|
$result[self::VALUE] = $zval[self::VALUE][$index];
|
||||||
$result[self::VALUE] = &$array[$index];
|
|
||||||
$result[self::IS_REF] = true;
|
if (!isset($zval[self::REF])) {
|
||||||
} else {
|
// Save creating references when doing read-only lookups
|
||||||
$result[self::VALUE] = $array[$index];
|
} elseif (is_array($zval[self::VALUE])) {
|
||||||
// Objects are always passed around by reference
|
$result[self::REF] = &$zval[self::REF][$index];
|
||||||
$result[self::IS_REF] = is_object($array[$index]) ? true : false;
|
} elseif (is_object($result[self::VALUE])) {
|
||||||
|
$result[self::REF] = $result[self::VALUE];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,39 +439,32 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the a property from an object or array.
|
* Reads the a property from an object.
|
||||||
*
|
*
|
||||||
* @param object $object The object to read from.
|
* @param array $zval The array containing the object to read from
|
||||||
* @param string $property The property to read.
|
* @param string $property The property to read.
|
||||||
*
|
*
|
||||||
* @return mixed The value of the read property
|
* @return array The array containing the value of the property
|
||||||
*
|
*
|
||||||
* @throws NoSuchPropertyException If the property does not exist or is not
|
* @throws NoSuchPropertyException If the property does not exist or is not public.
|
||||||
* public.
|
|
||||||
*/
|
*/
|
||||||
private function &readProperty(&$object, $property)
|
private function readProperty($zval, $property)
|
||||||
{
|
{
|
||||||
// Use an array instead of an object since performance is
|
if (!is_object($zval[self::VALUE])) {
|
||||||
// very crucial here
|
|
||||||
$result = array(
|
|
||||||
self::VALUE => null,
|
|
||||||
self::IS_REF => false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!is_object($object)) {
|
|
||||||
throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%s]" instead.', $property, $property));
|
throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%s]" instead.', $property, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
$access = $this->getReadAccessInfo($object, $property);
|
$result = self::$resultProto;
|
||||||
|
$object = $zval[self::VALUE];
|
||||||
|
$access = $this->getReadAccessInfo(get_class($object), $property);
|
||||||
|
|
||||||
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
|
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
|
||||||
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
||||||
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
||||||
if ($access[self::ACCESS_REF]) {
|
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
|
||||||
$result[self::VALUE] = &$object->{$access[self::ACCESS_NAME]};
|
|
||||||
$result[self::IS_REF] = true;
|
if ($access[self::ACCESS_REF] && isset($zval[self::REF])) {
|
||||||
} else {
|
$result[self::REF] = &$object->{$access[self::ACCESS_NAME]};
|
||||||
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
|
|
||||||
}
|
}
|
||||||
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
|
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
|
||||||
// Needed to support \stdClass instances. We need to explicitly
|
// Needed to support \stdClass instances. We need to explicitly
|
||||||
|
@ -428,8 +473,10 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
// returns true, consequently the following line will result in a
|
// returns true, consequently the following line will result in a
|
||||||
// fatal error.
|
// fatal error.
|
||||||
|
|
||||||
$result[self::VALUE] = &$object->$property;
|
$result[self::VALUE] = $object->$property;
|
||||||
$result[self::IS_REF] = true;
|
if (isset($zval[self::REF])) {
|
||||||
|
$result[self::REF] = &$object->$property;
|
||||||
|
}
|
||||||
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
|
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
|
||||||
// we call the getter and hope the __call do the job
|
// we call the getter and hope the __call do the job
|
||||||
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
||||||
|
@ -438,8 +485,8 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects are always passed around by reference
|
// Objects are always passed around by reference
|
||||||
if (is_object($result[self::VALUE])) {
|
if (isset($zval[self::REF]) && is_object($result[self::VALUE])) {
|
||||||
$result[self::IS_REF] = true;
|
$result[self::REF] = $result[self::VALUE];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
@ -448,21 +495,21 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
/**
|
/**
|
||||||
* Guesses how to read the property value.
|
* Guesses how to read the property value.
|
||||||
*
|
*
|
||||||
* @param string $object
|
* @param string $class
|
||||||
* @param string $property
|
* @param string $property
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function getReadAccessInfo($object, $property)
|
private function getReadAccessInfo($class, $property)
|
||||||
{
|
{
|
||||||
$key = get_class($object).'::'.$property;
|
$key = $class.'::'.$property;
|
||||||
|
|
||||||
if (isset($this->readPropertyCache[$key])) {
|
if (isset($this->readPropertyCache[$key])) {
|
||||||
$access = $this->readPropertyCache[$key];
|
$access = $this->readPropertyCache[$key];
|
||||||
} else {
|
} else {
|
||||||
$access = array();
|
$access = array();
|
||||||
|
|
||||||
$reflClass = new \ReflectionClass($object);
|
$reflClass = new \ReflectionClass($class);
|
||||||
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||||
$camelProp = $this->camelize($property);
|
$camelProp = $this->camelize($property);
|
||||||
$getter = 'get'.$camelProp;
|
$getter = 'get'.$camelProp;
|
||||||
|
@ -519,45 +566,45 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
/**
|
/**
|
||||||
* Sets the value of an index in a given array-accessible value.
|
* Sets the value of an index in a given array-accessible value.
|
||||||
*
|
*
|
||||||
* @param \ArrayAccess|array $array An array or \ArrayAccess object to write to
|
* @param array $zval The array containing the array or \ArrayAccess object to write to
|
||||||
* @param string|int $index The index to write at
|
* @param string|int $index The index to write at
|
||||||
* @param mixed $value The value to write
|
* @param mixed $value The value to write
|
||||||
*
|
*
|
||||||
* @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
|
* @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
|
||||||
*/
|
*/
|
||||||
private function writeIndex(&$array, $index, $value)
|
private function writeIndex($zval, $index, $value)
|
||||||
{
|
{
|
||||||
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
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($array)));
|
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])));
|
||||||
}
|
}
|
||||||
|
|
||||||
$array[$index] = $value;
|
$zval[self::REF][$index] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of a property in the given object.
|
* Sets the value of a property in the given object.
|
||||||
*
|
*
|
||||||
* @param object $object The object to write to
|
* @param array $zval The array containing the object to write to
|
||||||
* @param string $property The property to write
|
* @param string $property The property to write
|
||||||
* @param mixed $value The value to write
|
* @param mixed $value The value to write
|
||||||
*
|
*
|
||||||
* @throws NoSuchPropertyException If the property does not exist or is not
|
* @throws NoSuchPropertyException If the property does not exist or is not public.
|
||||||
* public.
|
|
||||||
*/
|
*/
|
||||||
private function writeProperty(&$object, $property, $value)
|
private function writeProperty($zval, $property, $value)
|
||||||
{
|
{
|
||||||
if (!is_object($object)) {
|
if (!is_object($zval[self::VALUE])) {
|
||||||
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
$access = $this->getWriteAccessInfo($object, $property, $value);
|
$object = $zval[self::VALUE];
|
||||||
|
$access = $this->getWriteAccessInfo(get_class($object), $property, $value);
|
||||||
|
|
||||||
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
|
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
|
||||||
$object->{$access[self::ACCESS_NAME]}($value);
|
$object->{$access[self::ACCESS_NAME]}($value);
|
||||||
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
||||||
$object->{$access[self::ACCESS_NAME]} = $value;
|
$object->{$access[self::ACCESS_NAME]} = $value;
|
||||||
} elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) {
|
} elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) {
|
||||||
$this->writeCollection($object, $property, $value, $access[self::ACCESS_ADDER], $access[self::ACCESS_REMOVER]);
|
$this->writeCollection($zval, $property, $value, $access[self::ACCESS_ADDER], $access[self::ACCESS_REMOVER]);
|
||||||
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
|
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
|
||||||
// Needed to support \stdClass instances. We need to explicitly
|
// Needed to support \stdClass instances. We need to explicitly
|
||||||
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
|
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
|
||||||
|
@ -574,72 +621,63 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts a collection-valued property by calling add*() and remove*()
|
* Adjusts a collection-valued property by calling add*() and remove*() methods.
|
||||||
* methods.
|
|
||||||
*
|
*
|
||||||
* @param object $object The object to write to
|
* @param array $zval The array containing the object to write to
|
||||||
* @param string $property The property to write
|
* @param string $property The property to write
|
||||||
* @param array|\Traversable $collection The collection to write
|
* @param array|\Traversable $collection The collection to write
|
||||||
* @param string $addMethod The add*() method
|
* @param string $addMethod The add*() method
|
||||||
* @param string $removeMethod The remove*() method
|
* @param string $removeMethod The remove*() method
|
||||||
*/
|
*/
|
||||||
private function writeCollection($object, $property, $collection, $addMethod, $removeMethod)
|
private function writeCollection($zval, $property, $collection, $addMethod, $removeMethod)
|
||||||
{
|
{
|
||||||
// At this point the add and remove methods have been found
|
// At this point the add and remove methods have been found
|
||||||
// Use iterator_to_array() instead of clone in order to prevent side effects
|
$previousValue = $this->readProperty($zval, $property);
|
||||||
// see https://github.com/symfony/symfony/issues/4670
|
$previousValue = $previousValue[self::VALUE];
|
||||||
$itemsToAdd = is_object($collection) ? iterator_to_array($collection) : $collection;
|
|
||||||
$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) {
|
if ($previousValue instanceof \Traversable) {
|
||||||
foreach ($previousValue as $previousItem) {
|
$previousValue = iterator_to_array($previousValue);
|
||||||
foreach ($collection as $key => $item) {
|
}
|
||||||
if ($item === $previousItem) {
|
if ($previousValue && is_array($previousValue)) {
|
||||||
// Item found, don't add
|
if (is_object($collection)) {
|
||||||
unset($itemsToAdd[$key]);
|
$collection = iterator_to_array($collection);
|
||||||
|
|
||||||
// Next $previousItem
|
|
||||||
continue 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Item not found, add to remove list
|
|
||||||
$itemToRemove[] = $previousItem;
|
|
||||||
}
|
}
|
||||||
|
foreach ($previousValue as $key => $item) {
|
||||||
|
if (!in_array($item, $collection, true)) {
|
||||||
|
unset($previousValue[$key]);
|
||||||
|
$zval[self::VALUE]->{$removeMethod}($item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$previousValue = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($itemToRemove as $item) {
|
foreach ($collection as $item) {
|
||||||
$object->{$removeMethod}($item);
|
if (!$previousValue || !in_array($item, $previousValue, true)) {
|
||||||
}
|
$zval[self::VALUE]->{$addMethod}($item);
|
||||||
|
}
|
||||||
foreach ($itemsToAdd as $item) {
|
|
||||||
$object->{$addMethod}($item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guesses how to write the property value.
|
* Guesses how to write the property value.
|
||||||
*
|
*
|
||||||
* @param string $object
|
* @param string $class
|
||||||
* @param string $property
|
* @param string $property
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function getWriteAccessInfo($object, $property, $value)
|
private function getWriteAccessInfo($class, $property, $value)
|
||||||
{
|
{
|
||||||
$key = get_class($object).'::'.$property;
|
$key = $class.'::'.$property;
|
||||||
|
|
||||||
if (isset($this->writePropertyCache[$key])) {
|
if (isset($this->writePropertyCache[$key])) {
|
||||||
$access = $this->writePropertyCache[$key];
|
$access = $this->writePropertyCache[$key];
|
||||||
} else {
|
} else {
|
||||||
$access = array();
|
$access = array();
|
||||||
|
|
||||||
$reflClass = new \ReflectionClass($object);
|
$reflClass = new \ReflectionClass($class);
|
||||||
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||||
$camelized = $this->camelize($property);
|
$camelized = $this->camelize($property);
|
||||||
$singulars = (array) StringUtil::singularify($camelized);
|
$singulars = (array) StringUtil::singularify($camelized);
|
||||||
|
@ -778,8 +816,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
* @param string $methodName The method name
|
* @param string $methodName The method name
|
||||||
* @param int $parameters The number of parameters
|
* @param int $parameters The number of parameters
|
||||||
*
|
*
|
||||||
* @return bool Whether the method is public and has $parameters
|
* @return bool Whether the method is public and has $parameters required parameters
|
||||||
* required parameters
|
|
||||||
*/
|
*/
|
||||||
private function isMethodAccessible(\ReflectionClass $class, $methodName, $parameters)
|
private function isMethodAccessible(\ReflectionClass $class, $methodName, $parameters)
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,8 +45,7 @@ interface PropertyAccessorInterface
|
||||||
*
|
*
|
||||||
* @throws Exception\InvalidArgumentException If the property path is invalid
|
* @throws Exception\InvalidArgumentException If the property path is invalid
|
||||||
* @throws Exception\AccessException If a property/index does not exist or is not public
|
* @throws Exception\AccessException If a property/index does not exist or is not public
|
||||||
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
|
* @throws Exception\UnexpectedTypeException If a value within the path is neither object nor array
|
||||||
* nor array
|
|
||||||
*/
|
*/
|
||||||
public function setValue(&$objectOrArray, $propertyPath, $value);
|
public function setValue(&$objectOrArray, $propertyPath, $value);
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
|
||||||
$remaining = $propertyPath;
|
$remaining = $propertyPath;
|
||||||
|
|
||||||
// first element is evaluated differently - no leading dot for properties
|
// first element is evaluated differently - no leading dot for properties
|
||||||
$pattern = '/^(([^\.\[]+)|\[([^\]]+)\])(.*)/';
|
$pattern = '/^(([^\.\[]++)|\[([^\]]++)\])(.*)/';
|
||||||
|
|
||||||
while (preg_match($pattern, $remaining, $matches)) {
|
while (preg_match($pattern, $remaining, $matches)) {
|
||||||
if ('' !== $matches[2]) {
|
if ('' !== $matches[2]) {
|
||||||
|
@ -111,7 +111,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
|
||||||
|
|
||||||
$position += strlen($matches[1]);
|
$position += strlen($matches[1]);
|
||||||
$remaining = $matches[4];
|
$remaining = $matches[4];
|
||||||
$pattern = '/^(\.([^\.|\[]+)|\[([^\]]+)\])(.*)/';
|
$pattern = '/^(\.([^\.|\[]++)|\[([^\]]++)\])(.*)/';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('' !== $remaining) {
|
if ('' !== $remaining) {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class TypeHinted
|
||||||
|
{
|
||||||
|
private $date;
|
||||||
|
|
||||||
|
public function setDate(\DateTime $date)
|
||||||
|
{
|
||||||
|
$this->date = $date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDate()
|
||||||
|
{
|
||||||
|
return $this->date;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
|
||||||
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
|
||||||
|
|
||||||
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
@ -510,4 +511,22 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
$this->assertEquals($value, $this->propertyAccessor->isWritable($object, $path));
|
$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->propertyAccessor->setValue(new TypeHinted(), 'date', 'This is a string, \DateTime excepted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetTypeHint()
|
||||||
|
{
|
||||||
|
$date = new \DateTime();
|
||||||
|
$object = new TypeHinted();
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($object, 'date', $date);
|
||||||
|
$this->assertSame($date, $object->getDate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@ use Symfony\Component\Validator\Exception\MissingOptionsException;
|
||||||
/**
|
/**
|
||||||
* Contains the properties of a constraint definition.
|
* Contains the properties of a constraint definition.
|
||||||
*
|
*
|
||||||
* A constraint can be defined on a class, an option or a getter method.
|
* A constraint can be defined on a class, a property or a getter method.
|
||||||
* The Constraint class encapsulates all the configuration required for
|
* The Constraint class encapsulates all the configuration required for
|
||||||
* validating this class, option or getter result successfully.
|
* validating this class, property or getter result successfully.
|
||||||
*
|
*
|
||||||
* Constraint instances are immutable and serializable.
|
* Constraint instances are immutable and serializable.
|
||||||
*
|
*
|
||||||
|
|
|
@ -93,7 +93,7 @@ class EmailValidator extends ConstraintValidator
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$host = substr($value, strpos($value, '@') + 1);
|
$host = substr($value, strrpos($value, '@') + 1);
|
||||||
|
|
||||||
// Check for host DNS resource records
|
// Check for host DNS resource records
|
||||||
if ($constraint->checkMX) {
|
if ($constraint->checkMX) {
|
||||||
|
|
|
@ -143,4 +143,16 @@ class EmailValidatorTest extends AbstractConstraintValidatorTest
|
||||||
array('AAAA', Email::HOST_CHECK_FAILED_ERROR),
|
array('AAAA', Email::HOST_CHECK_FAILED_ERROR),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testHostnameIsProperlyParsed()
|
||||||
|
{
|
||||||
|
DnsMock::withMockedHosts(array('baz.com' => array(array('type' => 'MX'))));
|
||||||
|
|
||||||
|
$this->validator->validate(
|
||||||
|
'"foo@bar"@baz.com',
|
||||||
|
new Email(array('checkMX' => true))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue