getEnvironment(); $arrayAccessSandbox = false; // optimize array calls if ( $this->getAttribute('optimizable') && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) && !$this->isDefinedTestEnabled() && Template::ARRAY_CALL === $this->getAttribute('type') ) { $var = '$'.$compiler->getVarName(); $compiler ->raw('(('.$var.' = ') ->subcompile($this->getNode('node')) ->raw(') && is_array(') ->raw($var); if (!$env->hasExtension(SandboxExtension::class)) { $compiler ->raw(') || ') ->raw($var) ->raw(' instanceof ArrayAccess ? (') ->raw($var) ->raw('[') ->subcompile($this->getNode('attribute')) ->raw('] ?? null) : null)') ; return; } $arrayAccessSandbox = true; $compiler ->raw(') || ') ->raw($var) ->raw(' instanceof ArrayAccess && in_array(') ->raw($var.'::class') ->raw(', \\Twig\\Extension\\CoreExtension::ARRAY_LIKE_CLASSES, true) ? (') ->raw($var) ->raw('[') ->subcompile($this->getNode('attribute')) ->raw('] ?? null) : ') ; } // Different from the parent: call our customGetAttribute() so the receiver is cast first. $compiler->raw(static::class.'::customGetAttribute($this->env, $this->source, '); if ($this->getAttribute('ignore_strict_check')) { $this->getNode('node')->setAttribute('ignore_strict_check', true); } $compiler ->subcompile($this->getNode('node')) ->raw(', ') ->subcompile($this->getNode('attribute')) ; if ($this->hasNode('arguments')) { $compiler->raw(', ')->subcompile($this->getNode('arguments')); } else { $compiler->raw(', []'); } $compiler->raw(', ') ->repr($this->getAttribute('type')) ->raw(', ')->repr($this->isDefinedTestEnabled()) ->raw(', ')->repr($this->getAttribute('ignore_strict_check')) ->raw(', ')->repr($env->hasExtension(SandboxExtension::class)) ->raw(', ')->repr($this->getNode('node')->getTemplateLine()) ->raw(')') ; if ($arrayAccessSandbox) { $compiler->raw(')'); } } /** * customGetAttribute wraps CoreExtension::getAttribute, casting the object to a safe proxy * before a method-invoking access when the sandbox is active. * * The cast fires on METHOD_CALL and on any access that carries arguments (so the built-in * `attribute()` function, which compiles to an ANY_CALL, is also covered). Property/relation * access with no arguments is left untouched, so collections stay iterable. */ public static function customGetAttribute( Environment $env, Source $source, $object, $item, array $arguments = [], $type = /* Template::ANY_CALL */ 'any', $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1 ) { if ( $sandboxed && ($type === Template::METHOD_CALL || $arguments) && $env->hasExtension(SandboxExtension::class) ) { $policy = $env->getExtension(SandboxExtension::class)->getSecurityPolicy(); if ($policy instanceof SecurityPolicy) { $object = $policy->castMethodObjectToSafeObject($object); } } return CoreExtension::getAttribute( $env, $source, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck, $sandboxed, $lineno ); } }