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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
while ($node = $node->parentNode) {
|
||||
if (1 === $node->nodeType) {
|
||||
if (XML_ELEMENT_NODE === $node->nodeType) {
|
||||
$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(
|
||||
'positive' => 2147483647,
|
||||
'negative' => -2147483648,
|
||||
);
|
||||
private static $int32Max = 2147483647;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
'positive' => 9223372036854775807,
|
||||
'negative' => -9223372036854775808,
|
||||
);
|
||||
private static $int64Max = 9223372036854775807;
|
||||
|
||||
private static $enSymbols = array(
|
||||
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 $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
|
||||
*/
|
||||
|
@ -835,7 +829,7 @@ class NumberFormatter
|
|||
*/
|
||||
private function getInt32Value($value)
|
||||
{
|
||||
if ($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) {
|
||||
if ($value > self::$int32Max || $value < -self::$int32Max - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -848,20 +842,18 @@ class NumberFormatter
|
|||
* @param mixed $value The value to be converted
|
||||
*
|
||||
* @return int|float|false The converted value
|
||||
*
|
||||
* @see https://bugs.php.net/bug.php?id=59597 Bug #59597
|
||||
*/
|
||||
private function getInt64Value($value)
|
||||
{
|
||||
if ($value > self::$int64Range['positive'] || $value < self::$int64Range['negative']) {
|
||||
if ($value > self::$int64Max || $value < -self::$int64Max - 1) {
|
||||
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
|
||||
// The negative PHP_INT_MAX was being converted to float
|
||||
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')))
|
||||
) {
|
||||
return (int) $value;
|
||||
|
@ -874,7 +866,7 @@ class NumberFormatter
|
|||
// 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
|
||||
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)) &&
|
||||
!(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))
|
||||
) {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Symfony\Component\PropertyAccess;
|
||||
|
||||
use Symfony\Component\PropertyAccess\Exception\AccessException;
|
||||
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
||||
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
||||
|
@ -21,6 +22,7 @@ use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
|||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class PropertyAccessor implements PropertyAccessorInterface
|
||||
{
|
||||
|
@ -32,7 +34,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
const IS_REF = 1;
|
||||
const REF = 1;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -113,6 +115,9 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
* @var 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
|
||||
|
@ -136,7 +141,10 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
$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];
|
||||
}
|
||||
|
@ -150,51 +158,99 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
$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
|
||||
array_unshift($propertyValues, array(
|
||||
self::VALUE => &$objectOrArray,
|
||||
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;
|
||||
}
|
||||
try {
|
||||
if (PHP_VERSION_ID < 70000 && false === self::$previousErrorHandler) {
|
||||
self::$previousErrorHandler = set_error_handler(self::$errorHandler);
|
||||
}
|
||||
|
||||
$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 {
|
||||
$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
||||
$zval = array(
|
||||
self::VALUE => $objectOrArray,
|
||||
);
|
||||
$this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
|
||||
|
||||
return true;
|
||||
} catch (AccessException $e) {
|
||||
|
@ -228,31 +287,26 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
}
|
||||
|
||||
try {
|
||||
$propertyValues = $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
||||
|
||||
// Add the root object to the list
|
||||
array_unshift($propertyValues, array(
|
||||
$zval = array(
|
||||
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) {
|
||||
$objectOrArray = $propertyValues[$i][self::VALUE];
|
||||
|
||||
$property = $propertyPath->getElement($i);
|
||||
for ($i = count($propertyValues) - 1; 0 <= $i; --$i) {
|
||||
$zval = $propertyValues[$i];
|
||||
unset($propertyValues[$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;
|
||||
}
|
||||
} else {
|
||||
if (!$this->isPropertyWritable($objectOrArray, $property)) {
|
||||
if (!$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($propertyValues[$i][self::VALUE]) || $propertyValues[$i][self::IS_REF_CHAINED]) {
|
||||
if (is_object($zval[self::VALUE])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -268,83 +322,84 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
/**
|
||||
* 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 int $lastIndex The index up to which should be read
|
||||
* @param bool $ignoreInvalidIndices Whether to ignore invalid indices
|
||||
* or throw an exception
|
||||
* @param bool $ignoreInvalidIndices Whether to ignore invalid indices or throw an exception
|
||||
*
|
||||
* @return array The values read in the path.
|
||||
*
|
||||
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
||||
* @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)) {
|
||||
throw new UnexpectedTypeException($objectOrArray, $propertyPath, 0);
|
||||
if (!is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) {
|
||||
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) {
|
||||
$property = $propertyPath->getElement($i);
|
||||
$isIndex = $propertyPath->isIndex($i);
|
||||
|
||||
// Create missing nested arrays on demand
|
||||
if ($isIndex &&
|
||||
(
|
||||
($objectOrArray instanceof \ArrayAccess && !isset($objectOrArray[$property])) ||
|
||||
(is_array($objectOrArray) && !array_key_exists($property, $objectOrArray))
|
||||
)
|
||||
) {
|
||||
if (!$ignoreInvalidIndices) {
|
||||
if (!is_array($objectOrArray)) {
|
||||
if (!$objectOrArray instanceof \Traversable) {
|
||||
throw new NoSuchIndexException(sprintf(
|
||||
'Cannot read index "%s" while trying to traverse path "%s".',
|
||||
$property,
|
||||
(string) $propertyPath
|
||||
));
|
||||
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]))
|
||||
) {
|
||||
if (!$ignoreInvalidIndices) {
|
||||
if (!is_array($zval[self::VALUE])) {
|
||||
if (!$zval[self::VALUE] instanceof \Traversable) {
|
||||
throw new NoSuchIndexException(sprintf(
|
||||
'Cannot read index "%s" while trying to traverse path "%s".',
|
||||
$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(
|
||||
'Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".',
|
||||
$property,
|
||||
(string) $propertyPath,
|
||||
print_r(array_keys($objectOrArray), true)
|
||||
));
|
||||
if ($i + 1 < $propertyPath->getLength()) {
|
||||
$zval[self::VALUE][$property] = array();
|
||||
|
||||
if (isset($zval[self::REF])) {
|
||||
$zval[self::REF] = $zval[self::VALUE];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($i + 1 < $propertyPath->getLength()) {
|
||||
$objectOrArray[$property] = array();
|
||||
}
|
||||
}
|
||||
|
||||
if ($isIndex) {
|
||||
$propertyValue = &$this->readIndex($objectOrArray, $property);
|
||||
$zval = $this->readIndex($zval, $property);
|
||||
} 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
|
||||
if ($i + 1 < $propertyPath->getLength() && !is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||
throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i + 1);
|
||||
if ($i + 1 < $propertyPath->getLength() && !is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) {
|
||||
throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1);
|
||||
}
|
||||
|
||||
// Set the IS_REF_CHAINED flag to true if:
|
||||
// current property is passed by reference and
|
||||
// it is the first element in the property path or
|
||||
// the IS_REF_CHAINED flag of its parent element is true
|
||||
// Basically, this flag is true only when the reference chain from the top element to current element is not broken
|
||||
$propertyValue[self::IS_REF_CHAINED] = $propertyValue[self::IS_REF] && (0 === $i || $propertyValues[$i - 1][self::IS_REF_CHAINED]);
|
||||
if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) {
|
||||
// Set the IS_REF_CHAINED flag to true if:
|
||||
// current property is passed by reference and
|
||||
// it is the first element in the property path or
|
||||
// the IS_REF_CHAINED flag of its parent element is true
|
||||
// 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;
|
||||
|
@ -353,33 +408,30 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
/**
|
||||
* Reads a key from an array-like structure.
|
||||
*
|
||||
* @param \ArrayAccess|array $array The array or \ArrayAccess object to read from
|
||||
* @param string|int $index The key to read
|
||||
* @param array $zval The array containing the array or \ArrayAccess object to read from
|
||||
* @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
|
||||
*/
|
||||
private function &readIndex(&$array, $index)
|
||||
private function readIndex($zval, $index)
|
||||
{
|
||||
if (!$array instanceof \ArrayAccess && !is_array($array)) {
|
||||
throw new NoSuchIndexException(sprintf('Cannot read index "%s" from object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_class($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($zval[self::VALUE])));
|
||||
}
|
||||
|
||||
// Use an array instead of an object since performance is very crucial here
|
||||
$result = array(
|
||||
self::VALUE => null,
|
||||
self::IS_REF => false,
|
||||
);
|
||||
$result = self::$resultProto;
|
||||
|
||||
if (isset($array[$index])) {
|
||||
if (is_array($array)) {
|
||||
$result[self::VALUE] = &$array[$index];
|
||||
$result[self::IS_REF] = true;
|
||||
} else {
|
||||
$result[self::VALUE] = $array[$index];
|
||||
// Objects are always passed around by reference
|
||||
$result[self::IS_REF] = is_object($array[$index]) ? true : false;
|
||||
if (isset($zval[self::VALUE][$index])) {
|
||||
$result[self::VALUE] = $zval[self::VALUE][$index];
|
||||
|
||||
if (!isset($zval[self::REF])) {
|
||||
// Save creating references when doing read-only lookups
|
||||
} elseif (is_array($zval[self::VALUE])) {
|
||||
$result[self::REF] = &$zval[self::REF][$index];
|
||||
} 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.
|
||||
*
|
||||
* @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
|
||||
* public.
|
||||
* @throws NoSuchPropertyException If the property does not exist or is not public.
|
||||
*/
|
||||
private function &readProperty(&$object, $property)
|
||||
private function readProperty($zval, $property)
|
||||
{
|
||||
// Use an array instead of an object since performance is
|
||||
// very crucial here
|
||||
$result = array(
|
||||
self::VALUE => null,
|
||||
self::IS_REF => false,
|
||||
);
|
||||
|
||||
if (!is_object($object)) {
|
||||
if (!is_object($zval[self::VALUE])) {
|
||||
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]) {
|
||||
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
||||
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
||||
if ($access[self::ACCESS_REF]) {
|
||||
$result[self::VALUE] = &$object->{$access[self::ACCESS_NAME]};
|
||||
$result[self::IS_REF] = true;
|
||||
} else {
|
||||
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
|
||||
$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
|
||||
|
@ -428,8 +473,10 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
// returns true, consequently the following line will result in a
|
||||
// fatal error.
|
||||
|
||||
$result[self::VALUE] = &$object->$property;
|
||||
$result[self::IS_REF] = true;
|
||||
$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]}();
|
||||
|
@ -438,8 +485,8 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
}
|
||||
|
||||
// Objects are always passed around by reference
|
||||
if (is_object($result[self::VALUE])) {
|
||||
$result[self::IS_REF] = true;
|
||||
if (isset($zval[self::REF]) && is_object($result[self::VALUE])) {
|
||||
$result[self::REF] = $result[self::VALUE];
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@ -448,21 +495,21 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
/**
|
||||
* Guesses how to read the property value.
|
||||
*
|
||||
* @param string $object
|
||||
* @param string $class
|
||||
* @param string $property
|
||||
*
|
||||
* @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])) {
|
||||
$access = $this->readPropertyCache[$key];
|
||||
} else {
|
||||
$access = array();
|
||||
|
||||
$reflClass = new \ReflectionClass($object);
|
||||
$reflClass = new \ReflectionClass($class);
|
||||
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||
$camelProp = $this->camelize($property);
|
||||
$getter = 'get'.$camelProp;
|
||||
|
@ -519,45 +566,45 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
/**
|
||||
* 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 string|int $index The index to write at
|
||||
* @param mixed $value The value to write
|
||||
* @param array $zval The array containing the array or \ArrayAccess object to write to
|
||||
* @param string|int $index The index to write at
|
||||
* @param mixed $value The value to write
|
||||
*
|
||||
* @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)) {
|
||||
throw new NoSuchIndexException(sprintf('Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($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($zval[self::VALUE])));
|
||||
}
|
||||
|
||||
$array[$index] = $value;
|
||||
$zval[self::REF][$index] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mixed $value The value to write
|
||||
*
|
||||
* @throws NoSuchPropertyException If the property does not exist or is not
|
||||
* public.
|
||||
* @throws NoSuchPropertyException If the property does not exist or is not 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));
|
||||
}
|
||||
|
||||
$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]) {
|
||||
$object->{$access[self::ACCESS_NAME]}($value);
|
||||
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
||||
$object->{$access[self::ACCESS_NAME]} = $value;
|
||||
} 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)) {
|
||||
// Needed to support \stdClass instances. We need to explicitly
|
||||
// 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*()
|
||||
* methods.
|
||||
* Adjusts a collection-valued property by calling add*() and remove*() 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 array|\Traversable $collection The collection to write
|
||||
* @param string $addMethod The add*() method
|
||||
* @param string $removeMethod The remove*() method
|
||||
*/
|
||||
private function writeCollection($object, $property, $collection, $addMethod, $removeMethod)
|
||||
private function writeCollection($zval, $property, $collection, $addMethod, $removeMethod)
|
||||
{
|
||||
// At this point the add and remove methods have been found
|
||||
// Use iterator_to_array() instead of clone in order to prevent side effects
|
||||
// see https://github.com/symfony/symfony/issues/4670
|
||||
$itemsToAdd = is_object($collection) ? iterator_to_array($collection) : $collection;
|
||||
$itemToRemove = array();
|
||||
$propertyValue = &$this->readProperty($object, $property);
|
||||
$previousValue = $propertyValue[self::VALUE];
|
||||
// remove reference to avoid modifications
|
||||
unset($propertyValue);
|
||||
$previousValue = $this->readProperty($zval, $property);
|
||||
$previousValue = $previousValue[self::VALUE];
|
||||
|
||||
if (is_array($previousValue) || $previousValue instanceof \Traversable) {
|
||||
foreach ($previousValue as $previousItem) {
|
||||
foreach ($collection as $key => $item) {
|
||||
if ($item === $previousItem) {
|
||||
// Item found, don't add
|
||||
unset($itemsToAdd[$key]);
|
||||
|
||||
// Next $previousItem
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Item not found, add to remove list
|
||||
$itemToRemove[] = $previousItem;
|
||||
if ($previousValue instanceof \Traversable) {
|
||||
$previousValue = iterator_to_array($previousValue);
|
||||
}
|
||||
if ($previousValue && is_array($previousValue)) {
|
||||
if (is_object($collection)) {
|
||||
$collection = iterator_to_array($collection);
|
||||
}
|
||||
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) {
|
||||
$object->{$removeMethod}($item);
|
||||
}
|
||||
|
||||
foreach ($itemsToAdd as $item) {
|
||||
$object->{$addMethod}($item);
|
||||
foreach ($collection as $item) {
|
||||
if (!$previousValue || !in_array($item, $previousValue, true)) {
|
||||
$zval[self::VALUE]->{$addMethod}($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses how to write the property value.
|
||||
*
|
||||
* @param string $object
|
||||
* @param string $class
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
*
|
||||
* @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])) {
|
||||
$access = $this->writePropertyCache[$key];
|
||||
} else {
|
||||
$access = array();
|
||||
|
||||
$reflClass = new \ReflectionClass($object);
|
||||
$reflClass = new \ReflectionClass($class);
|
||||
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||
$camelized = $this->camelize($property);
|
||||
$singulars = (array) StringUtil::singularify($camelized);
|
||||
|
@ -778,8 +816,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
* @param string $methodName The method name
|
||||
* @param int $parameters The number of parameters
|
||||
*
|
||||
* @return bool Whether the method is public and has $parameters
|
||||
* required parameters
|
||||
* @return bool Whether the method is public and has $parameters required 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\AccessException If a property/index does not exist or is not public
|
||||
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
|
||||
* nor array
|
||||
* @throws Exception\UnexpectedTypeException If a value within the path is neither object nor array
|
||||
*/
|
||||
public function setValue(&$objectOrArray, $propertyPath, $value);
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
|
|||
$remaining = $propertyPath;
|
||||
|
||||
// first element is evaluated differently - no leading dot for properties
|
||||
$pattern = '/^(([^\.\[]+)|\[([^\]]+)\])(.*)/';
|
||||
$pattern = '/^(([^\.\[]++)|\[([^\]]++)\])(.*)/';
|
||||
|
||||
while (preg_match($pattern, $remaining, $matches)) {
|
||||
if ('' !== $matches[2]) {
|
||||
|
@ -111,7 +111,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
|
|||
|
||||
$position += strlen($matches[1]);
|
||||
$remaining = $matches[4];
|
||||
$pattern = '/^(\.([^\.|\[]+)|\[([^\]]+)\])(.*)/';
|
||||
$pattern = '/^(\.([^\.|\[]++)|\[([^\]]++)\])(.*)/';
|
||||
}
|
||||
|
||||
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\TestClassSetValue;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
|
||||
|
||||
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -510,4 +511,22 @@ class PropertyAccessorTest extends \PHPUnit_Framework_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->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.
|
||||
*
|
||||
* 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
|
||||
* validating this class, option or getter result successfully.
|
||||
* validating this class, property or getter result successfully.
|
||||
*
|
||||
* Constraint instances are immutable and serializable.
|
||||
*
|
||||
|
|
|
@ -93,7 +93,7 @@ class EmailValidator extends ConstraintValidator
|
|||
return;
|
||||
}
|
||||
|
||||
$host = substr($value, strpos($value, '@') + 1);
|
||||
$host = substr($value, strrpos($value, '@') + 1);
|
||||
|
||||
// Check for host DNS resource records
|
||||
if ($constraint->checkMX) {
|
||||
|
|
|
@ -143,4 +143,16 @@ class EmailValidatorTest extends AbstractConstraintValidatorTest
|
|||
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