Skip to content

Commit

Permalink
Improve the way one can deprecate a Twig callable
Browse files Browse the repository at this point in the history
  • Loading branch information
fabpot committed Sep 9, 2024
1 parent 540b54e commit d6656c9
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 3.14.0 (2024-XX-XX)

* Improve the way one can deprecate a Twig callable (use `deprecated_info` instead of the other callable options)
* Add the possibility to reset globals via `Environment::resetGlobals()`
* Deprecate `Environment::mergeGlobals()`

Expand Down
48 changes: 36 additions & 12 deletions doc/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.14

The ``deprecated_info`` option was added in Twig 3.14.

You can mark a filter as being deprecated by setting the ``deprecated_info``
option::

$filter = new \Twig\TwigFilter('obsolete', function () {
// ...
}, ['deprecated' => true, 'alternative' => 'new_one']);
}, ['deprecated_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.14, 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
---------

Expand Down
19 changes: 19 additions & 0 deletions doc/deprecated.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.14, and will be removed
in Twig 4.0. Use the ``deprecated_info`` option instead:

Before::

$twig->addFunction(new TwigFunction('foo', 'foo', [
'deprecated' => '3.12', 'deprecating_package' => 'twig/twig',
]));

After::

$twig->addFunction(new TwigFunction('foo', 'foo', [
'deprecated_info' => new DeprecatedCallableInfo('twig/twig', '3.12'),
]));
47 changes: 46 additions & 1 deletion src/AbstractTwigCallable.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,35 @@ public function __construct(string $name, $callable = null, array $options = [])
'needs_context' => false,
'needs_charset' => false,
'is_variadic' => false,
'deprecated_info' => null,
'deprecated' => false,
'deprecating_package' => '',
'alternative' => null,
], $options);

if ($this->options['deprecated_info'] && !$this->options['deprecated_info'] instanceof DeprecatedCallableInfo) {
throw new \LogicException(\sprintf('The "deprecated_info" options must be an instance of "%s".', DeprecatedCallableInfo::class));
}

if ($this->options['deprecated']) {
if ($this->options['deprecated_info']) {
throw new \LogicException('When setting the "deprecated_info" option, you need to remove the obsolete deprecated options.');
}

trigger_deprecation('twig/twig', '3.14', 'Using the "deprecated", "deprecating_package", and "alternative" options is deprecated, pass a "deprecated_info" one instead.');

$this->options['deprecated_info'] = new DeprecatedCallableInfo(
$this->options['deprecating_package'],
$this->options['deprecated'],
null,
$this->options['alternative'],
);
}

if ($this->options['deprecated_info']) {
$this->options['deprecated_info']->setName($name);
$this->options['deprecated_info']->setType($this->getType());
}
}

public function __toString(): string
Expand Down Expand Up @@ -111,21 +136,41 @@ public function isVariadic(): bool

public function isDeprecated(): bool
{
return (bool) $this->options['deprecated'];
return (bool) $this->options['deprecated_info'];
}

public function triggerDeprecation(?string $file = null, ?int $line = null): void
{
$this->options['deprecated_info']->triggerDeprecation($file, $line);
}

/**
* @deprecated since Twig 3.14
*/
public function getDeprecatingPackage(): string
{
trigger_deprecation('twig/twig', '3.14', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);

return $this->options['deprecating_package'];
}

/**
* @deprecated since Twig 3.14
*/
public function getDeprecatedVersion(): string
{
trigger_deprecation('twig/twig', '3.14', '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.14
*/
public function getAlternative(): ?string
{
trigger_deprecation('twig/twig', '3.14', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);

return $this->options['alternative'];
}

Expand Down
67 changes: 67 additions & 0 deletions src/DeprecatedCallableInfo.php
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
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);
}
}
25 changes: 3 additions & 22 deletions src/ExpressionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -773,15 +773,8 @@ private function getTest(int $line): TwigTest

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;
Expand All @@ -797,14 +790,8 @@ private function getFunction(string $name, int $line): TwigFunction
}

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;
Expand All @@ -820,14 +807,8 @@ private function getFilter(string $name, int $line): TwigFilter
}

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;
Expand Down
3 changes: 2 additions & 1 deletion src/Extension/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Twig\Extension;

use Twig\DeprecatedCallableInfo;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
Expand Down Expand Up @@ -217,7 +218,7 @@ public function getFilters(): array
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'], 'deprecated_info' => new DeprecatedCallableInfo('twig/twig', '3.12')]),

// array helpers
new TwigFilter('join', [self::class, 'join']),
Expand Down
80 changes: 80 additions & 0 deletions tests/DeprecatedCallableInfoTest.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit d6656c9

Please sign in to comment.