diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index f8f80ac2f0c..3c221d74bb8 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -102,6 +102,9 @@ final class CoreExtension extends AbstractExtension private $numberFormat = [0, '.', ',']; private $timezone = null; + private static $classReflectors = []; + private static $propertyReflectors = []; + /** * Sets the default format to be used by the date filter. * @@ -1626,9 +1629,11 @@ public static function getAttribute(Environment $env, Source $source, $object, $ if (Template::METHOD_CALL !== $type) { $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; - if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) - || ($object instanceof \ArrayAccess && isset($object[$arrayItem])) - ) { + if (match (true) { + \is_array($object) => \array_key_exists($arrayItem, $object), + $object instanceof \ArrayAccess => $object->offsetExists($arrayItem), + default => false, + }) { if ($isDefinedTest) { return true; } @@ -1697,7 +1702,12 @@ public static function getAttribute(Environment $env, Source $source, $object, $ // object property if (Template::METHOD_CALL !== $type) { - if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + $propertyReflector = false; + if (match (true) { + isset($object->$item) => true, + ($propertyReflector = self::$propertyReflectors[$object::class][$item] ??= self::getPropertyReflector($object, $item))->isInitialized($object) => true, + default => false, + }) { if ($isDefinedTest) { return true; } @@ -1706,7 +1716,7 @@ public static function getAttribute(Environment $env, Source $source, $object, $ $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); } - return isset($object->$item) ? $object->$item : ((array) $object)[(string) $item]; + return $propertyReflector ? $propertyReflector->getValue($object) : $object->$item; } if (\defined($object::class.'::'.$item)) { @@ -2055,4 +2065,54 @@ public static function parseAttributeFunction(Parser $parser, Node $fakeNode, $a return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line); } + + private static function getPropertyReflector(object $object, string $property): \ReflectionProperty + { + $class = self::$classReflectors[$object::class] ??= new \ReflectionClass($object::class); + + if ($class->hasProperty($property)) { + $reflector = $class->getProperty($property); + + if ($reflector->isPublic()) { + return $reflector; + } + + return new class () extends \ReflectionProperty { + public function __construct() + { + } + + public function isInitialized(?object $object = null): bool + { + return false; + } + }; + } + + return new class ($property) extends \ReflectionProperty { + private $value; + + public function __construct(private string $property) + { + } + + public function isInitialized(?object $object = null): bool + { + $arrayCast = (array) $object; + + if (\array_key_exists($this->property, $arrayCast)) { + $this->value = $arrayCast[$this->property]; + + return true; + } + + return false; + } + + public function getValue(?object $object = null): mixed + { + return $this->value; + } + }; + } }