Skip to content

Commit

Permalink
Rely on reflection to access null properties
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas committed Nov 7, 2024
1 parent d72ae24 commit 5cd1ff1
Showing 1 changed file with 45 additions and 6 deletions.
51 changes: 45 additions & 6 deletions src/Extension/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -1642,7 +1642,7 @@ public static function getAttribute(Environment $env, Source $source, $object, $

// array
if (Template::METHOD_CALL !== $type) {
$arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item;
$arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item = (string) $item;

if ($sandboxed && $object instanceof \ArrayAccess && !\in_array($object::class, self::ARRAY_LIKE_CLASSES, true)) {
try {
Expand All @@ -1652,9 +1652,11 @@ public static function getAttribute(Environment $env, Source $source, $object, $
}
}

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;
}
Expand Down Expand Up @@ -1697,6 +1699,8 @@ public static function getAttribute(Environment $env, Source $source, $object, $
}
}

$item = (string) $item;

if (!\is_object($object)) {
if ($isDefinedTest) {
return false;
Expand Down Expand Up @@ -1731,12 +1735,24 @@ public static function getAttribute(Environment $env, Source $source, $object, $
}
}

if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) {
static $propertyCheckers = [];

if (isset($object->$item)
|| ($propertyCheckers[$object::class][$item] ??= self::getPropertyChecker($object::class, $item))($object, $item)
) {
if ($isDefinedTest) {
return true;
}

return isset($object->$item) ? $object->$item : ((array) $object)[(string) $item];
return $object->$item;
}

if ($object instanceof \DateTimeInterface && \in_array($item, ['date', 'timezone', 'timezone_type'], true)) {
if ($isDefinedTest) {
return true;
}

return ((array) $object)[$item];
}

if (\defined($object::class.'::'.$item)) {
Expand Down Expand Up @@ -2099,4 +2115,27 @@ 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 getPropertyChecker(string $class, string $property): \Closure
{
static $classReflectors = [];

$class = $classReflectors[$class] ??= new \ReflectionClass($class);

if (!$class->hasProperty($property)) {
static $propertyExists;

return $propertyExists ??= \Closure::fromCallable('property_exists');
}

$property = $class->getProperty($property);

if (!$property->isPublic()) {
static $false;

return $false ??= static fn () => false;
}

return static fn ($object) => $property->isInitialized($object);
}
}

0 comments on commit 5cd1ff1

Please sign in to comment.