8000 [PropertyAccess] Reduce overhead of UnexpectedTypeException tracking · symfony/symfony@5fe2b06 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5fe2b06

Browse files
[PropertyAccess] Reduce overhead of UnexpectedTypeException tracking
1 parent 10c8d5e commit 5fe2b06

File tree

2 files changed

+60
-84
lines changed

2 files changed

+60
-84
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 59 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class PropertyAccessor implements PropertyAccessorInterface
8989
private $magicCall;
9090
private $readPropertyCache = array();
9191
private $writePropertyCache = array();
92+
private static $previousErrorHandler;
9293

9394
/**
9495
* Should not be used by application code. Use
@@ -131,23 +132,66 @@ public function setValue(&$objectOrArray, $propertyPath, $value)
131132
self::IS_REF => true,
132133
));
133134

134-
for ($i = count($propertyValues) - 1; $i >= 0; --$i) {
135-
$objectOrArray = &$propertyValues[$i][self::VALUE];
135+
try {
136+
if (PHP_VERSION_ID < 70000) {
137+
self::$previousErrorHandler = set_error_handler(array(__CLASS__, 'handleError'));
138+
}
136139

137-
if ($overwrite) {
138-
$property = $propertyPath->getElement($i);
139-
//$singular = $propertyPath->singulars[$i];
140-
$singular = null;
140+
for ($i = count($propertyValues) - 1; $i >= 0; --$i) {
141+
$objectOrArray = &$propertyValues[$i][self::VALUE];
141142

142-
if ($propertyPath->isIndex($i)) {
143-
$this->writeIndex($objectOrArray, $property, $value);
144-
} else {
145-
$this->writeProperty($objectOrArray, $property, $singular, $value);
143+
if ($overwrite) {
144+
$property = $propertyPath->getElement($i);
145+
//$singular = $propertyPath->singulars[$i];
146+
$singular = null;
147+
148+
if ($propertyPath->isIndex($i)) {
149+
$this->writeIndex($objectOrArray, $property, $value);
150+
} else {
151+
$this->writeProperty($objectOrArray, $property, $singular, $value);
152+
}
146153
}
154+
155+
$value = &$objectOrArray;
156+
$overwrite = !$propertyValues[$i][self::IS_REF];
157+
}
158+
} catch (\TypeError $e) {
159+
try {
160+
self::throwUnexpectedTypeException($e->getMessage(), $e->getTrace(), 0);
161+
} catch (UnexpectedTypeException $e) {
147162
}
163+
} catch (\Exception $e) {
164+
} catch (\Throwable $e) {
165+
}
166+
167+
if (PHP_VERSION_ID < 70000) {
168+
restore_error_handler();
169+
self::$previousErrorHandler = null;
170+
}
171+
if (isset($e)) {
172+
throw $e;
173+
}
174+
}
148175

149-
$value = &$objectOrArray;
150-
$overwrite = !$propertyValues[$i][self::IS_REF];
176+
/**
177+
* @internal
178+
*/
179+
public static function handleError($type, $message, $file, $line, $context)
180+
{
181+
if (E_RECOVERABLE_ERROR === $type) {
182+
self::throwUnexpectedTypeException($message, debug_backtrace(false), 1);
183+
}
184+
185+
return null !== self::$previousErrorHandler && false !== call_user_func(self::$previousErrorHandler, $type, $message, $file, $line, $context);
186+
}
187+
188+
private static function throwUnexpectedTypeException($message, $trace, $i)
189+
{
190+
if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file']) {
191+
$pos = strpos($message, $delim = 'must be of the type ') ?: strpos($message, $delim = 'must be an instance of ');
192+
$pos += strlen($delim);
193+
194+
throw new UnexpectedTypeException($trace[$i]['args'][0], substr($message, $pos, strpos($message, ',', $pos) - $pos));
151195
}
152196
}
153197

