Skip to content

Commit

Permalink
Add the possibility to deprecate attributes and nodes on Node
Browse files Browse the repository at this point in the history
  • Loading branch information
fabpot committed Aug 8, 2024
1 parent 9252578 commit 639d1ee
Show file tree
Hide file tree
Showing 4 changed files with 149 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`
* Mark `ConstantExpression` as being `@final`
* Add the `find` filter
* Fix optimizer mode validation in `OptimizerNodeVisitor`
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;
}
}
50 changes: 50 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,37 @@ public function getAttribute(string $name)
throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
}

if (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 and will throw an exception in 4.0.', $name, static::class);
}
}

return $this->attributes[$name];
}

public function setAttribute(string $name, $value): void
{
if (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 and with no alternative in 4.0.', $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 +160,29 @@ public function getNode(string $name): self
throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
}

if (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 and will throw an exception in 4.0.', $name, static::class);
}
}

return $this->nodes[$name];
}

public function setNode(string $name, self $node): void
{
if (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 with no alternative in 4.0.', $name, static::class);
}
}

if (null !== $this->sourceContext) {
$node->setSourceContext($this->sourceContext);
}
Expand All @@ -149,6 +194,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
52 changes: 52 additions & 0 deletions tests/Node/NodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,67 @@
*/

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

/**
* @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 and will throw an exception in 4.0.');
$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.');
$node->getAttribute('foo');
}

/**
* @group legacy
*/
public function testNodeDeprecationWithoutAlternative()
{
$node = new Node(['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 and will throw an exception in 4.0.');
$node->getNode('foo');
}

/**
* @group legacy
*/
public function testNodeAttributeDeprecationWithAlternative()
{
$node = new Node(['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.');
$node->getNode('foo');
}
}

0 comments on commit 639d1ee

Please sign in to comment.