Skip to content

Commit 6952a4a

Browse files
Rely on reflection to access null properties
1 parent 494f010 commit 6952a4a

File tree

1 file changed

+49
-6
lines changed

1 file changed

+49
-6
lines changed

src/Extension/CoreExtension.php

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,11 +1624,13 @@ public static function getAttribute(Environment $env, Source $source, $object, $
16241624
{
16251625
// array
16261626
if (Template::METHOD_CALL !== $type) {
1627-
$arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item;
1627+
$arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item = (string) $item;
16281628

1629-
if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object)))
1630-
|| ($object instanceof \ArrayAccess && isset($object[$arrayItem]))
1631-
) {
1629+
if (match (true) {
1630+
\is_array($object) => \array_key_exists($arrayItem, $object),
1631+
$object instanceof \ArrayAccess => $object->offsetExists($arrayItem),
1632+
default => false,
1633+
}) {
16321634
if ($isDefinedTest) {
16331635
return true;
16341636
}
@@ -1671,6 +1673,8 @@ public static function getAttribute(Environment $env, Source $source, $object, $
16711673
}
16721674
}
16731675

1676+
$item = (string) $item;
1677+
16741678
if (!\is_object($object)) {
16751679
if ($isDefinedTest) {
16761680
return false;
@@ -1697,7 +1701,23 @@ public static function getAttribute(Environment $env, Source $source, $object, $
16971701

16981702
// object property
16991703
if (Template::METHOD_CALL !== $type) {
1700-
if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) {
1704+
static $propertyCheckers = [];
1705+
1706+
if (isset($object->$item)
1707+
|| ($propertyCheckers[$object::class][$item] ??= self::getPropertyChecker($object::class, $item))($object, $item)
1708+
) {
1709+
if ($isDefinedTest) {
1710+
return true;
1711+
}
1712+
1713+
if ($sandboxed) {
1714+
$env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source);
1715+
}
1716+
1717+
return $object->$item;
1718+
}
1719+
1720+
if ($object instanceof \DateTimeInterface && \in_array($item, ['date', 'timezone', 'timezone_type'], true)) {
17011721
if ($isDefinedTest) {
17021722
return true;
17031723
}
@@ -1706,7 +1726,7 @@ public static function getAttribute(Environment $env, Source $source, $object, $
17061726
$env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source);
17071727
}
17081728

1709-
return isset($object->$item) ? $object->$item : ((array) $object)[(string) $item];
1729+
return ((array) $object)[$item];
17101730
}
17111731

17121732
if (\defined($object::class.'::'.$item)) {
@@ -2055,4 +2075,27 @@ public static function parseAttributeFunction(Parser $parser, Node $fakeNode, $a
20552075

20562076
return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line);
20572077
}
2078+
2079+
private static function getPropertyChecker(string $class, string $property): \Closure
2080+
{
2081+
static $classReflectors = [];
2082+
2083+
$class = $classReflectors[$class] ??= new \ReflectionClass($class);
2084+
2085+
if (!$class->hasProperty($property)) {
2086+
static $propertyExists;
2087+
2088+
return $propertyExists ??= \Closure::fromCallable('property_exists');
2089+
}
2090+
2091+
$property = $class->getProperty($property);
2092+
2093+
if (!$property->isPublic()) {
2094+
static $false;
2095+
2096+
return $false ??= static fn () => false;
2097+
}
2098+
2099+
return static fn ($object) => $property->isInitialized($object);
2100+
}
20582101
}

0 commit comments

Comments
 (0)