Skip to content

Commit

Permalink
Add #[YieldReady] to allow extensions to tell when they're ready fo…
Browse files Browse the repository at this point in the history
…r yielding
  • Loading branch information
nicolas-grekas committed Feb 13, 2024
1 parent 7f5958c commit 0fc9739
Show file tree
Hide file tree
Showing 58 changed files with 639 additions and 760 deletions.
26 changes: 10 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ permissions:

jobs:
tests:
name: "PHP ${{ matrix.php-version }} (yield: ${{ matrix.use_yield }})"
name: "PHP ${{ matrix.php-version }}"

runs-on: 'ubuntu-latest'

continue-on-error: ${{ matrix.experimental }}

strategy:
matrix:
php-version:
Expand All @@ -30,8 +28,6 @@ jobs:
- '8.1'
- '8.2'
- '8.3'
experimental: [false]
use_yield: [true, false]

steps:
- name: "Checkout code"
Expand All @@ -49,8 +45,8 @@ jobs:

- run: composer install

- name: "Switch use_yield to true"
if: ${{ matrix.use_yield }}
- name: "Switch use_yield to true on PHP ${{ matrix.php-version }}"
if: "matrix.php-version == '8.2'"
run: |
sed -i -e "s/'use_yield' => false/'use_yield' => true/" src/Environment.php
Expand All @@ -61,13 +57,13 @@ jobs:
run: vendor/bin/simple-phpunit --version

- name: "Run tests"
run: SYMFONY_DEPRECATIONS_HELPER=ignoreFile=./tests/ignore-use-yield-deprecations vendor/bin/simple-phpunit
run: vendor/bin/simple-phpunit

extension-tests:
needs:
- 'tests'

name: "${{ matrix.extension }} PHP ${{ matrix.php-version }} (yield: ${{ matrix.use_yield }})"
name: "${{ matrix.extension }} PHP ${{ matrix.php-version }}"

runs-on: 'ubuntu-latest'

Expand All @@ -92,8 +88,6 @@ jobs:
- 'markdown-extra'
- 'string-extra'
- 'twig-extra-bundle'
experimental: [false]
use_yield: [true, false]

steps:
- name: "Checkout code"
Expand All @@ -118,18 +112,18 @@ jobs:
- name: "PHPUnit version"
run: vendor/bin/simple-phpunit --version

- name: "Composer install ${{ matrix.extension}}"
working-directory: extra/${{ matrix.extension}}
- name: "Composer install ${{ matrix.extension }}"
working-directory: extra/${{ matrix.extension }}
run: composer install

- name: "Switch use_yield to true"
if: ${{ matrix.use_yield }}
if: "matrix.php == '8.2'"
run: |
sed -i -e "s/'use_yield' => false/'use_yield' => true/" extra/${{ matrix.extension }}/vendor/twig/twig/src/Environment.php
- name: "Run tests for ${{ matrix.extension}}"
- name: "Run tests for ${{ matrix.extension }}"
working-directory: extra/${{ matrix.extension }}
run: SYMFONY_DEPRECATIONS_HELPER=ignoreFile=../../tests/ignore-use-yield-deprecations ../../vendor/bin/simple-phpunit
run: ../../vendor/bin/simple-phpunit

integration-tests:
needs:
Expand Down
20 changes: 20 additions & 0 deletions src/Attribute/YieldReady.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Twig\Attribute;

/**
* Marks nodes that are ready for using "yield" instead of "echo" or "print()" for rendering.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
final class YieldReady
{
}
49 changes: 22 additions & 27 deletions src/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ class Compiler
private $sourceOffset;
private $sourceLine;
private $varNameSalt = 0;
private $checkForOutput;
private $didUseEcho = false;
private $didUseEchoStack = [];

public function __construct(Environment $env)
{
$this->env = $env;
$this->checkForOutput = $env->isDebug();
}

public function getEnvironment(): Environment
Expand Down Expand Up @@ -68,33 +68,33 @@ public function reset(int $indentation = 0)
public function compile(Node $node, int $indentation = 0)
{
$this->reset($indentation);
$node->compile($this);

return $this;
return $this->subcompile($node);
}

/**
* @return $this
*/
public function subcompile(Node $node, bool $raw = true)
{
if (false === $raw) {
if (!$raw) {
$this->source .= str_repeat(' ', $this->indentation * 4);
}

$node->compile($this);
$this->didUseEchoStack[] = $this->didUseEcho;

return $this;
}
try {
$this->didUseEcho = false;
$node->compile($this);

/**
* @return $this
*/
public function checkForOutput(bool $checkForOutput)
{
$this->checkForOutput = $checkForOutput ? $this->env->isDebug() : false;
if ($this->didUseEcho) {
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node));
}

return $this;
return $this;
} finally {
$this->didUseEcho = array_pop($this->didUseEchoStack);
}
}

