[PropertyAccess] Throw an UnexpectedTypeException when the type do not match

This commit is contained in:
Kévin Dunglas 2016-02-09 17:23:20 +01:00 committed by Nicolas Grekas
parent 56624e6a9d
commit 10c8d5eadb
4 changed files with 118 additions and 3 deletions

View File

@ -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.
*

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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());
}
}