Add the possibility to deprecate attributes and nodes on Node

This commit is contained in:
Fabien Potencier
2024-08-08 11:07:26 +02:00
parent 90fbb9acbc
commit 18894cf91e
4 changed files with 169 additions and 0 deletions
+1
View File
@@ -1,5 +1,6 @@
# 3.11.0 (2024-XX-XX)
* Add the possibility to deprecate attributes and nodes on `Node`
* Add the possibility to add a package and a version to the `deprecated` tag
* Add the possibility to add a package for filter/function/test deprecations
* Mark `ConstantExpression` as being `@final`
+46
View File
@@ -0,0 +1,46 @@
<?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\Node;
/**
* Represents a deprecation for a named node or attribute on a Node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NameDeprecation
{
private $package;
private $version;
private $newName;
public function __construct(string $package = '', string $version = '', string $newName = '')
{
$this->package = $package;
$this->version = $version;
$this->newName = $newName;
}
public function getPackage(): string
{
return $this->package;
}
public function getVersion(): string
{
return $this->version;
}
public function getNewName(): string
{
return $this->newName;
}
}
+54
View File
@@ -30,6 +30,10 @@ class Node implements \Countable, \IteratorAggregate
protected $tag;
private $sourceContext;
/** @var array<string, NameDeprecation> */
private $nodeNameDeprecations = [];
/** @var array<string, NameDeprecation> */
private $attributeNameDeprecations = [];
/**
* @param array $nodes An array of named nodes
@@ -109,14 +113,39 @@ class Node implements \Countable, \IteratorAggregate
throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
}
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {
$dep = $this->attributeNameDeprecations[$name];
if ($dep->getNewName()) {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated, get the "%s" attribute instead.', $name, static::class, $dep->getNewName());
} else {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class);
}
}
return $this->attributes[$name];
}
public function setAttribute(string $name, $value): void
{
$triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true;
if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {
$dep = $this->attributeNameDeprecations[$name];
if ($dep->getNewName()) {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated, set the "%s" attribute instead.', $name, static::class, $dep->getNewName());
} else {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class);
}
}
$this->attributes[$name] = $value;
}
public function deprecateAttribute(string $name, NameDeprecation $dep): void
{
$this->attributeNameDeprecations[$name] = $dep;
}
public function removeAttribute(string $name): void
{
unset($this->attributes[$name]);
@@ -133,11 +162,31 @@ class Node implements \Countable, \IteratorAggregate
throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
}
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {
$dep = $this->nodeNameDeprecations[$name];
if ($dep->getNewName()) {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated, get the "%s" node instead.', $name, static::class, $dep->getNewName());
} else {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class);
}
}
return $this->nodes[$name];
}
public function setNode(string $name, self $node): void
{
$triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true;
if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {
$dep = $this->nodeNameDeprecations[$name];
if ($dep->getNewName()) {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated, set the "%s" node instead.', $name, static::class, $dep->getNewName());
} else {
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class);
}
}
if (null !== $this->sourceContext) {
$node->setSourceContext($this->sourceContext);
}
@@ -149,6 +198,11 @@ class Node implements \Countable, \IteratorAggregate
unset($this->nodes[$name]);
}
public function deprecateNode(string $name, NameDeprecation $dep): void
{
$this->nodeNameDeprecations[$name] = $dep;
}
/**
* @return int
*/
+68
View File
@@ -12,10 +12,14 @@ namespace Twig\Tests\Node;
*/
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Twig\Node\NameDeprecation;
use Twig\Node\Node;
class NodeTest extends TestCase
{
use ExpectDeprecationTrait;
public function testToString()
{
// callable is not a supported type for a Node attribute, but Drupal uses some apparently
@@ -23,4 +27,68 @@ class NodeTest extends TestCase
$this->assertEquals('Twig\Node\Node(value: \Closure)', (string) $node);
}
public function testAttributeDeprecationIgnore()
{
$node = new Node([], ['foo' => false]);
$node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));
$this->assertSame(false, $node->getAttribute('foo', false));
}
/**
* @group legacy
*/
public function testAttributeDeprecationWithoutAlternative()
{
$node = new Node([], ['foo' => false]);
$node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0'));
$this->expectDeprecation('Since foo/bar 2.0: Getting attribute "foo" on a "Twig\Node\Node" class is deprecated.');
$this->assertSame(false, $node->getAttribute('foo'));
}
/**
* @group legacy
*/
public function testAttributeDeprecationWithAlternative()
{
$node = new Node([], ['foo' => false]);
$node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));
$this->expectDeprecation('Since foo/bar 2.0: Getting attribute "foo" on a "Twig\Node\Node" class is deprecated, get the "bar" attribute instead.');
$this->assertSame(false, $node->getAttribute('foo'));
}
public function testNodeDeprecationIgnore()
{
$node = new Node(['foo' => $foo = new Node()], []);
$node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0'));
$this->assertSame($foo, $node->getNode('foo', false));
}
/**
* @group legacy
*/
public function testNodeDeprecationWithoutAlternative()
{
$node = new Node(['foo' => $foo = new Node()], []);
$node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0'));
$this->expectDeprecation('Since foo/bar 2.0: Getting node "foo" on a "Twig\Node\Node" class is deprecated.');
$this->assertSame($foo, $node->getNode('foo'));
}
/**
* @group legacy
*/
public function testNodeAttributeDeprecationWithAlternative()
{
$node = new Node(['foo' => $foo = new Node()], []);
$node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));
$this->expectDeprecation('Since foo/bar 2.0: Getting node "foo" on a "Twig\Node\Node" class is deprecated, get the "bar" node instead.');
$this->assertSame($foo, $node->getNode('foo'));
}
}