mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-17 10:45:53 +00:00
Improve the way one can deprecate a Twig callable
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 3.15.0 (2024-XX-XX)
|
||||
|
||||
* n/a
|
||||
* Improve the way one can deprecate a Twig callable (use `deprecation_info` instead of the other callable options)
|
||||
|
||||
# 3.14.0 (2024-09-09)
|
||||
|
||||
|
||||
+36
-12
@@ -277,29 +277,53 @@ filter: ``('a', 'b', 'foo')``.
|
||||
Deprecated Filters
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can mark a filter as being deprecated by setting the ``deprecated`` option
|
||||
to ``true``. You can also give an alternative filter that replaces the
|
||||
deprecated one when that makes sense::
|
||||
.. versionadded:: 3.15
|
||||
|
||||
The ``deprecation_info`` option was added in Twig 3.15.
|
||||
|
||||
You can mark a filter as being deprecated by setting the ``deprecation_info``
|
||||
option::
|
||||
|
||||
$filter = new \Twig\TwigFilter('obsolete', function () {
|
||||
// ...
|
||||
}, ['deprecated' => true, 'alternative' => 'new_one']);
|
||||
}, ['deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.11', 'new_one')]);
|
||||
|
||||
.. versionadded:: 3.11
|
||||
The ``DeprecatedCallableInfo`` constructor takes the following parameters:
|
||||
|
||||
The ``deprecating_package`` option was added in Twig 3.11.
|
||||
* The Composer package name that defines the filter;
|
||||
* The version when the filter was deprecated.
|
||||
|
||||
You can also set the ``deprecating_package`` option to specify the package that
|
||||
is deprecating the filter, and ``deprecated`` can be set to the package version
|
||||
when the filter was deprecated::
|
||||
Optionally, you can also provide the following parameters about an alternative:
|
||||
|
||||
$filter = new \Twig\TwigFilter('obsolete', function () {
|
||||
// ...
|
||||
}, ['deprecated' => '1.1', 'deprecating_package' => 'foo/bar']);
|
||||
* The package name that contains the alternative filter;
|
||||
* The alternative filter name that replaces the deprecated one;
|
||||
* The package version that added the alternative filter.
|
||||
|
||||
When a filter is deprecated, Twig emits a deprecation notice when compiling a
|
||||
template using it. See :ref:`deprecation-notices` for more information.
|
||||
|
||||
.. note::
|
||||
|
||||
Before Twig 3.15, you can mark a filter as being deprecated by setting the
|
||||
``deprecated`` option to ``true``. You can also give an alternative filter
|
||||
that replaces the deprecated one when that makes sense::
|
||||
|
||||
$filter = new \Twig\TwigFilter('obsolete', function () {
|
||||
// ...
|
||||
}, ['deprecated' => true, 'alternative' => 'new_one']);
|
||||
|
||||
.. versionadded:: 3.11
|
||||
|
||||
The ``deprecating_package`` option was added in Twig 3.11.
|
||||
|
||||
You can also set the ``deprecating_package`` option to specify the package
|
||||
that is deprecating the filter, and ``deprecated`` can be set to the
|
||||
package version when the filter was deprecated::
|
||||
|
||||
$filter = new \Twig\TwigFilter('obsolete', function () {
|
||||
// ...
|
||||
}, ['deprecated' => '1.1', 'deprecating_package' => 'foo/bar']);
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
|
||||
@@ -237,3 +237,22 @@ Environment
|
||||
After::
|
||||
|
||||
$context += $twig->getGlobals();
|
||||
|
||||
Functions/Filters/Tests
|
||||
-----------------------
|
||||
|
||||
* The ``deprecated``, ``deprecating_package``, ``alternative`` options on Twig
|
||||
functions/filters/Tests are deprecated as of Twig 3.15, and will be removed
|
||||
in Twig 4.0. Use the ``deprecation_info`` option instead:
|
||||
|
||||
Before::
|
||||
|
||||
$twig->addFunction(new TwigFunction('foo', 'foo', [
|
||||
'deprecated' => '3.12', 'deprecating_package' => 'twig/twig',
|
||||
]));
|
||||
|
||||
After::
|
||||
|
||||
$twig->addFunction(new TwigFunction('foo', 'foo', [
|
||||
'deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.12'),
|
||||
]));
|
||||
|
||||
@@ -33,10 +33,35 @@ abstract class AbstractTwigCallable implements TwigCallableInterface
|
||||
'needs_context' => false,
|
||||
'needs_charset' => false,
|
||||
'is_variadic' => false,
|
||||
'deprecation_info' => null,
|
||||
'deprecated' => false,
|
||||
'deprecating_package' => '',
|
||||
'alternative' => null,
|
||||
], $options);
|
||||
|
||||
if ($this->options['deprecation_info'] && !$this->options['deprecation_info'] instanceof DeprecatedCallableInfo) {
|
||||
throw new \LogicException(\sprintf('The "deprecation_info" option must be an instance of "%s".', DeprecatedCallableInfo::class));
|
||||
}
|
||||
|
||||
if ($this->options['deprecated']) {
|
||||
if ($this->options['deprecation_info']) {
|
||||
throw new \LogicException('When setting the "deprecation_info" option, you need to remove the obsolete deprecated options.');
|
||||
}
|
||||
|
||||
trigger_deprecation('twig/twig', '3.15', 'Using the "deprecated", "deprecating_package", and "alternative" options is deprecated, pass a "deprecation_info" one instead.');
|
||||
|
||||
$this->options['deprecation_info'] = new DeprecatedCallableInfo(
|
||||
$this->options['deprecating_package'],
|
||||
$this->options['deprecated'],
|
||||
null,
|
||||
$this->options['alternative'],
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->options['deprecation_info']) {
|
||||
$this->options['deprecation_info']->setName($name);
|
||||
$this->options['deprecation_info']->setType($this->getType());
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
@@ -111,21 +136,41 @@ abstract class AbstractTwigCallable implements TwigCallableInterface
|
||||
|
||||
public function isDeprecated(): bool
|
||||
{
|
||||
return (bool) $this->options['deprecated'];
|
||||
return (bool) $this->options['deprecation_info'];
|
||||
}
|
||||
|
||||
public function triggerDeprecation(?string $file = null, ?int $line = null): void
|
||||
{
|
||||
$this->options['deprecation_info']->triggerDeprecation($file, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.15
|
||||
*/
|
||||
public function getDeprecatingPackage(): string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);
|
||||
|
||||
return $this->options['deprecating_package'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.15
|
||||
*/
|
||||
public function getDeprecatedVersion(): string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);
|
||||
|
||||
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.15
|
||||
*/
|
||||
public function getAlternative(): ?string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);
|
||||
|
||||
return $this->options['alternative'];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class DeprecatedCallableInfo
|
||||
{
|
||||
private string $type;
|
||||
private string $name;
|
||||
|
||||
public function __construct(
|
||||
private string $package,
|
||||
private string $version,
|
||||
private ?string $altName = null,
|
||||
private ?string $altPackage = null,
|
||||
private ?string $altVersion = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function setType(string $type): void
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function triggerDeprecation(?string $file = null, ?int $line = null): void
|
||||
{
|
||||
$message = \sprintf('Twig %s "%s" is deprecated', ucfirst($this->type), $this->name);
|
||||
|
||||
if ($this->altName) {
|
||||
$message .= \sprintf('; use "%s"', $this->altName);
|
||||
if ($this->altPackage) {
|
||||
$message .= \sprintf(' from the "%s" package', $this->altPackage);
|
||||
}
|
||||
if ($this->altVersion) {
|
||||
$message .= \sprintf(' (available since version %s)', $this->altVersion);
|
||||
}
|
||||
$message .= ' instead';
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$message .= \sprintf(' in %s', $file);
|
||||
if ($line) {
|
||||
$message .= \sprintf(' at line %d', $line);
|
||||
}
|
||||
}
|
||||
|
||||
$message .= '.';
|
||||
|
||||
trigger_deprecation($this->package, $this->version, $message);
|
||||
}
|
||||
}
|
||||
@@ -773,15 +773,8 @@ class ExpressionParser
|
||||
|
||||
if ($test->isDeprecated()) {
|
||||
$stream = $this->parser->getStream();
|
||||
$message = \sprintf('Twig Test "%s" is deprecated', $test->getName());
|
||||
|
||||
if ($test->getAlternative()) {
|
||||
$message .= \sprintf('. Use "%s" instead', $test->getAlternative());
|
||||
}
|
||||
$src = $stream->getSourceContext();
|
||||
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
|
||||
|
||||
trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message);
|
||||
$test->triggerDeprecation($src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
|
||||
}
|
||||
|
||||
return $test;
|
||||
@@ -797,14 +790,8 @@ class ExpressionParser
|
||||
}
|
||||
|
||||
if ($function->isDeprecated()) {
|
||||
$message = \sprintf('Twig Function "%s" is deprecated', $function->getName());
|
||||
if ($function->getAlternative()) {
|
||||
$message .= \sprintf('. Use "%s" instead', $function->getAlternative());
|
||||
}
|
||||
$src = $this->parser->getStream()->getSourceContext();
|
||||
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
|
||||
|
||||
trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message);
|
||||
$function->triggerDeprecation($src->getPath() ?: $src->getName(), $line);
|
||||
}
|
||||
|
||||
return $function;
|
||||
@@ -820,14 +807,8 @@ class ExpressionParser
|
||||
}
|
||||
|
||||
if ($filter->isDeprecated()) {
|
||||
$message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName());
|
||||
if ($filter->getAlternative()) {
|
||||
$message .= \sprintf('. Use "%s" instead', $filter->getAlternative());
|
||||
}
|
||||
$src = $this->parser->getStream()->getSourceContext();
|
||||
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
|
||||
|
||||
trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message);
|
||||
$filter->triggerDeprecation($src->getPath() ?: $src->getName(), $line);
|
||||
}
|
||||
|
||||
return $filter;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\DeprecatedCallableInfo;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
@@ -217,7 +218,7 @@ final class CoreExtension extends AbstractExtension
|
||||
new TwigFilter('striptags', [self::class, 'striptags']),
|
||||
new TwigFilter('trim', [self::class, 'trim']),
|
||||
new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
||||
new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecated' => '3.12', 'deprecating_package' => 'twig/twig']),
|
||||
new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.12')]),
|
||||
|
||||
// array helpers
|
||||
new TwigFilter('join', [self::class, 'join']),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Twig\Tests;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Twig\DeprecatedCallableInfo;
|
||||
|
||||
class DeprecatedCallableInfoTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideTestsForTriggerDeprecation
|
||||
*/
|
||||
public function testTriggerDeprecation($expected, DeprecatedCallableInfo $info)
|
||||
{
|
||||
$info->setType('function');
|
||||
$info->setName('foo');
|
||||
|
||||
$deprecations = [];
|
||||
try {
|
||||
set_error_handler(function ($type, $msg) use (&$deprecations) {
|
||||
if (\E_USER_DEPRECATED === $type) {
|
||||
$deprecations[] = $msg;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$info->triggerDeprecation('foo.twig', 1);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$this->assertSame([$expected], $deprecations);
|
||||
}
|
||||
|
||||
public static function provideTestsForTriggerDeprecation(): iterable
|
||||
{
|
||||
yield ['Since foo/bar 1.1: Twig Function "foo" is deprecated in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1')];
|
||||
yield ['Since foo/bar 1.1: Twig Function "foo" is deprecated; use "alt_foo" from the "all/bar" package (available since version 12.10) instead in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1', 'alt_foo', 'all/bar', '12.10')];
|
||||
yield ['Since foo/bar 1.1: Twig Function "foo" is deprecated; use "alt_foo" from the "all/bar" package instead in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1', 'alt_foo', 'all/bar')];
|
||||
yield ['Since foo/bar 1.1: Twig Function "foo" is deprecated; use "alt_foo" instead in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1', 'alt_foo')];
|
||||
}
|
||||
|
||||
public function testTriggerDeprecationWithoutFileOrLine()
|
||||
{
|
||||
$info = new DeprecatedCallableInfo('foo/bar', '1.1');
|
||||
$info->setType('function');
|
||||
$info->setName('foo');
|
||||
|
||||
$deprecations = [];
|
||||
try {
|
||||
set_error_handler(function ($type, $msg) use (&$deprecations) {
|
||||
if (\E_USER_DEPRECATED === $type) {
|
||||
$deprecations[] = $msg;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$info->triggerDeprecation();
|
||||
$info->triggerDeprecation('foo.twig');
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$this->assertSame([
|
||||
'Since foo/bar 1.1: Twig Function "foo" is deprecated.',
|
||||
'Since foo/bar 1.1: Twig Function "foo" is deprecated in foo.twig.',
|
||||
], $deprecations);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
--TEST--
|
||||
Functions can be deprecated_function
|
||||
--DEPRECATION--
|
||||
Since foo/bar 1.1: Twig Function "deprecated_function" is deprecated. Use "not_deprecated_function" instead in index.twig at line 2.
|
||||
Since foo/bar 1.1: Twig Function "deprecated_function" is deprecated. Use "not_deprecated_function" instead in index.twig at line 4.
|
||||
Since foo/bar 1.1: Twig Function "deprecated_function" is deprecated; use "not_deprecated_function" instead in index.twig at line 2.
|
||||
Since foo/bar 1.1: Twig Function "deprecated_function" is deprecated; use "not_deprecated_function" instead in index.twig at line 4.
|
||||
--TEMPLATE--
|
||||
{{ deprecated_function() }}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Twig\Tests;
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Twig\DeprecatedCallableInfo;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\Extension\DebugExtension;
|
||||
use Twig\Extension\SandboxExtension;
|
||||
@@ -185,7 +186,7 @@ class TwigTestExtension extends AbstractExtension
|
||||
new TwigFunction('*_path', [$this, 'dynamic_path']),
|
||||
new TwigFunction('*_foo_*_bar', [$this, 'dynamic_foo']),
|
||||
new TwigFunction('anon_foo', function ($name) { return '*'.$name.'*'; }),
|
||||
new TwigFunction('deprecated_function', function () { return 'foo'; }, ['deprecated' => '1.1', 'deprecating_package' => 'foo/bar', 'alternative' => 'not_deprecated_function']),
|
||||
new TwigFunction('deprecated_function', function () { return 'foo'; }, ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.1', 'not_deprecated_function')]),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Twig\Tests\Util;
|
||||
*/
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Twig\DeprecatedCallableInfo;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\TwigFunction;
|
||||
@@ -25,7 +26,7 @@ class DeprecationCollectorTest extends TestCase
|
||||
public function testCollect()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader());
|
||||
$twig->addFunction(new TwigFunction('deprec', [$this, 'deprec'], ['deprecated' => '1.1', 'deprecating_package' => 'foo/bar']));
|
||||
$twig->addFunction(new TwigFunction('deprec', [$this, 'deprec'], ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.1')]));
|
||||
|
||||
$collector = new DeprecationCollector($twig);
|
||||
$deprecations = $collector->collect(new Iterator());
|
||||
|
||||
Reference in New Issue
Block a user