Skip to content

Commit 65aa27a

Browse files
committed
{layout file, vars} passes explicit variables to the parent template
Arguments after the template name are passed to the layout only and are not created in the current template, so child blocks do not see them. An explicit argument overrides a same-named parameter of the child. Works with {extends} and {layout auto} too; {layout none} forbids them. The comma before arguments is required.
1 parent 59b9548 commit 65aa27a

4 files changed

Lines changed: 89 additions & 5 deletions

File tree

docs/reference/tags.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ out of sync with the code.
2727
| `{dump}` | `DumpNode::create` | |
2828
| `{embed}` | `EmbedNode::create` | loose content becomes an implicit `{block default}` |
2929
| `{exitIf}` | `JumpNode::create` | |
30-
| `{extends}` | `ExtendsNode::create` | |
30+
| `{extends}` | `ExtendsNode::create` | optional explicit variables for the parent template: `{extends file, var: value}` |
3131
| `{first}` | `FirstLastSepNode::create` | |
3232
| `{for}` | `ForNode::create` | |
3333
| `{foreach}` | `ForeachNode::create` | |

src/Latte/Essential/Nodes/ExtendsNode.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Latte\Essential\Nodes;
99

1010
use Latte\CompileException;
11+
use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
1112
use Latte\Compiler\Nodes\Php\ExpressionNode;
1213
use Latte\Compiler\Nodes\Php\Scalar\BooleanNode;
1314
use Latte\Compiler\Nodes\Php\Scalar\NullNode;
@@ -17,12 +18,13 @@
1718

1819

1920
/**
20-
* {extends 'parent.latte'}
21-
* {layout 'layout.latte'}
21+
* {extends 'parent.latte' [, args]}
22+
* {layout 'layout.latte' [, args]}
2223
*/
2324
class ExtendsNode extends StatementNode
2425
{
2526
public ExpressionNode $extends;
27+
public ArrayNode $args;
2628

2729

2830
public static function create(Tag $tag): static
@@ -38,18 +40,25 @@ public static function create(Tag $tag): static
3840
} else {
3941
$node->extends = $tag->parser->parseUnquotedStringOrExpression();
4042
}
43+
$tag->parser->consumeCommaBeforeArguments();
44+
$node->args = $tag->parser->parseArguments();
45+
if ($node->args->items && $node->extends instanceof BooleanNode) {
46+
throw new CompileException("{{$tag->name} none} cannot have arguments.", $tag->position);
47+
}
4148
return $node;
4249
}
4350

4451

4552
public function print(PrintContext $context): string
4653
{
47-
return $context->format('$this->parentName = %node;', $this->extends);
54+
return $context->format('$this->parentName = %node;', $this->extends)
55+
. ($this->args->items ? $context->format('$this->parentArgs = %node;', $this->args) : '');
4856
}
4957

5058

5159
public function &getIterator(): \Generator
5260
{
5361
yield $this->extends;
62+
yield $this->args;
5463
}
5564
}

src/Latte/Runtime/Template.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class Template
3636
/** @internal */
3737
protected string|false|null $parentName = null;
3838

39+
/** @var mixed[] variables passed explicitly to the parent template @internal */
40+
protected array $parentArgs = [];
41+
3942
/** @var mixed[][] */
4043
protected array $varStack = [];
4144

@@ -96,7 +99,7 @@ public function render(?string $block = null): void
9699
trigger_error("Content outside of blocks is ignored when the template extends another one (in '$this->name').", E_USER_WARNING);
97100
}
98101
$this->params = $params;
99-
$this->createTemplate($parentName, $params, 'extends')->render($block);
102+
$this->createTemplate($parentName, $this->parentArgs + $params, 'extends')->render($block);
100103

101104
} elseif ($block !== null) { // single block rendering
102105
$this->renderBlock($block, $this->params);

tests/tags/extends.args.phpt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php declare(strict_types=1);
2+
3+
/**
4+
* Test: {layout file, args} & {extends file, args} explicit variables for parent template
5+
*/
6+
7+
use Tester\Assert;
8+
9+
require __DIR__ . '/../bootstrap.php';
10+
11+
12+
test('arguments are passed to the layout', function () {
13+
$latte = new Latte\Engine;
14+
$latte->setLoader(new Latte\Loaders\StringLoader([
15+
'layout' => 'foo={$foo} {include content}',
16+
'main' => '{layout "layout", foo: 123}{block content}child{/block}',
17+
]));
18+
Assert::same('foo=123 child', $latte->renderToString('main'));
19+
});
20+
21+
22+
test('argument beats template parameter of the same name, blocks keep the original', function () {
23+
$latte = new Latte\Engine;
24+
$latte->setLoader(new Latte\Loaders\StringLoader([
25+
'layout' => 'foo={$foo} {include content}',
26+
'main' => '{extends "layout", foo: "explicit"}{block content}{$foo}{/block}',
27+
]));
28+
Assert::same('foo=explicit param', $latte->renderToString('main', ['foo' => 'param']));
29+
});
30+
31+
32+
test('arguments are not created in the child template', function () {
33+
$latte = new Latte\Engine;
34+
$latte->setLoader(new Latte\Loaders\StringLoader([
35+
'layout' => '{include content}',
36+
'main' => '{layout "layout", foo: 123}{block content}{$foo ?? "undefined"}{/block}',
37+
]));
38+
Assert::same('undefined', $latte->renderToString('main'));
39+
});
40+
41+
42+
test('{layout auto} with arguments', function () {
43+
$latte = new Latte\Engine;
44+
$latte->setLoader(new Latte\Loaders\StringLoader([
45+
'layout' => 'foo={$foo} {include content}',
46+
'main' => '{layout auto, foo: 123}{block content}child{/block}',
47+
]));
48+
$latte->addProvider('coreParentFinder', fn() => 'layout');
49+
Assert::same('foo=123 child', $latte->renderToString('main'));
50+
});
51+
52+
53+
test('{layout none} with arguments is forbidden', function () {
54+
$latte = new Latte\Engine;
55+
$latte->setLoader(new Latte\Loaders\StringLoader);
56+
Assert::exception(
57+
fn() => $latte->compile('{layout none, foo: 123}'),
58+
Latte\CompileException::class,
59+
'{layout none} cannot have arguments%a%',
60+
);
61+
});
62+
63+
64+
test('comma before arguments is required', function () {
65+
$latte = new Latte\Engine;
66+
$latte->setLoader(new Latte\Loaders\StringLoader);
67+
Assert::exception(
68+
fn() => $latte->compile('{layout "layout" foo: 123}'),
69+
Latte\CompileException::class,
70+
'Unexpected %a%',
71+
);
72+
});

0 commit comments

Comments
 (0)