@@ -398,9 +442,7 @@ private function writeIndex(&$array, $index, $value)
398442
* @param string|null $singular The singular form of the property name or null
399443
* @param mixed $value The value to write
400444
*
401-
* @throws NoSuchPropertyException If the property does not exist or is not
402-
* public.
403-
* @throws UnexpectedTypeException
445+
* @throws NoSuchPropertyException If the property does not exist or is not public.
404446
*/
405447
private function writeProperty(&$object, $property, $singular, $value)
406448
{
@@ -411,7 +453,7 @@ private function writeProperty(&$object, $property, $singular, $value)
411453
$access = $this->getWriteAccessInfo($object, $property, $singular, $value);
412454

413455
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
414-
$this->callMethod($object, $access[self::ACCESS_NAME], $value);
456+
$object->{$access[self::ACCESS_NAME]}($value);
415457
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
416458
$object->{$access[self::ACCESS_NAME]} = $value;
417459
} elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) {
@@ -458,78 +500,12 @@ private function writeProperty(&$object, $property, $singular, $value)
458500

459501
$object->$property = $value;
460502
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
461-
$this->callMethod($object, $access[self::ACCESS_NAME], $value);
503+
$object->{$access[self::ACCESS_NAME]}($value);
462504
} else {
463505
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
464506
}
465507
}
466508

467-
/**
468-
* Throws a {@see UnexpectedTypeException} as in PHP 7 when using PHP 5.
469-
*
470-
* @param object $object
471-
* @param string $method
472-
* @param mixed $value
473-
*
474-
* @throws UnexpectedTypeException
475-
* @throws \Exception
476-
*/
477-
private function callMethod($object, $method, $value) {
478-
if (PHP_MAJOR_VERSION >= 7) {
479-
try {
480-
$object->{$method}($value);
481-
} catch (\TypeError $e) {
482-
throw $this->createUnexpectedTypeException($object, $method, $value);
483-
}
484-
485-
return;
486-
}
487-
488-
$that = $this;
489-
set_error_handler(function ($errno, $errstr) use ($object, $method, $value, $that) {
490-
if (E_RECOVERABLE_ERROR === $errno && false !== strpos($errstr, sprintf('passed to %s::%s() must', get_class($object), $method))) {
491-
throw $that->createUnexpectedTypeException($object, $method, $value);
492-
}
493-
494-
return false;
495-
});
496-
497-
try {
498-
$object->{$method}($value);
499-
restore_error_handler();
500-
} catch (\Exception $e) {
501-
// Cannot use finally in 5.5 because of https://bugs.php.net/bug.php?id=67047
502-
restore_error_handler();
503-
504-
throw $e;
505-
}
506-
}
507-
508-
/**
509-
* Creates an UnexpectedTypeException.
510-
*
511-
* @param object $object
512-
* @param string $method
513-
* @param mixed $value
514-
*
515-
* @return UnexpectedTypeException
516-
*/
517-
private function createUnexpectedTypeException($object, $method, $value)
518-
{
519-
$reflectionMethod = new \ReflectionMethod($object, $method);
520-
$parameters = $reflectionMethod->getParameters();
521-
522-
$expectedType = 'unknown';
523-
if (isset($parameters[0])) {
524-
$class = $parameters[0]->getClass();
525-
if (null !== $class) {
526-
$expectedType = $class->getName();
527-
}
528-
}
529-
530-
return new UnexpectedTypeException($value, $expectedType);
531-
}
532-
533509
/**
534510
* Guesses how to write the property value.
535511
*

src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ interface PropertyAccessorInterface
4444
* @param mixed $value The value to set at the end of the property path
4545
*
4646
* @throws Exception\NoSuchPropertyException If a property does not exist or is not public.
47-
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
47+
* @throws Exception\UnexpectedTypeException If a value within the path is neither object nor array.
4848
*/
4949
public function setValue(&$objectOrArray, $propertyPath, $value);
5050

0 commit comments

Comments
 (0)
0