Skip to content

Commit

Permalink
feature #4182 Add the possibility to deprecate attributes and nodes o…
Browse files Browse the repository at this point in the history
…n Node (fabpot)

This PR was merged into the 3.x branch.

Discussion
----------

Add the possibility to deprecate attributes and nodes on Node

Commits
-------

18894cf Add the possibility to deprecate attributes and nodes on Node
  • Loading branch information
fabpot committed Aug 8, 2024
2 parents 90fbb9a + 18894cf commit 0d524d3
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
46 changes: 46 additions & 0 deletions src/Node/NameDeprecation.php
Original file line number Diff line number Diff line change
@@ -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 changes: 54 additions & 0 deletions src/Node/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -109,14 +113,39 @@ public function getAttribute(string $name)
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]);
Expand All @@ -133,11 +162,31 @@ public function getNode(string $name): self
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);
}
Expand All @@ -149,6 +198,11 @@ public function removeNode(string $name): void
unset($this->nodes[$name]);
}

public function deprecateNode(string $name, NameDeprecation $dep): void
{
$this->nodeNameDeprecations[$name] = $dep;
}

/**
* @return int
*/
Expand Down
68 changes: 68 additions & 0 deletions tests/Node/NodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,83 @@
*/

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
$node = new Node([], ['value' => function () { return '1'; }], 1);

$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'));
}
}

0 comments on commit 0d524d3

Please sign in to comment.