Skip to content

Commit

Permalink
feature #1769 Deprecation notices (fabpot, nicolas-grekas)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.x branch.

Discussion
----------

Deprecation notices

Added a way to warn our users of deprecation features. This framework can be used for core features, but also for functions/tests/filters created by third-party. I haven't added something for tags as it can be done easily in the token parser class.

Fixes #1756 and #1767

Commits
-------

56c7382 added Twig_Util_DeprecationCollector to collect deprecation notices for a set of templates
2da6bae tweaked some deprecation messages
cc98028 added deprecation notices for deprecated classes
c6ab7de optimized legacy tests management
eebfdc7 added a way to manage legacy tests in fixtures
8c4291a removed usage of deprecated features, marked some tests as being legacy
b5db9dc added the Symfony PHPUnit bridge to better manage deprecation notices
23c3922 added deprecated notices for deprecated features
bd87ba8 added some documentation about deprecation notices
d16b7bf added a way to trigger deprecation notices for filters and functions
21f536b added deprecation for the raw tag
efcfe8e added deprecation notices for deprecated tests
65e70a2 fixed CS
  • Loading branch information
fabpot committed Aug 18, 2015
2 parents 5f253f7 + 56c7382 commit 5fdbd99
Show file tree
Hide file tree
Showing 57 changed files with 491 additions and 133 deletions.
37 changes: 21 additions & 16 deletions .travis.yml
@@ -1,28 +1,33 @@
language: php

php:
- 5.2
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
- nightly
- 5.2
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
- nightly

allow_failures:
- php: nightly

env:
- TWIG_EXT=no
- TWIG_EXT=yes
- TWIG_EXT=no
- TWIG_EXT=yes

before_script:
- if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && sudo make install"; fi
- if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi
- if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && sudo make install"; fi
- if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi
- if [ ${TRAVIS_PHP_VERSION:0:3} == "5.2" ]; then sed -i.bak "s|vendor/autoload.php|test/bootstrap.php|" phpunit.xml.dist; fi

install:
# Composer is not available on PHP 5.2
- if [ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]; then travis_retry composer install; fi

matrix:
exclude:
- php: hhvm
env: TWIG_EXT=yes
- php: nightly
env: TWIG_EXT=yes
exclude:
- php: hhvm
env: TWIG_EXT=yes
- php: nightly
env: TWIG_EXT=yes
5 changes: 3 additions & 2 deletions CHANGELOG
@@ -1,6 +1,7 @@
* 1.20.1 (2015-XX-XX)
* 1.21.0 (2015-XX-XX)

* n/a
* added deprecation notices for deprecated features
* added a deprecation "framework" for filters/functions/tests and test fixtures

* 1.20.0 (2015-08-12)

