Skip to content

Commit 5f15bc2

Browse files
authored
Fix #154: Support for Twig 3.9
1 parent 34d28fc commit 5f15bc2

File tree

5 files changed

+62
-10
lines changed

5 files changed

+62
-10
lines changed

CHANGELOG.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
Yii Framework 2 twig extension Change Log
22
=========================================
33

4-
2.4.4 under development
4+
2.5.0 under development
55
-----------------------
66

7-
- no changes in this release.
7+
- Bug #154: Support for Twig 3.9
8+
In twig 3.9 there were many internal changes that might affect and break codebases using twig:
9+
- Internal functions (including twig_get_attribute) has been moved and renamed.
10+
- The internal working of twig does not store template state in the output buffer anymore.
11+
This means if a custom twig function reads and modifies the output buffer, it might not work as expected.
812

913

1014
2.4.3 April 29, 2024

composer.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
"require": {
2121
"php": "^7.2.5|^8.0|^8.1",
2222
"yiisoft/yii2": "~2.0.4",
23-
"twig/twig": "~3.0"
24-
},
25-
"conflict": {
26-
"twig/twig": ">=3.9"
23+
"twig/twig": "~3.9"
2724
},
2825
"require-dev": {
2926
"cweagans/composer-patches": "^1.7",

src/Extension.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ class Extension extends AbstractExtension
3737
*/
3838
protected $widgets = [];
3939

40+
/**
41+
* Little hack to work with twig 3.9
42+
* see explanation at the end of yii\twig\ViewRenderer::render function
43+
*
44+
* @var bool
45+
*/
46+
protected $viewEndPage = false;
47+
48+
public function withViewEndPage(): bool
49+
{
50+
return $this->viewEndPage;
51+
}
4052

4153
/**
4254
* Creates new instance
@@ -194,7 +206,11 @@ public function widget($widget, $config = [])
194206
public function viewHelper($context, $name = null)
195207
{
196208
if ($name !== null && isset($context['this'])) {
197-
$this->call($context['this'], Inflector::variablize($name));
209+
if ($name === 'end_page') {
210+
$this->viewEndPage = true;
211+
} else {
212+
$this->call($context['this'], Inflector::variablize($name));
213+
}
198214
}
199215
}
200216

src/Template.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Twig\Error\RuntimeError;
1313
use Twig\Template as TwigTemplate;
1414
use Twig\Markup;
15+
use Twig\Extension\CoreExtension;
1516

1617
/**
1718
* Template helper
@@ -51,6 +52,7 @@ public static function attribute(Environment $env, Source $source, $object, $ite
5152
$arguments[$key] = (string)$value;
5253
}
5354
}
54-
return \twig_get_attribute($env, $source, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
55+
56+
return CoreExtension::getAttribute($env, $source, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
5557
}
5658
}

src/ViewRenderer.php

+35-2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ class ViewRenderer extends BaseViewRenderer
113113
*/
114114
public $twigFallbackPaths = [];
115115

116+
/**
117+
* Custom Extension for twig
118+
* Need this in the render function for twig3.9 fix.
119+
*
120+
* @var Extension
121+
*/
122+
protected $extension;
123+
116124

117125
public function init()
118126
{
@@ -121,6 +129,7 @@ public function init()
121129
$this->twig = new Environment($loader, array_merge([
122130
'cache' => Yii::getAlias($this->cachePath),
123131
'charset' => Yii::$app->charset,
132+
'use_yield' => false
124133
], $this->options));
125134

126135
// Adding custom globals (objects or static classes)
@@ -138,7 +147,8 @@ public function init()
138147
$this->addFilters($this->filters);
139148
}
140149

141-
$this->addExtensions([new Extension($this->uses)]);
150+
$this->extension = new Extension($this->uses);
151+
$this->addExtensions([$this->extension]);
142152

143153
// Adding custom extensions
144154
if (!empty($this->extensions)) {
@@ -176,7 +186,30 @@ public function render($view, $file, $params)
176186
$this->setLexerOptions($this->lexerOptions);
177187
}
178188

179-
return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
189+
$content = $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
190+
/**
191+
* Hack to work with twig3.9.
192+
* Explanation:
193+
* Twig 3.9 does not hold the contents of the template in the output buffer anymore,
194+
* but it still reads the contents from it (if we stick to use `use_yield` false)
195+
* (it was an internal implementation and this package relied on this fact)
196+
* This means that when the endPage method in the yii2 View class wants to read the output buffer
197+
* to replace the placeholders it will be empty, because twig has already emptied it (and does not hold its state anymore).
198+
*
199+
* By not doing anything in the twig function call yet (see yii\twig\Extension::viewHelper), we can work around this limitation
200+
* by calling the endPage function with the twig render results in the buffer after twig has already done its work.
201+
*/
202+
if ($this->extension->withViewEndPage()) {
203+
// $view->endPage will end the current buffer when calling ob_get_clean and echo the modified(replaced placeholders) contents.
204+
// this means that we need 2 levels deep output buffer.
205+
ob_start();
206+
ob_start();
207+
echo $content;
208+
$view->endPage();
209+
$content = ob_get_clean();
210+
}
211+
212+
return $content;
180213
}
181214

182215
/**

0 commit comments

Comments
 (0)