From ca56be1c8ac563c942b895d13055195a464a2a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 16 Mar 2016 19:32:37 +0100 Subject: [PATCH 1/8] [FrameworkBundle] Add tests for the Controller class --- .../Tests/Controller/ControllerTest.php | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php index 9e120160e5..89b143bbe2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php @@ -39,4 +39,122 @@ class ControllerTest extends TestCase $response = $controller->forward('a_controller'); $this->assertEquals('xml--fr', $response->getContent()); } + + 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()); + } } From 10c8d5eadbc100b4cc63c56d40633edfeec70db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 9 Feb 2016 17:23:20 +0100 Subject: [PATCH 2/8] [PropertyAccess] Throw an UnexpectedTypeException when the type do not match --- .../PropertyAccess/PropertyAccessor.php | 71 ++++++++++++++++++- .../PropertyAccessorInterface.php | 1 - .../Tests/Fixtures/TypeHinted.php | 30 ++++++++ .../Tests/PropertyAccessorTest.php | 19 +++++ 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 3b9b49490d..40ba789900 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -400,6 +400,7 @@ class PropertyAccessor implements PropertyAccessorInterface * * @throws NoSuchPropertyException If the property does not exist or is not * public. + * @throws UnexpectedTypeException */ private function writeProperty(&$object, $property, $singular, $value) { @@ -410,7 +411,7 @@ class PropertyAccessor implements PropertyAccessorInterface $access = $this->getWriteAccessInfo($object, $property, $singular, $value); if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) { - $object->{$access[self::ACCESS_NAME]}($value); + $this->callMethod($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]) { @@ -457,12 +458,78 @@ class PropertyAccessor implements PropertyAccessorInterface $object->$property = $value; } elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) { - $object->{$access[self::ACCESS_NAME]}($value); + $this->callMethod($object, $access[self::ACCESS_NAME], $value); } else { throw new NoSuchPropertyException($access[self::ACCESS_NAME]); } } + /** + * Throws a {@see UnexpectedTypeException} as in PHP 7 when using PHP 5. + * + * @param object $object + * @param string $method + * @param mixed $value + * + * @throws UnexpectedTypeException + * @throws \Exception + */ + private function callMethod($object, $method, $value) { + if (PHP_MAJOR_VERSION >= 7) { + try { + $object->{$method}($value); + } catch (\TypeError $e) { + throw $this->createUnexpectedTypeException($object, $method, $value); + } + + return; + } + + $that = $this; + set_error_handler(function ($errno, $errstr) use ($object, $method, $value, $that) { + if (E_RECOVERABLE_ERROR === $errno && false !== strpos($errstr, sprintf('passed to %s::%s() must', get_class($object), $method))) { + throw $that->createUnexpectedTypeException($object, $method, $value); + } + + return false; + }); + + try { + $object->{$method}($value); + restore_error_handler(); + } catch (\Exception $e) { + // Cannot use finally in 5.5 because of https://bugs.php.net/bug.php?id=67047 + restore_error_handler(); + + throw $e; + } + } + + /** + * Creates an UnexpectedTypeException. + * + * @param object $object + * @param string $method + * @param mixed $value + * + * @return UnexpectedTypeException + */ + private function createUnexpectedTypeException($object, $method, $value) + { + $reflectionMethod = new \ReflectionMethod($object, $method); + $parameters = $reflectionMethod->getParameters(); + + $expectedType = 'unknown'; + if (isset($parameters[0])) { + $class = $parameters[0]->getClass(); + if (null !== $class) { + $expectedType = $class->getName(); + } + } + + return new UnexpectedTypeException($value, $expectedType); + } + /** * Guesses how to write the property value. * diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php index ecedabc134..755f5ccb3d 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php @@ -45,7 +45,6 @@ interface PropertyAccessorInterface * * @throws Exception\NoSuchPropertyException If a property does not exist or is not public. * @throws Exception\UnexpectedTypeException If a value within the path is neither object - * nor array */ public function setValue(&$objectOrArray, $propertyPath, $value); diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php new file mode 100644 index 0000000000..ca4c5745ae --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php @@ -0,0 +1,30 @@ + + * + * 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 + */ +class TypeHinted +{ + private $date; + + public function setDate(\DateTime $date) + { + $this->date = $date; + } + + public function getDate() + { + return $this->date; + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 51bc6eabc2..85ea848027 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -16,6 +16,7 @@ use Symfony\Component\PropertyAccess\Tests\Fixtures\Author; use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician; use Symfony\Component\PropertyAccess\Tests\Fixtures\MagicianCall; use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object; +use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted; class PropertyAccessorTest extends \PHPUnit_Framework_TestCase { @@ -403,4 +404,22 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase array(array('root' => array('index' => array())), '[root][index][firstName]', null), ); } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException + * @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()); + } } From 5fe2b06bc42be80eeb1219accddb389453a243bf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Mar 2016 09:10:46 +0100 Subject: [PATCH 3/8] [PropertyAccess] Reduce overhead of UnexpectedTypeException tracking --- .../PropertyAccess/PropertyAccessor.php | 148 ++++++++---------- .../PropertyAccessorInterface.php | 2 +- 2 files changed, 63 insertions(+), 87 deletions(-) diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 40ba789900..cb00c093ce 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -89,6 +89,7 @@ class PropertyAccessor implements PropertyAccessorInterface private $magicCall; private $readPropertyCache = array(); private $writePropertyCache = array(); + private static $previousErrorHandler; /** * Should not be used by application code. Use @@ -131,23 +132,66 @@ class PropertyAccessor implements PropertyAccessorInterface self::IS_REF => true, )); - for ($i = count($propertyValues) - 1; $i >= 0; --$i) { - $objectOrArray = &$propertyValues[$i][self::VALUE]; - - if ($overwrite) { - $property = $propertyPath->getElement($i); - //$singular = $propertyPath->singulars[$i]; - $singular = null; - - if ($propertyPath->isIndex($i)) { - $this->writeIndex($objectOrArray, $property, $value); - } else { - $this->writeProperty($objectOrArray, $property, $singular, $value); - } + try { + if (PHP_VERSION_ID < 70000) { + self::$previousErrorHandler = set_error_handler(array(__CLASS__, 'handleError')); } - $value = &$objectOrArray; - $overwrite = !$propertyValues[$i][self::IS_REF]; + for ($i = count($propertyValues) - 1; $i >= 0; --$i) { + $objectOrArray = &$propertyValues[$i][self::VALUE]; + + if ($overwrite) { + $property = $propertyPath->getElement($i); + //$singular = $propertyPath->singulars[$i]; + $singular = null; + + if ($propertyPath->isIndex($i)) { + $this->writeIndex($objectOrArray, $property, $value); + } else { + $this->writeProperty($objectOrArray, $property, $singular, $value); + } + } + + $value = &$objectOrArray; + $overwrite = !$propertyValues[$i][self::IS_REF]; + } + } catch (\TypeError $e) { + try { + self::throwUnexpectedTypeException($e->getMessage(), $e->getTrace(), 0); + } catch (UnexpectedTypeException $e) { + } + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + if (PHP_VERSION_ID < 70000) { + restore_error_handler(); + self::$previousErrorHandler = null; + } + if (isset($e)) { + throw $e; + } + } + + /** + * @internal + */ + public static function handleError($type, $message, $file, $line, $context) + { + if (E_RECOVERABLE_ERROR === $type) { + self::throwUnexpectedTypeException($message, debug_backtrace(false), 1); + } + + return null !== self::$previousErrorHandler && false !== call_user_func(self::$previousErrorHandler, $type, $message, $file, $line, $context); + } + + private static function throwUnexpectedTypeException($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); + + throw new UnexpectedTypeException($trace[$i]['args'][0], substr($message, $pos, strpos($message, ',', $pos) - $pos)); } } @@ -398,9 +442,7 @@ class PropertyAccessor implements PropertyAccessorInterface * @param string|null $singular The singular form of the property name or null * @param mixed $value The value to write * - * @throws NoSuchPropertyException If the property does not exist or is not - * public. - * @throws UnexpectedTypeException + * @throws NoSuchPropertyException If the property does not exist or is not public. */ private function writeProperty(&$object, $property, $singular, $value) { @@ -411,7 +453,7 @@ class PropertyAccessor implements PropertyAccessorInterface $access = $this->getWriteAccessInfo($object, $property, $singular, $value); if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) { - $this->callMethod($object, $access[self::ACCESS_NAME], $value); + $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]) { @@ -458,78 +500,12 @@ class PropertyAccessor implements PropertyAccessorInterface $object->$property = $value; } elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) { - $this->callMethod($object, $access[self::ACCESS_NAME], $value); + $object->{$access[self::ACCESS_NAME]}($value); } else { throw new NoSuchPropertyException($access[self::ACCESS_NAME]); } } - /** - * Throws a {@see UnexpectedTypeException} as in PHP 7 when using PHP 5. - * - * @param object $object - * @param string $method - * @param mixed $value - * - * @throws UnexpectedTypeException - * @throws \Exception - */ - private function callMethod($object, $method, $value) { - if (PHP_MAJOR_VERSION >= 7) { - try { - $object->{$method}($value); - } catch (\TypeError $e) { - throw $this->createUnexpectedTypeException($object, $method, $value); - } - - return; - } - - $that = $this; - set_error_handler(function ($errno, $errstr) use ($object, $method, $value, $that) { - if (E_RECOVERABLE_ERROR === $errno && false !== strpos($errstr, sprintf('passed to %s::%s() must', get_class($object), $method))) { - throw $that->createUnexpectedTypeException($object, $method, $value); - } - - return false; - }); - - try { - $object->{$method}($value); - restore_error_handler(); - } catch (\Exception $e) { - // Cannot use finally in 5.5 because of https://bugs.php.net/bug.php?id=67047 - restore_error_handler(); - - throw $e; - } - } - - /** - * Creates an UnexpectedTypeException. - * - * @param object $object - * @param string $method - * @param mixed $value - * - * @return UnexpectedTypeException - */ - private function createUnexpectedTypeException($object, $method, $value) - { - $reflectionMethod = new \ReflectionMethod($object, $method); - $parameters = $reflectionMethod->getParameters(); - - $expectedType = 'unknown'; - if (isset($parameters[0])) { - $class = $parameters[0]->getClass(); - if (null !== $class) { - $expectedType = $class->getName(); - } - } - - return new UnexpectedTypeException($value, $expectedType); - } - /** * Guesses how to write the property value. * diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php index 755f5ccb3d..90f69b3925 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php @@ -44,7 +44,7 @@ interface PropertyAccessorInterface * @param mixed $value The value to set at the end of the property path * * @throws Exception\NoSuchPropertyException If a property 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. */ public function setValue(&$objectOrArray, $propertyPath, $value); From cffea91c8e39d0688897843058ebcb48d319c691 Mon Sep 17 00:00:00 2001 From: Richard van Laak Date: Thu, 17 Mar 2016 10:11:54 +0100 Subject: [PATCH 4/8] Use XML_ELEMENT_NODE in nodeType check --- src/Symfony/Component/DomCrawler/Crawler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 79615185dd..fbc0b0af97 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -455,7 +455,7 @@ class Crawler extends \SplObjectStorage $nodes = array(); while ($node = $node->parentNode) { - if (1 === $node->nodeType) { + if (XML_ELEMENT_NODE === $node->nodeType) { $nodes[] = $node; } } From 999c0a5501866c38d37f9dd2c4acde893003c0a4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Mar 2016 12:09:45 +0100 Subject: [PATCH 5/8] [NumberFormatter] Fix invalid numeric literal on PHP 7 --- .../Intl/NumberFormatter/NumberFormatter.php | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index f89ce8a469..7118a03515 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -213,24 +213,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', '@', ','), @@ -508,7 +502,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 */ @@ -795,7 +789,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; } @@ -808,20 +802,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; @@ -834,7 +826,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')) ) { From c551bd17fc940095ef511fcb5443b3c3038ac7f9 Mon Sep 17 00:00:00 2001 From: natechicago Date: Wed, 16 Mar 2016 21:58:22 -0500 Subject: [PATCH 6/8] [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols --- .../Validator/Constraints/EmailValidator.php | 2 +- .../Tests/Constraints/EmailValidatorTest.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Constraints/EmailValidator.php b/src/Symfony/Component/Validator/Constraints/EmailValidator.php index c8c3c5fc72..ec94664efd 100644 --- a/src/Symfony/Component/Validator/Constraints/EmailValidator.php +++ b/src/Symfony/Component/Validator/Constraints/EmailValidator.php @@ -37,7 +37,7 @@ class EmailValidator extends ConstraintValidator $valid = filter_var($value, FILTER_VALIDATE_EMAIL); if ($valid) { - $host = substr($value, strpos($value, '@') + 1); + $host = substr($value, strrpos($value, '@') + 1); // Check for host DNS resource records if ($valid && $constraint->checkMX) { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php index 10d17b5c68..b2d74bac65 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php @@ -125,4 +125,16 @@ class EmailValidatorTest extends AbstractConstraintValidatorTest array('AAAA', true), ); } + + 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(); + } } From 72940d758816b2e630f185797f5a82ffc349a15d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Mar 2016 15:00:21 +0100 Subject: [PATCH 7/8] [PropertyAccess] Remove most ref mismatches to improve perf --- .../PropertyAccess/PropertyAccessor.php | 281 +++++++++--------- .../Component/PropertyAccess/PropertyPath.php | 4 +- 2 files changed, 135 insertions(+), 150 deletions(-) diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index cb00c093ce..2265618c6f 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -18,6 +18,7 @@ use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; * Default implementation of {@link PropertyAccessorInterface}. * * @author Bernhard Schussek + * @author Nicolas Grekas */ class PropertyAccessor implements PropertyAccessorInterface { @@ -29,7 +30,7 @@ class PropertyAccessor implements PropertyAccessorInterface /** * @internal */ - const IS_REF = 1; + const REF = 1; /** * @internal @@ -90,6 +91,8 @@ class PropertyAccessor implements PropertyAccessorInterface private $readPropertyCache = array(); private $writePropertyCache = array(); private static $previousErrorHandler; + private static $errorHandler = array(__CLASS__, 'handleError'); + private static $resultProto = array(self::VALUE => null); /** * Should not be used by application code. Use @@ -109,7 +112,10 @@ class PropertyAccessor implements PropertyAccessorInterface $propertyPath = new PropertyPath($propertyPath); } - $propertyValues = &$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength()); + $zval = array( + self::VALUE => $objectOrArray, + ); + $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength()); return $propertyValues[count($propertyValues) - 1][self::VALUE]; } @@ -123,37 +129,39 @@ 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, - )); - try { if (PHP_VERSION_ID < 70000) { - self::$previousErrorHandler = set_error_handler(array(__CLASS__, 'handleError')); + self::$previousErrorHandler = set_error_handler(self::$errorHandler); } - for ($i = count($propertyValues) - 1; $i >= 0; --$i) { - $objectOrArray = &$propertyValues[$i][self::VALUE]; + for ($i = count($propertyValues) - 1; 0 <= $i; --$i) { + $zval = $propertyValues[$i]; + unset($propertyValues[$i]); if ($overwrite) { $property = $propertyPath->getElement($i); - //$singular = $propertyPath->singulars[$i]; - $singular = null; if ($propertyPath->isIndex($i)) { - $this->writeIndex($objectOrArray, $property, $value); + 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($objectOrArray, $property, $singular, $value); + $this->writeProperty($zval, $property, $value); } } - $value = &$objectOrArray; - $overwrite = !$propertyValues[$i][self::IS_REF]; + $value = $zval[self::VALUE]; } } catch (\TypeError $e) { try { @@ -198,53 +206,51 @@ 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 PropertyPathInterface $propertyPath The property path to read - * @param int $lastIndex The index up to which should be read + * @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 * * @return array The values read in the path. * * @throws UnexpectedTypeException If a value within the path is neither object nor array. */ - private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex) + private function readPropertiesUntil($zval, PropertyPathInterface $propertyPath, $lastIndex) { - if (!is_object($objectOrArray) && !is_array($objectOrArray)) { - throw new UnexpectedTypeException($objectOrArray, 'object or array'); + if (!is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) { + throw new UnexpectedTypeException($zval[self::VALUE], 'object or array'); } - $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 ($i + 1 < $propertyPath->getLength()) { - $objectOrArray[$property] = array(); - } - } - if ($isIndex) { - $propertyValue = &$this->readIndex($objectOrArray, $property); - } else { - $propertyValue = &$this->readProperty($objectOrArray, $property); - } + // Create missing nested arrays on demand + if ($i + 1 < $propertyPath->getLength() && ( + ($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])) + )) { + $zval[self::VALUE][$property] = array(); - $objectOrArray = &$propertyValue[self::VALUE]; + if (isset($zval[self::REF])) { + $zval[self::REF] = $zval[self::VALUE]; + } + } + + $zval = $this->readIndex($zval, $property); + } else { + $zval = $this->readProperty($zval, $property); + } // 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, 'object or array'); + if ($i + 1 < $propertyPath->getLength() && !is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) { + throw new UnexpectedTypeException($zval[self::VALUE], 'object or array'); } - $propertyValues[] = &$propertyValue; + $propertyValues[] = $zval; } return $propertyValues; @@ -253,33 +259,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 NoSuchPropertyException 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 NoSuchPropertyException(sprintf('Index "%s" cannot be read 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 NoSuchPropertyException(sprintf('Index "%s" cannot be read 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]; } } @@ -287,39 +290,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 should 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 @@ -328,8 +324,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]}(); @@ -338,8 +336,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; @@ -348,21 +346,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; @@ -387,9 +385,6 @@ class PropertyAccessor implements PropertyAccessorInterface $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; $access[self::ACCESS_NAME] = $property; $access[self::ACCESS_REF] = true; - - $result[self::VALUE] = &$object->$property; - $result[self::IS_REF] = true; } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { // we call the getter and hope the __call do the job $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC; @@ -419,38 +414,38 @@ class PropertyAccessor implements PropertyAccessorInterface /** * Sets the value of the property at the given index in the path. * - * @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 NoSuchPropertyException 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 NoSuchPropertyException(sprintf('Index "%s" cannot be modified 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 NoSuchPropertyException(sprintf('Index "%s" cannot be modified 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 the property at the given index in the path. * - * @param object|array $object The object or array to write to - * @param string $property The property to write - * @param string|null $singular The singular form of the property name or null - * @param mixed $value The value to write + * @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. */ - private function writeProperty(&$object, $property, $singular, $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, $singular, $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); @@ -458,38 +453,30 @@ class PropertyAccessor implements PropertyAccessorInterface $object->{$access[self::ACCESS_NAME]} = $value; } elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) { // At this point the add and remove methods have been found - // Use iterator_to_array() instead of clone in order to prevent side effects - // see https://github.com/symfony/symfony/issues/4670 - $itemsToAdd = is_object($value) ? iterator_to_array($value) : $value; - $itemToRemove = array(); - $propertyValue = &$this->readProperty($object, $property); - $previousValue = $propertyValue[self::VALUE]; - // remove reference to avoid modifications - unset($propertyValue); + $previousValue = $this->readProperty($zval, $property); + $previousValue = $previousValue[self::VALUE]; - if (is_array($previousValue) || $previousValue instanceof \Traversable) { - foreach ($previousValue as $previousItem) { - foreach ($value as $key => $item) { - if ($item === $previousItem) { - // Item found, don't add - unset($itemsToAdd[$key]); - - // Next $previousItem - continue 2; - } - } - - // Item not found, add to remove list - $itemToRemove[] = $previousItem; + if ($previousValue instanceof \Traversable) { + $previousValue = iterator_to_array($previousValue); + } + if ($previousValue && is_array($previousValue)) { + if (is_object($value)) { + $value = iterator_to_array($value); } + foreach ($previousValue as $key => $item) { + if (!in_array($item, $value, true)) { + unset($previousValue[$key]); + $object->{$access[self::ACCESS_REMOVER]}($item); + } + } + } else { + $previousValue = false; } - foreach ($itemToRemove as $item) { - $object->{$access[self::ACCESS_REMOVER]}($item); - } - - foreach ($itemsToAdd as $item) { - $object->{$access[self::ACCESS_ADDER]}($item); + foreach ($value as $item) { + if (!$previousValue || !in_array($item, $previousValue, true)) { + $object->{$access[self::ACCESS_ADDER]}($item); + } } } elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) { // Needed to support \stdClass instances. We need to explicitly @@ -509,16 +496,15 @@ class PropertyAccessor implements PropertyAccessorInterface /** * Guesses how to write the property value. * - * @param string $object - * @param string $property - * @param string|null $singular - * @param mixed $value + * @param string $class + * @param string $property + * @param mixed $value * * @return array */ - private function getWriteAccessInfo($object, $property, $singular, $value) + private function getWriteAccessInfo($class, $property, $value) { - $key = get_class($object).'::'.$property; + $key = $class.'::'.$property; $guessedAdders = ''; if (isset($this->writePropertyCache[$key])) { @@ -526,12 +512,12 @@ class PropertyAccessor implements PropertyAccessorInterface } else { $access = array(); - $reflClass = new \ReflectionClass($object); + $reflClass = new \ReflectionClass($class); $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property); $plural = $this->camelize($property); // Any of the two methods is required, but not yet known - $singulars = null !== $singular ? array($singular) : (array) StringUtil::singularify($plural); + $singulars = (array) StringUtil::singularify($plural); if (is_array($value) || $value instanceof \Traversable) { $methods = $this->findAdderAndRemover($reflClass, $singulars); @@ -638,8 +624,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 isAccessible(\ReflectionClass $class, $methodName, $parameters) { diff --git a/src/Symfony/Component/PropertyAccess/PropertyPath.php b/src/Symfony/Component/PropertyAccess/PropertyPath.php index 4d964c2d8a..dfd6c9588f 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyPath.php +++ b/src/Symfony/Component/PropertyAccess/PropertyPath.php @@ -91,7 +91,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]) { @@ -106,7 +106,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface $position += strlen($matches[1]); $remaining = $matches[4]; - $pattern = '/^(\.(\w+)|\[([^\]]+)\])(.*)/'; + $pattern = '/^(\.(\w++)|\[([^\]]++)\])(.*)/'; } if ('' !== $remaining) { From e38d954c590a6258e2774e5ec313667d1abfb965 Mon Sep 17 00:00:00 2001 From: Miroslav Sustek Date: Fri, 18 Mar 2016 21:39:03 +0100 Subject: [PATCH 8/8] [Validator] use correct term for a property in docblock (not "option") --- src/Symfony/Component/Validator/Constraint.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraint.php b/src/Symfony/Component/Validator/Constraint.php index 8268cba1b1..5f5f1f7e80 100644 --- a/src/Symfony/Component/Validator/Constraint.php +++ b/src/Symfony/Component/Validator/Constraint.php @@ -18,9 +18,9 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; /** * 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. *