Expand Down
4 changes: 4 additions & 0 deletions composer.json
Expand Up @@ -29,6 +29,10 @@
"require": {
"php": ">=5.2.7"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7",
"symfony/debug": "~2.7"
},
"autoload": {
"psr-0" : {
"Twig_" : "lib/"
Expand Down
17 changes: 17 additions & 0 deletions doc/advanced.rst
Expand Up @@ -267,6 +267,23 @@ arguments, but after the environment and the context. For instance, a call to
``'foo'|a_path_b()`` will result in the following arguments to be passed to
the filter: ``('a', 'b', 'foo')``.

Deprecated Filters
~~~~~~~~~~~~~~~~~~

.. versionadded:: 1.21
Support for deprecated filters was added in Twig 1.21.

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_SimpleFilter('obsolete', function () {
// ...
}, array('deprecated' => true, 'alternative' => 'new_one'));

When a filter is deprecated, Twig emits a deprecation notice when compiling a
template using it. See :ref:`deprecation-notices` for more information.

Functions
---------

Expand Down
6 changes: 6 additions & 0 deletions doc/deprecated.rst
Expand Up @@ -5,6 +5,12 @@ This document lists all deprecated features in Twig. Deprecated features are
kept for backward compatibility and removed in the next major release (a
feature that was deprecated in Twig 1.x is removed in Twig 2.0).

Deprecation Notices
-------------------

As of Twig 1.21, Twig generates deprecation notices when a template uses
deprecated features. See :ref:`deprecation-notices` for more information.

Token Parsers
-------------

Expand Down
58 changes: 58 additions & 0 deletions doc/recipes.rst
@@ -1,6 +1,64 @@
Recipes
=======

.. _deprecation-notices:

Displaying Deprecation Notices
------------------------------

.. versionadded:: 1.21
This works as of Twig 1.21.

Deprecated features generate deprecation notices (via a call to the
``trigger_error()`` PHP function). By default, they are silenced and never
displayed nor logged.

To easily remove all deprecated feature usages from your templates, write and
run a script along the lines of the following::

require_once __DIR__.'/vendor/autoload.php';

$twig = create_your_twig_env();

$deprecations = new Twig_Util_DeprecationCollector($twig);

print_r($deprecations->collectDir(__DIR__.'/templates'));

The ``collectDir()`` method compiles all templates found in a directory,
catches deprecation notices, and return them.

.. tip::

If your templates are not stored on the filesystem, use the ``collect()``
method instead which takes an ``Iterator``; the iterator must return
template names as keys and template contents as values (as done by
``Twig_Util_TemplateDirIterator``).

However, this code won't find all deprecations (like using deprecated some Twig
classes). To catch all notices, register a custom error handler like the one
below::

$deprecations = array();
set_error_handler(function ($type, $msg) use (&$deprecations) {
if (E_USER_DEPRECATED === $type) {
$deprecations[] = $msg;
}
});

// run your application

print_r($deprecations);

Note that most deprecation notices are triggered during **compilation**, so
they won't be generated when templates are already cached.

.. tip::

If you want to manage the deprecation notices from your PHPUnit tests, have
a look at the `symfony/phpunit-bridge
<https://github.com/symfony/phpunit-bridge>`_ package, which eases the
process a lot.

Making a Layout conditional
---------------------------

Expand Down
6 changes: 6 additions & 0 deletions lib/Twig/Autoloader.php
Expand Up @@ -9,10 +9,14 @@
* file that was distributed with this source code.
*/

@trigger_error('The Twig_Autoloader class is deprecated and will be removed in 2.0. Use Composer instead.', E_USER_DEPRECATED);

/**
* Autoloads Twig classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated Use Composer instead. Will be removed in Twig 2.0.
*/
class Twig_Autoloader
{
Expand All @@ -23,6 +27,8 @@ class Twig_Autoloader
*/
public static function register($prepend = false)
{
@trigger_error('Using Twig_Autoloader is deprecated. Use Composer instead.', E_USER_DEPRECATED);

if (PHP_VERSION_ID < 50300) {
spl_autoload_register(array(__CLASS__, 'autoload'));
} else {
Expand Down
4 changes: 2 additions & 2 deletions lib/Twig/BaseNodeVisitor.php
Expand Up @@ -43,8 +43,8 @@ final public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
/**
* Called before child nodes are visited.
*
* @param Twig_Node $node The node to visit
* @param Twig_Environment $env The Twig environment instance
* @param Twig_Node $node The node to visit
* @param Twig_Environment $env The Twig environment instance
*
* @return Twig_Node The modified node
*/
Expand Down
28 changes: 24 additions & 4 deletions lib/Twig/Environment.php
Expand Up @@ -86,6 +86,8 @@ public function __construct(Twig_LoaderInterface $loader = null, $options = arra
{
if (null !== $loader) {
$this->setLoader($loader);
} else {
@trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated.', E_USER_DEPRECATED);
}

$options = array_merge(array(
Expand Down Expand Up @@ -448,6 +450,8 @@ public function resolveTemplate($names)
*/
public function clearTemplateCache()
{
@trigger_error(sprintf('The %s method is deprecated and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);

$this->loadedTemplates = array();
}

Expand Down Expand Up @@ -711,6 +715,8 @@ public function addExtension(Twig_ExtensionInterface $extension)
*/
public function removeExtension($name)
{
@trigger_error(sprintf('The %s method is deprecated and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);

if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
}
Expand Down Expand Up @@ -830,6 +836,8 @@ public function addFilter($name, $filter = null)
if ($name instanceof Twig_SimpleFilter) {
$filter = $name;
$name = $filter->getName();
} else {
@trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED);
}

if ($this->extensionInitialized) {
Expand Down Expand Up @@ -919,6 +927,8 @@ public function addTest($name, $test = null)
if ($name instanceof Twig_SimpleTest) {
$test = $name;
$name = $test->getName();
} else {
@trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED);
}

if ($this->extensionInitialized) {
Expand Down Expand Up @@ -977,6 +987,8 @@ public function addFunction($name, $function = null)
if ($name instanceof Twig_SimpleFunction) {
$function = $name;
$name = $function->getName();
} else {
@trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED);
}

if ($this->extensionInitialized) {
Expand Down Expand Up @@ -1067,11 +1079,11 @@ public function addGlobal($name, $value)
$this->globals = $this->initGlobals();
}

/* This condition must be uncommented in Twig 2.0
if (!array_key_exists($name, $this->globals)) {
throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
// The deprecation notice must be turned into the following exception in Twig 2.0
@trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated.', $name), E_USER_DEPRECATED);
//throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
}
*/
}

if ($this->extensionInitialized || $this->runtimeInitialized) {
Expand Down Expand Up @@ -1186,7 +1198,7 @@ protected function initExtensions()
}

$this->extensionInitialized = true;
$this->parsers = new Twig_TokenParserBroker();
$this->parsers = new Twig_TokenParserBroker(array(), array(), false);
$this->filters = array();
$this->functions = array();
$this->tests = array();
Expand All @@ -1206,6 +1218,8 @@ protected function initExtension(Twig_ExtensionInterface $extension)
foreach ($extension->getFilters() as $name => $filter) {
if ($filter instanceof Twig_SimpleFilter) {
$name = $filter->getName();
} else {
@trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated. Use Twig_SimpleFilter instead.', get_class($filter), $name), E_USER_DEPRECATED);
}

$this->filters[$name] = $filter;
Expand All @@ -1215,6 +1229,8 @@ protected function initExtension(Twig_ExtensionInterface $extension)
foreach ($extension->getFunctions() as $name => $function) {
if ($function instanceof Twig_SimpleFunction) {
$name = $function->getName();
} else {
@trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated. Use Twig_SimpleFunction instead.', get_class($filter), $name), E_USER_DEPRECATED);
}

$this->functions[$name] = $function;
Expand All @@ -1224,6 +1240,8 @@ protected function initExtension(Twig_ExtensionInterface $extension)
foreach ($extension->getTests() as $name => $test) {
if ($test instanceof Twig_SimpleTest) {
$name = $test->getName();
} else {
@trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated. Use Twig_SimpleTest instead.', get_class($filter), $name), E_USER_DEPRECATED);
}

$this->tests[$name] = $test;
Expand All @@ -1234,6 +1252,8 @@ protected function initExtension(Twig_ExtensionInterface $extension)
if ($parser instanceof Twig_TokenParserInterface) {
$this->parsers->addTokenParser($parser);
} elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
@trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated.', E_USER_DEPRECATED);

$this->parsers->addTokenParserBroker($parser);
} else {
throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
Expand Down
20 changes: 20 additions & 0 deletions lib/Twig/ExpressionParser.php
Expand Up @@ -578,6 +578,16 @@ protected function getFunctionNodeClass($name, $line)
throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
}

if ($function instanceof Twig_SimpleFunction && $function->isDeprecated()) {
$message = sprintf('Twig Function "%s" is deprecated', $function->getName());
if ($test->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $function->getAlternative());
}
$message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line);

@trigger_error($message, E_USER_DEPRECATED);
}

if ($function instanceof Twig_SimpleFunction) {
return $function->getNodeClass();
}
Expand All @@ -598,6 +608,16 @@ protected function getFilterNodeClass($name, $line)
throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
}

if ($filter instanceof Twig_SimpleFilter && $filter->isDeprecated()) {
$message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if ($test->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $filter->getAlternative());
}
$message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line);

@trigger_error($message, E_USER_DEPRECATED);
}

if ($filter instanceof Twig_SimpleFilter) {
return $filter->getNodeClass();
}
Expand Down

0 comments on commit 5fdbd99

Please sign in to comment.