/**
Expand All @@ -104,9 +104,7 @@ public function checkForOutput(bool $checkForOutput)
*/
public function raw(string $string)
{
if ($this->checkForOutput) {
$this->checkStringForOutput(trim($string));
}
$this->checkForEcho($string);
$this->source .= $string;

return $this;
Expand All @@ -120,10 +118,7 @@ public function raw(string $string)
public function write(...$strings)
{
foreach ($strings as $string) {
if ($this->checkForOutput) {
$this->checkStringForOutput(trim($string));
}

$this->checkForEcho($string);
$this->source .= str_repeat(' ', $this->indentation * 4).$string;
}

Expand Down Expand Up @@ -240,12 +235,12 @@ public function getVarName(): string
return sprintf('__internal_compile_%d', $this->varNameSalt++);
}

private function checkStringForOutput(string $string): void
private function checkForEcho(string $string): void
{
if (str_starts_with($string, 'echo')) {
trigger_deprecation('twig/twig', '3.9.0', 'Using "echo" in a "Node::compile()" method is deprecated; use a "TextNode" or "PrintNode" instead or use "yield" when "use_yield" is "true" on the environment (triggered by "%s").', $string);
} elseif (str_starts_with($string, 'print')) {
trigger_deprecation('twig/twig', '3.9.0', 'Using "print" in a "Node::compile()" method is deprecated; use a "TextNode" or "PrintNode" instead or use "yield" when "use_yield" is "true" on the environment (triggered by "%s").', $string);
if ($this->didUseEcho) {
return;
}

$this->didUseEcho = preg_match('/^\s*+(echo|print)\b/', $string, $m) ? $m[1] : false;
}
}
11 changes: 5 additions & 6 deletions src/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Twig\Extension\EscaperExtension;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\OptimizerExtension;
use Twig\Extension\YieldNotReadyExtension;
use Twig\Loader\ArrayLoader;
use Twig\Loader\ChainLoader;
use Twig\Loader\LoaderInterface;
Expand Down Expand Up @@ -100,7 +101,7 @@ class Environment
* (default to -1 which means that all optimizations are enabled;
* set it to 0 to disable).
*
* * use_yield: Enable a new mode where template are using "yield" instead of "echo"
* * use_yield: Enable templates to exclusively use "yield" instead of "echo"
* (default to "false", but switch it to "true" when possible
* as this will be the only supported mode in Twig 4.0)
*/
Expand All @@ -120,10 +121,6 @@ public function __construct(LoaderInterface $loader, $options = [])
], $options);

$this->useYield = (bool) $options['use_yield'];
if (!$this->useYield) {
trigger_deprecation('twig/twig', '3.9.0', 'Not setting "use_yield" to "true" is deprecated.');
}

$this->debug = (bool) $options['debug'];
$this->setCharset($options['charset'] ?? 'UTF-8');
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
Expand All @@ -133,6 +130,9 @@ public function __construct(LoaderInterface $loader, $options = [])

$this->addExtension(new CoreExtension());
$this->addExtension(new EscaperExtension($options['autoescape']));
if (\PHP_VERSION_ID >= 80000) {
$this->addExtension(new YieldNotReadyExtension($this->useYield));
}
$this->addExtension(new OptimizerExtension($options['optimizations']));
}

Expand Down Expand Up @@ -854,7 +854,6 @@ private function updateOptionsHash(): void
self::VERSION,
(int) $this->debug,
(int) $this->strictVariables,
$this->useYield ? '1' : '0',
]);
}
}
31 changes: 31 additions & 0 deletions src/Extension/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -1841,4 +1841,35 @@ public static function checkArrowInSandbox(Environment $env, $arrow, $thing, $ty
throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type));
}
}

/**
* @internal to be removed in Twig 4
*/
public static function captureOutput(iterable $body): string
{
$output = '';
$level = ob_get_level();
ob_start();

try {
foreach ($body as $data) {
if (ob_get_length()) {
$output .= ob_get_clean();
ob_start();
}

$output .= $data;
}

if (ob_get_length()) {
$output .= ob_get_clean();
}
} finally {
while (ob_get_level() > $level) {
ob_end_clean();
}
}

return $output;
}
}
32 changes: 32 additions & 0 deletions src/Extension/YieldNotReadyExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Twig\Extension;

use Twig\NodeVisitor\YieldNotReadyNodeVisitor;

/**
* @internal to be removed in Twig 4
*/
final class YieldNotReadyExtension extends AbstractExtension
{
private $useYield;

public function __construct(bool $useYield)
{
$this->useYield = $useYield;
}

public function getNodeVisitors(): array
{
return [new YieldNotReadyNodeVisitor($this->useYield)];
}
}
2 changes: 2 additions & 0 deletions src/Node/AutoEscapeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Twig\Node;

use Twig\Attribute\YieldReady;
use Twig\Compiler;

/**
Expand All @@ -24,6 +25,7 @@
*
* @author Fabien Potencier <[email protected]>
*/
#[YieldReady]
class AutoEscapeNode extends Node
{
public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
Expand Down
11 changes: 3 additions & 8 deletions src/Node/BlockNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@

namespace Twig\Node;

use Twig\Attribute\YieldReady;
use Twig\Compiler;

/**
* Represents a block node.
*
* @author Fabien Potencier <[email protected]>
*/
#[YieldReady]
class BlockNode extends Node
{
public function __construct(string $name, Node $body, int $lineno, ?string $tag = null)
Expand All @@ -37,14 +39,7 @@ public function compile(Compiler $compiler): void

$compiler
->subcompile($this->getNode('body'))
;

if (!$this->getNode('body') instanceof NodeOutputInterface && $compiler->getEnvironment()->useYield()) {
// needed when body doesn't yield anything
$compiler->write("yield '';\n");
}

$compiler
->write("return; yield '';\n") // needed when body doesn't yield anything
->outdent()
->write("}\n\n")
;
Expand Down
Loading

0 comments on commit 0fc9739

Please sign in to comment.