Skip to content

Commit fdaca31

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

File tree

2 files changed

+61
-84
lines changed

2 files changed

+61
-84
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 60 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,67 @@ 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+
static $errorHandler = array(__CLASS__, 'handleError');
138+
self::$previousErrorHandler = set_error_handler($errorHandler);
139+
}
136140

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

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

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

@@ -398,9 +443,7 @@ private function writeIndex(&$array, $index, $value)
398443
* @param string|null $singular The singular form of the property name or null
399444
* @param mixed $value The value to write
400445
*
401-
* @throws NoSuchPropertyException If the property does not exist or is not
402-
* public.
403-
* @throws UnexpectedTypeException
446+
* @throws NoSuchPropertyException If the property does not exist or is not public.
404447
*/
405448
private function writeProperty(&$object, $property, $singular, $value)
406449
{
@@ -411,7 +454,7 @@ private function writeProperty(&$object, $property, $singular, $value)
411454
$access = $this->getWriteAccessInfo($object, $property, $singular, $value);
412455

413456
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
414-
$this->callMethod($object, $access[self::ACCESS_NAME], $value);
457+
$object->{$access[self::ACCESS_NAME]}($value);
415458
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
416459
$object->{$access[self::ACCESS_NAME]} = $value;
417460
} elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) {
@@ -458,78 +501,12 @@ private function writeProperty(&$object, $property, $singular, $value)
458501

459502
$object->$property = $value;
460503
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
461-
$this->callMethod($object, $access[self::ACCESS_NAME], $value);
504+
$object->{$access[self::ACCESS_NAME]}($value);
462505
} else {
463506
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
464507
}
465508
}
466509

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-
533510
/**
534511
* Guesses how to write the property value.
535512
*

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