8000 feature #17738 [PropertyAccess] Throw an InvalidArgumentException whe… · symfony/symfony@48f05ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 48f05ec

Browse files
committed
feature #17738 [PropertyAccess] Throw an InvalidArgumentException when the type do not match (dunglas)
This PR was squashed before being merged into the 3.1-dev branch (closes #17738). Discussion ---------- [PropertyAccess] Throw an InvalidArgumentException when the type do not match | Q | A | ------------- | --- | Bug fix? | no (?) | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a Currently, when the Property Access Component call a setter with a value not matching its typehint, a `\TypeError` is thrown with PHP 7 and a `PHP Catchable fatal error` with PHP 5. This PR make the component returning an `InvalidArgumentException` with both version. It's a (better) alternative to #17660 (the hardening part) to make the Symfony Serializer (and probably many other pieces of code) more robust when types do not match. /cc @csarrazi @mRoca @blazarecki Commits ------- e70fdbc [PropertyAccess] Throw an InvalidArgumentException when the type do not match
2 parents 07aa280 + e70fdbc commit 48f05ec

File tree

5 files changed

+75
-5
lines changed

5 files changed

+75
-5
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ private function writeIndex(&$array, $index, $value)
543543
*
544544
* @throws NoSuchPropertyException If the property does not exist or is not
545545
* public.
546+
* @throws \TypeError
546547
*/
547548
private function writeProperty(&$object, $property, $value)
548549
{
@@ -553,7 +554,7 @@ private function writeProperty(&$object, $property, $value)
553554
$access = $this->getWriteAccessInfo($object, $property, $value);
554555

555556
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
556-
$object->{$access[self::ACCESS_NAME]}($value);
557+
$this->callMethod($object, $access[self::ACCESS_NAME], $value);
557558
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
558559
$object->{$access[self::ACCESS_NAME]} = $value;
559560
} elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) {
@@ -567,12 +568,48 @@ private function writeProperty(&$object, $property, $value)
567568

568569
$object->$property = $value;
569570
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
570-
$object->{$access[self::ACCESS_NAME]}($value);
571+
$this->callMethod($object, $access[self::ACCESS_NAME], $value);
571572
} else {
572573
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
573574
}
574575
}
575576

577+
/**
578+
* Throws a {@see \TypeError} as in PHP 7 when using PHP 5.
579+
*
580+
* @param object $object
581+
* @param string $method
582+
* @param mixed $value
583+
*
584+
* @throws \TypeError
585+
* @throws \Exception
586+
*/
587+
private function callMethod($object, $method, $value) {
588+
if (PHP_MAJOR_VERSION >= 7) {
589+
$object->{$method}($value);
590+
591+
return;
592+
}
593+
594+
set_error_handler(function ($errno, $errstr) use ($object, $method) {
595+
if (E_RECOVERABLE_ERROR === $errno && false !== strpos($errstr, sprintf('passed to %s::%s() must', get_class($object), $method))) {
596+
throw new \TypeError($errstr);
597+
}
598+
599+
return false;
600+
});
601+
602+
try {
603+
$object->{$method}($value);
604+
restore_error_handler();
605+
} catch (\Exception $e) {
606+
// Cannot use finally in 5.5 because of https://bugs.php.net/bug.php?id=67047
607+
restore_error_handler();
608+
609+
throw $e;
610+
}
611+
}
612+
576613
/**
577614
* Adjusts a collection-valued property by calling add*() and remove*()
578615
* methods.
@@ -582,6 +619,8 @@ private function writeProperty(&$object, $property, $value)
582619
* @param array|\Traversable $collection The collection to write
583620
* @param string $addMethod The add*() method
584621
* @param string $removeMethod The remove*() method
622+
*
623+
* @throws \TypeError
585624
*/
586625
private function writeCollection($object, $property, $collection, $addMethod, $removeMethod)
587626
{
@@ -613,11 +652,11 @@ private function writeCollection($object, $property, $collection, $addMethod, $r
613652
}
614653

615654
foreach ($itemToRemove as $item) {
616-
$object->{$removeMethod}($item);
655+
$this->callMethod($object, $removeMethod, $item);
617656
}
618657

619658
foreach ($itemsToAdd as $item) {
620-
$object->{$addMethod}($item);
659+
$this->callMethod($object, $addMethod, $item);
621660
}
622661
}
623662

src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ interface PropertyAccessorInterface
4747
* @throws Exception\AccessException If a property/index does not exist or is not public
4848
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
4949
* nor array
50+
* @throws \TypeError If a the type of the value does not match the type
51+
* of the parameter of the mutator method
5052
*/
5153
public function setValue(&$objectOrArray, $propertyPath, $value);
5254

src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class TestClass
2626
private $publicIsAccessor;
2727
private $publicHasAccessor;
2828
private $publicGetter;
29+
private $date;
2930

3031
public function __construct($value)
3132
{
@@ -173,4 +174,14 @@ public function getPublicGetter()
173174
{
174175
return $this->publicGetter;
175176
}
177+
178+
public function setDate(\DateTimeInterface $date)
179+
{
180+
$this->date = $date;
181+
}
182+
183+
public function getDate()
184+
{
185+
return $this->date;
186+
}
176187
}

src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,4 +510,21 @@ public function testIsWritableForReferenceChainIssue($object, $path, $value)
510510
{
511511
$this->assertEquals($value, $this->propertyAccessor->isWritable($object, $path));
512512
}
513+
514+
/**
515+
* @expectedException \TypeError
516+
*/
517+
public function testThrowTypeError()
518+
{
519+
$this->propertyAccessor->setValue(new TestClass('Kévin'), 'date', 'This is a string, \DateTime excepted.');
520+
}
521+
522+
public function testSetTypeHint()
523+
{
524+
$date = new \DateTimeImmutable();
525+
$object = new TestClass('Kévin');
526+
527+
$this->propertyAccessor->setValue($object, 'date', $date);
528+
$this->assertSame($date, $object->getDate());
529+
}
513530
}

src/Symfony/Component/PropertyAccess/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
}
1717
],
1818
"require": {
19-
"php": ">=5.5.9"
19+
"php": ">=5.5.9",
20+
"symfony/polyfill-php70": "~1.0"
2021
},
2122
"autoload": {
2223
"psr-4": { "Symfony\\Component\\PropertyAccess\\": "" },

0 commit comments

Comments
 (0)
0