2024-06-20 14:10:42 +00:00
< ? php
/*
* This file is part of Twig .
*
* ( c ) Fabien Potencier
* ( c ) Armin Ronacher
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Twig\Node ;
use Twig\Attribute\YieldReady ;
use Twig\Compiler ;
use Twig\Node\Expression\AbstractExpression ;
use Twig\Node\Expression\ConstantExpression ;
use Twig\Source ;
/**
* Represents a module node .
*
2025-01-13 09:56:01 +00:00
* If you need to customize the behavior of the generated class , add nodes to
* the following nodes : display_start , display_end , constructor_start ,
* constructor_end , and class_end .
2024-06-20 14:10:42 +00:00
*
* @ author Fabien Potencier < fabien @ symfony . com >
*/
#[YieldReady]
final class ModuleNode extends Node
{
2025-01-13 09:56:01 +00:00
/**
* @ param BodyNode $body
*/
2024-06-20 14:10:42 +00:00
public function __construct ( Node $body , ? AbstractExpression $parent , Node $blocks , Node $macros , Node $traits , $embeddedTemplates , Source $source )
{
2025-01-13 09:56:01 +00:00
if ( ! $body instanceof BodyNode ) {
trigger_deprecation ( 'twig/twig' , '3.12' , \sprintf ( 'Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.' , BodyNode :: class , static :: class ));
}
2024-06-20 14:10:42 +00:00
$nodes = [
'body' => $body ,
'blocks' => $blocks ,
'macros' => $macros ,
'traits' => $traits ,
2025-01-13 09:56:01 +00:00
'display_start' => new Nodes (),
'display_end' => new Nodes (),
'constructor_start' => new Nodes (),
'constructor_end' => new Nodes (),
'class_end' => new Nodes (),
2024-06-20 14:10:42 +00:00
];
if ( null !== $parent ) {
$nodes [ 'parent' ] = $parent ;
}
// embedded templates are set as attributes so that they are only visited once by the visitors
parent :: __construct ( $nodes , [
'index' => null ,
'embedded_templates' => $embeddedTemplates ,
], 1 );
// populate the template name of all node children
$this -> setSourceContext ( $source );
}
public function setIndex ( $index )
{
$this -> setAttribute ( 'index' , $index );
}
public function compile ( Compiler $compiler ) : void
{
$this -> compileTemplate ( $compiler );
foreach ( $this -> getAttribute ( 'embedded_templates' ) as $template ) {
$compiler -> subcompile ( $template );
}
}
protected function compileTemplate ( Compiler $compiler )
{
if ( ! $this -> getAttribute ( 'index' )) {
$compiler -> write ( '<?php' );
}
$this -> compileClassHeader ( $compiler );
$this -> compileConstructor ( $compiler );
$this -> compileGetParent ( $compiler );
$this -> compileDisplay ( $compiler );
$compiler -> subcompile ( $this -> getNode ( 'blocks' ));
$this -> compileMacros ( $compiler );
$this -> compileGetTemplateName ( $compiler );
$this -> compileIsTraitable ( $compiler );
$this -> compileDebugInfo ( $compiler );
$this -> compileGetSourceContext ( $compiler );
$this -> compileClassFooter ( $compiler );
}
protected function compileGetParent ( Compiler $compiler )
{
if ( ! $this -> hasNode ( 'parent' )) {
return ;
}
$parent = $this -> getNode ( 'parent' );
$compiler
2025-01-13 09:56:01 +00:00
-> write ( " protected function doGetParent(array \$ context): bool|string|Template|TemplateWrapper \n " , " { \n " )
2024-06-20 14:10:42 +00:00
-> indent ()
-> addDebugInfo ( $parent )
-> write ( 'return ' )
;
if ( $parent instanceof ConstantExpression ) {
$compiler -> subcompile ( $parent );
} else {
$compiler
-> raw ( '$this->loadTemplate(' )
-> subcompile ( $parent )
-> raw ( ', ' )
-> repr ( $this -> getSourceContext () -> getName ())
-> raw ( ', ' )
-> repr ( $parent -> getTemplateLine ())
-> raw ( ')' )
;
}
$compiler
-> raw ( " ; \n " )
-> outdent ()
-> write ( " } \n \n " )
;
}
protected function compileClassHeader ( Compiler $compiler )
{
$compiler
-> write ( " \n \n " )
;
if ( ! $this -> getAttribute ( 'index' )) {
$compiler
-> write ( " use Twig \ Environment; \n " )
-> write ( " use Twig \ Error \ LoaderError; \n " )
-> write ( " use Twig \ Error \R untimeError; \n " )
-> write ( " use Twig \ Extension \ CoreExtension; \n " )
-> write ( " use Twig \ Extension \ SandboxExtension; \n " )
-> write ( " use Twig \ Markup; \n " )
-> write ( " use Twig \ Sandbox \ SecurityError; \n " )
-> write ( " use Twig \ Sandbox \ SecurityNotAllowedTagError; \n " )
-> write ( " use Twig \ Sandbox \ SecurityNotAllowedFilterError; \n " )
-> write ( " use Twig \ Sandbox \ SecurityNotAllowedFunctionError; \n " )
-> write ( " use Twig \ Source; \n " )
2025-01-13 09:56:01 +00:00
-> write ( " use Twig \T emplate; \n " )
-> write ( " use Twig \T emplateWrapper; \n " )
-> write ( " \n " )
2024-06-20 14:10:42 +00:00
;
}
$compiler
// if the template name contains */, add a blank to avoid a PHP parse error
-> write ( '/* ' . str_replace ( '*/' , '* /' , $this -> getSourceContext () -> getName ()) . " */ \n " )
-> write ( 'class ' . $compiler -> getEnvironment () -> getTemplateClass ( $this -> getSourceContext () -> getName (), $this -> getAttribute ( 'index' )))
-> raw ( " extends Template \n " )
-> write ( " { \n " )
-> indent ()
2025-01-13 09:56:01 +00:00
-> write ( " private Source \$ source; \n " )
-> write ( " /** \n " )
-> write ( " * @var array<string, Template> \n " )
-> write ( " */ \n " )
-> write ( " private array \$ macros = []; \n \n " )
2024-06-20 14:10:42 +00:00
;
}
protected function compileConstructor ( Compiler $compiler )
{
$compiler
-> write ( " public function __construct(Environment \$ env) \n " , " { \n " )
-> indent ()
-> subcompile ( $this -> getNode ( 'constructor_start' ))
-> write ( " parent::__construct( \$ env); \n \n " )
-> write ( " \$ this->source = \$ this->getSourceContext(); \n \n " )
;
// parent
if ( ! $this -> hasNode ( 'parent' )) {
$compiler -> write ( " \$ this->parent = false; \n \n " );
}
$countTraits = \count ( $this -> getNode ( 'traits' ));
if ( $countTraits ) {
// traits
foreach ( $this -> getNode ( 'traits' ) as $i => $trait ) {
$node = $trait -> getNode ( 'template' );
$compiler
-> addDebugInfo ( $node )
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( '$_trait_%s = $this->loadTemplate(' , $i ))
2024-06-20 14:10:42 +00:00
-> subcompile ( $node )
-> raw ( ', ' )
-> repr ( $node -> getTemplateName ())
-> raw ( ', ' )
-> repr ( $node -> getTemplateLine ())
-> raw ( " ); \n " )
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( " if (! \$ _trait_%s->unwrap()->isTraitable()) { \n " , $i ))
2024-06-20 14:10:42 +00:00
-> indent ()
-> write ( " throw new RuntimeError('Template \" '. " )
-> subcompile ( $trait -> getNode ( 'template' ))
-> raw ( " .' \" cannot be used as a trait.', " )
-> repr ( $node -> getTemplateLine ())
-> raw ( " , \$ this->source); \n " )
-> outdent ()
-> write ( " } \n " )
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( " \$ _trait_%s_blocks = \$ _trait_%s->unwrap()->getBlocks(); \n \n " , $i , $i ))
2024-06-20 14:10:42 +00:00
;
foreach ( $trait -> getNode ( 'targets' ) as $key => $value ) {
$compiler
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( 'if (!isset($_trait_%s_blocks[' , $i ))
2024-06-20 14:10:42 +00:00
-> string ( $key )
-> raw ( " ])) { \n " )
-> indent ()
-> write ( " throw new RuntimeError('Block " )
-> string ( $key )
-> raw ( ' is not defined in trait ' )
-> subcompile ( $trait -> getNode ( 'template' ))
-> raw ( " .', " )
-> repr ( $node -> getTemplateLine ())
-> raw ( " , \$ this->source); \n " )
-> outdent ()
-> write ( " } \n \n " )
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( '$_trait_%s_blocks[' , $i ))
2024-06-20 14:10:42 +00:00
-> subcompile ( $value )
2024-09-05 17:51:48 +00:00
-> raw ( \sprintf ( '] = $_trait_%s_blocks[' , $i ))
2024-06-20 14:10:42 +00:00
-> string ( $key )
2024-09-05 17:51:48 +00:00
-> raw ( \sprintf ( ']; unset($_trait_%s_blocks[' , $i ))
2024-06-20 14:10:42 +00:00
-> string ( $key )
2025-01-13 09:56:01 +00:00
-> raw ( ']); $this->traitAliases[' )
-> subcompile ( $value )
-> raw ( '] = ' )
-> string ( $key )
-> raw ( " ; \n \n " )
2024-06-20 14:10:42 +00:00
;
}
}
if ( $countTraits > 1 ) {
$compiler
-> write ( " \$ this->traits = array_merge( \n " )
-> indent ()
;
for ( $i = 0 ; $i < $countTraits ; ++ $i ) {
$compiler
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( '$_trait_%s_blocks' . ( $i == $countTraits - 1 ? '' : ',' ) . " \n " , $i ))
2024-06-20 14:10:42 +00:00
;
}
$compiler
-> outdent ()
-> write ( " ); \n \n " )
;
} else {
$compiler
-> write ( " \$ this->traits = \$ _trait_0_blocks; \n \n " )
;
}
$compiler
-> write ( " \$ this->blocks = array_merge( \n " )
-> indent ()
-> write ( " \$ this->traits, \n " )
-> write ( " [ \n " )
;
} else {
$compiler
-> write ( " \$ this->blocks = [ \n " )
;
}
// blocks
$compiler
-> indent ()
;
foreach ( $this -> getNode ( 'blocks' ) as $name => $node ) {
$compiler
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( " '%s' => [ \$ this, 'block_%s'], \n " , $name , $name ))
2024-06-20 14:10:42 +00:00
;
}
if ( $countTraits ) {
$compiler
-> outdent ()
-> write ( " ] \n " )
-> outdent ()
-> write ( " ); \n " )
;
} else {
$compiler
-> outdent ()
-> write ( " ]; \n " )
;
}
$compiler
-> subcompile ( $this -> getNode ( 'constructor_end' ))
-> outdent ()
-> write ( " } \n \n " )
;
}
protected function compileDisplay ( Compiler $compiler )
{
$compiler
2025-01-13 09:56:01 +00:00
-> write ( " protected function doDisplay(array \$ context, array \$ blocks = []): iterable \n " , " { \n " )
2024-06-20 14:10:42 +00:00
-> indent ()
-> write ( " \$ macros = \$ this->macros; \n " )
-> subcompile ( $this -> getNode ( 'display_start' ))
-> subcompile ( $this -> getNode ( 'body' ))
;
if ( $this -> hasNode ( 'parent' )) {
$parent = $this -> getNode ( 'parent' );
$compiler -> addDebugInfo ( $parent );
if ( $parent instanceof ConstantExpression ) {
$compiler
-> write ( '$this->parent = $this->loadTemplate(' )
-> subcompile ( $parent )
-> raw ( ', ' )
-> repr ( $this -> getSourceContext () -> getName ())
-> raw ( ', ' )
-> repr ( $parent -> getTemplateLine ())
-> raw ( " ); \n " )
;
}
$compiler -> write ( 'yield from ' );
if ( $parent instanceof ConstantExpression ) {
$compiler -> raw ( '$this->parent' );
} else {
$compiler -> raw ( '$this->getParent($context)' );
}
$compiler -> raw ( " ->unwrap()->yield( \$ context, array_merge( \$ this->blocks, \$ blocks)); \n " );
}
$compiler -> subcompile ( $this -> getNode ( 'display_end' ));
if ( ! $this -> hasNode ( 'parent' )) {
2025-01-13 09:56:01 +00:00
$compiler -> write ( " yield from []; \n " );
2024-06-20 14:10:42 +00:00
}
$compiler
-> outdent ()
-> write ( " } \n \n " )
;
}
protected function compileClassFooter ( Compiler $compiler )
{
$compiler
-> subcompile ( $this -> getNode ( 'class_end' ))
-> outdent ()
-> write ( " } \n " )
;
}
protected function compileMacros ( Compiler $compiler )
{
$compiler -> subcompile ( $this -> getNode ( 'macros' ));
}
protected function compileGetTemplateName ( Compiler $compiler )
{
$compiler
-> write ( " /** \n " )
-> write ( " * @codeCoverageIgnore \n " )
-> write ( " */ \n " )
2025-01-13 09:56:01 +00:00
-> write ( " public function getTemplateName(): string \n " , " { \n " )
2024-06-20 14:10:42 +00:00
-> indent ()
-> write ( 'return ' )
-> repr ( $this -> getSourceContext () -> getName ())
-> raw ( " ; \n " )
-> outdent ()
-> write ( " } \n \n " )
;
}
protected function compileIsTraitable ( Compiler $compiler )
{
// A template can be used as a trait if:
// * it has no parent
// * it has no macros
// * it has no body
//
// Put another way, a template can be used as a trait if it
// only contains blocks and use statements.
$traitable = ! $this -> hasNode ( 'parent' ) && 0 === \count ( $this -> getNode ( 'macros' ));
if ( $traitable ) {
if ( $this -> getNode ( 'body' ) instanceof BodyNode ) {
$nodes = $this -> getNode ( 'body' ) -> getNode ( '0' );
} else {
$nodes = $this -> getNode ( 'body' );
}
if ( ! \count ( $nodes )) {
2025-01-13 09:56:01 +00:00
$nodes = new Nodes ([ $nodes ]);
2024-06-20 14:10:42 +00:00
}
foreach ( $nodes as $node ) {
if ( ! \count ( $node )) {
continue ;
}
$traitable = false ;
break ;
}
}
if ( $traitable ) {
return ;
}
$compiler
-> write ( " /** \n " )
-> write ( " * @codeCoverageIgnore \n " )
-> write ( " */ \n " )
2025-01-13 09:56:01 +00:00
-> write ( " public function isTraitable(): bool \n " , " { \n " )
2024-06-20 14:10:42 +00:00
-> indent ()
-> write ( " return false; \n " )
-> outdent ()
-> write ( " } \n \n " )
;
}
protected function compileDebugInfo ( Compiler $compiler )
{
$compiler
-> write ( " /** \n " )
-> write ( " * @codeCoverageIgnore \n " )
-> write ( " */ \n " )
2025-01-13 09:56:01 +00:00
-> write ( " public function getDebugInfo(): array \n " , " { \n " )
2024-06-20 14:10:42 +00:00
-> indent ()
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( " return %s; \n " , str_replace ( " \n " , '' , var_export ( array_reverse ( $compiler -> getDebugInfo (), true ), true ))))
2024-06-20 14:10:42 +00:00
-> outdent ()
-> write ( " } \n \n " )
;
}
protected function compileGetSourceContext ( Compiler $compiler )
{
$compiler
2025-01-13 09:56:01 +00:00
-> write ( " public function getSourceContext(): Source \n " , " { \n " )
2024-06-20 14:10:42 +00:00
-> indent ()
-> write ( 'return new Source(' )
-> string ( $compiler -> getEnvironment () -> isDebug () ? $this -> getSourceContext () -> getCode () : '' )
-> raw ( ', ' )
-> string ( $this -> getSourceContext () -> getName ())
-> raw ( ', ' )
-> string ( $this -> getSourceContext () -> getPath ())
-> raw ( " ); \n " )
-> outdent ()
-> write ( " } \n " )
;
}
protected function compileLoadTemplate ( Compiler $compiler , $node , $var )
{
if ( $node instanceof ConstantExpression ) {
$compiler
2024-09-05 17:51:48 +00:00
-> write ( \sprintf ( '%s = $this->loadTemplate(' , $var ))
2024-06-20 14:10:42 +00:00
-> subcompile ( $node )
-> raw ( ', ' )
-> repr ( $node -> getTemplateName ())
-> raw ( ', ' )
-> repr ( $node -> getTemplateLine ())
-> raw ( " ); \n " )
;
} else {
throw new \LogicException ( 'Trait templates can only be constant nodes.' );
}
}
}