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 1/2] [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 3b9b49490da58..40ba789900721 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -400,6 +400,7 @@ private function writeIndex(&$array, $index, $value) * * @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 @@ private function writeProperty(&$object, $property, $singular, $value) $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 @@ private function writeProperty(&$object, $property, $singular, $value) $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 ecedabc134ef4..755f5ccb3d683 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 0000000000000..ca4c5745ae06c --- /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 51bc6eabc2af1..85ea84802790d 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\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 @@ public function getValidPropertyPaths() 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 2/2] [PropertyAccess] Reduce overhead of UnexpectedTypeException tracking --- .../PropertyAccess/PropertyAccessor.php | 142 ++++++++---------- .../PropertyAccessorInterface.php | 2 +- 2 files changed, 60 insertions(+), 84 deletions(-) diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 40ba789900721..cb00c093ceef1 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 @@ public function setValue(&$objectOrArray, $propertyPath, $value) self::IS_REF => true, )); - for ($i = count($propertyValues) - 1; $i >= 0; --$i) { - $objectOrArray = &$propertyValues[$i][self::VALUE]; + try { + if (PHP_VERSION_ID < 70000) { + self::$previousErrorHandler = set_error_handler(array(__CLASS__, 'handleError')); + } - if ($overwrite) { - $property = $propertyPath->getElement($i); - //$singular = $propertyPath->singulars[$i]; - $singular = null; + for ($i = count($propertyValues) - 1; $i >= 0; --$i) { + $objectOrArray = &$propertyValues[$i][self::VALUE]; - if ($propertyPath->isIndex($i)) { - $this->writeIndex($objectOrArray, $property, $value); - } else { - $this->writeProperty($objectOrArray, $property, $singular, $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; + } + } - $value = &$objectOrArray; - $overwrite = !$propertyValues[$i][self::IS_REF]; + /** + * @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 @@ private function writeIndex(&$array, $index, $value) * @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 @@ private function writeProperty(&$object, $property, $singular, $value) $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 @@ private function writeProperty(&$object, $property, $singular, $value) $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 755f5ccb3d683..90f69b39259ff 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); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy