Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move operator definitions to objects #4543

Open
wants to merge 4 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:
strategy:
matrix:
php-version:
- '8.0'
- '8.1'
- '8.2'
- '8.3'
Expand Down Expand Up @@ -68,7 +67,6 @@ jobs:
strategy:
matrix:
php-version:
- '8.0'
- '8.1'
- '8.2'
- '8.3'
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 3.20.0 (2025-XX-XX)

* Bump minimum PHP version to 8.1
* Introduce operator classes to describe operators provided by extensions
instead of arrays (it comes with many deprecations that are documented in
the ``deprecated`` documentation chapter)

# 3.19.0 (2025-XX-XX)

* Deprecate `Token::getType()`, use `Token::test()` instead
Expand Down
53 changes: 53 additions & 0 deletions bin/generate_operators_precedence.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Operator\OperatorArity;

require_once dirname(__DIR__).'/vendor/autoload.php';

function printOperators($output, array $operators)
{
fwrite($output, "\n========== ========\n");
fwrite($output, "Precedence Operator\n");
fwrite($output, "========== ========\n");

usort($operators, function($a, $b) {
$aPrecedence = $a->getPrecedenceChange() ? $a->getPrecedenceChange()->getNewPrecedence() : $a->getPrecedence();
$bPrecedence = $b->getPrecedenceChange() ? $b->getPrecedenceChange()->getNewPrecedence() : $b->getPrecedence();
return $bPrecedence - $aPrecedence;
});

$current = \PHP_INT_MAX;
foreach ($operators as $operator) {
$precedence = $operator->getPrecedenceChange() ? $operator->getPrecedenceChange()->getNewPrecedence() : $operator->getPrecedence();
if ($precedence !== $current) {
$current = $precedence;
fwrite($output, \sprintf("\n%-11d %s", $precedence, $operator->getOperator()));
} else {
fwrite($output, "\n".str_repeat(' ', 12).$operator->getOperator());
}
}
fwrite($output, "\n");
}

$output = fopen(dirname(__DIR__).'/doc/operators_precedence.rst', 'w');

$twig = new Environment(new ArrayLoader([]));
$unaryOperators = [];
$notUnaryOperators = [];
foreach ($twig->getOperators() as $operator) {
if ($operator->getArity()->value == OperatorArity::Unary->value) {
$unaryOperators[] = $operator;
} else {
$notUnaryOperators[] = $operator;
}
}

fwrite($output, "Unary operators precedence:\n");
printOperators($output, $unaryOperators);

fwrite($output, "\nBinary and Ternary operators precedence:\n");
printOperators($output, $notUnaryOperators);

fclose($output);
23 changes: 3 additions & 20 deletions doc/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -775,26 +775,9 @@ responsible for parsing the tag and compiling it to PHP.
Operators
~~~~~~~~~

The ``getOperators()`` methods lets you add new operators. Here is how to add
the ``!``, ``||``, and ``&&`` operators::

class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
public function getOperators()
{
return [
[
'!' => ['precedence' => 50, 'class' => \Twig\Node\Expression\Unary\NotUnary::class],
],
[
'||' => ['precedence' => 10, 'class' => \Twig\Node\Expression\Binary\OrBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
'&&' => ['precedence' => 15, 'class' => \Twig\Node\Expression\Binary\AndBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
],
];
}

// ...
}
The ``getOperators()`` methods lets you add new operators. To implement a new
one, have a look at the default operators provided by
``Twig\Extension\CoreExtension``.

Tests
~~~~~
Expand Down
55 changes: 42 additions & 13 deletions doc/deprecated.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,27 +210,27 @@ Node Visitors
Parser
------

* Passing a second argument to ``ExpressionParser::parseFilterExpressionRaw()``
is deprecated as of Twig 3.12.

* The following methods from ``Twig\Parser`` are deprecated as of Twig 3.12:
``getBlockStack()``, ``hasBlock()``, ``getBlock()``, ``hasMacro()``,
``hasTraits()``, ``getParent()``.

* The ``Twig\ExpressionParser::parseHashExpression()`` method is deprecated, use
``Twig\ExpressionParser::parseMappingExpression()`` instead.

* The ``Twig\ExpressionParser::parseArrayExpression()`` method is deprecated, use
``Twig\ExpressionParser::parseSequenceExpression()`` instead.

* Passing ``null`` to ``Twig\Parser::setParent()`` is deprecated as of Twig
3.12.

* The ``Twig\ExpressionParser::parseOnlyArguments()`` and
``Twig\ExpressionParser::parseArguments()`` methods are deprecated, use
``Twig\ExpressionParser::parseNamedArguments()`` instead.
* The following ``Twig\ExpressionParser`` methods are deprecated:

* ``parseHashExpression()``, use ``parseMappingExpression()``
* ``parseArrayExpression()``, use ``parseSequenceExpression()``
* ``parseOnlyArguments()``, use ``parseNamedArguments()``
* ``parseArguments()``, use ``parseNamedArguments()``
* ``parsePostfixExpression``
* ``parseSubscriptExpression``
* ``parseFilterExpression``
* ``parseFilterExpressionRaw``
* ``parseAssignmentExpression``, use ``AbstractTokenParser::parseAssignmentExpression``
* ``parseMultitargetExpression``

Lexer
Token
-----

* Not passing a ``Source`` instance to ``Twig\TokenStream`` constructor is
Expand All @@ -239,6 +239,12 @@ Lexer
* The ``Token::getType()`` method is deprecated as of Twig 3.19, use
``Token::test()`` instead.

* The ``Token::ARROW_TYPE`` constant is deprecated as of Twig 3.20, the arrow
``=>`` is now an operator (``Token::OPERATOR_TYPE``).

* The ``Token::PUNCTUATION_TYPE`` with values ``(``, ``[``, ``|``, ``.``,
``?``, or ``?:`` are now of the ``Token::OPERATOR_TYPE`` type.

Templates
---------

Expand Down Expand Up @@ -418,3 +424,26 @@ Operators
{# or #}

{{ (not 1) * 2 }} {# this is equivalent to what Twig 4.x will do without the parentheses #}

* Operators are now instances of ``Twig\Operator\OperatorInterface`` instead of
arrays. The ``ExtensionInterface::getOperators()`` method should now return an
array of ``Twig\Operator\OperatorInterface`` instances.

Before:

public function getOperators(): array {
return [
'not' => [
'precedence' => 10,
'class' => NotUnaryOperator::class,
],
];
}

After:

public function getOperators(): array {
return [
new NotUnaryOperator(),
];
}
56 changes: 56 additions & 0 deletions doc/operators_precedence.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Unary operators precedence:
Copy link
Contributor Author

@fabpot fabpot Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc is the most important part of this PR.
We need to review the precedence order carefully.


========== ========
Precedence Operator
========== ========

500 -
+
70 not
0 (

Binary and Ternary operators precedence:

========== ========
Precedence Operator
========== ========

300 |
.
[
(
250 =>
200 **
100 is
is not
60 *
/
//
%
30 +
-
27 ~
25 ..
20 ==
!=
<=>
<
>
>=
<=
not in
in
matches
starts with
ends with
has some
has every
18 b-and
17 b-xor
16 b-or
15 and
12 xor
10 or
5 ?:
??
0 ?
32 changes: 3 additions & 29 deletions doc/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1033,35 +1033,9 @@ Understanding the precedence of these operators is crucial for writing correct
and efficient Twig templates.

The operator precedence rules are as follows, with the lowest-precedence
operators listed first:

============================= =================================== =====================================================
Operator Score of precedence Description
============================= =================================== =====================================================
``?:`` 0 Ternary operator, conditional statement
``or`` 10 Logical OR operation between two boolean expressions
``xor`` 12 Logical XOR operation between two boolean expressions
``and`` 15 Logical AND operation between two boolean expressions
``b-or`` 16 Bitwise OR operation on integers
``b-xor`` 17 Bitwise XOR operation on integers
``b-and`` 18 Bitwise AND operation on integers
``==``, ``!=``, ``<=>``, 20 Comparison operators
``<``, ``>``, ``>=``,
``<=``, ``not in``, ``in``,
``matches``, ``starts with``,
``ends with``, ``has some``,
``has every``
``..`` 25 Range of values
``+``, ``-`` 30 Addition and subtraction on numbers
``~`` 40 String concatenation
``not`` 50 Negates a statement
``*``, ``/``, ``//``, ``%`` 60 Arithmetic operations on numbers
``is``, ``is not`` 100 Tests
``**`` 200 Raises a number to the power of another
``??`` 300 Default value when a variable is null
``+``, ``-`` 500 Unary operations on numbers
``|``,``[]``,``.`` - Filters, sequence, mapping, and attribute access
============================= =================================== =====================================================
operators listed first.

.. include:: operators_precedence.rst

Without using any parentheses, the operator precedence rules are used to
determine how to convert the code to PHP:
Expand Down
19 changes: 3 additions & 16 deletions src/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@
use Twig\Loader\ArrayLoader;
use Twig\Loader\ChainLoader;
use Twig\Loader\LoaderInterface;
use Twig\Node\Expression\Binary\AbstractBinary;
use Twig\Node\Expression\Unary\AbstractUnary;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\Operator\Operators;
use Twig\Runtime\EscaperRuntime;
use Twig\RuntimeLoader\FactoryRuntimeLoader;
use Twig\RuntimeLoader\RuntimeLoaderInterface;
Expand Down Expand Up @@ -916,22 +915,10 @@ public function mergeGlobals(array $context): array

/**
* @internal
*
* @return array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractUnary>}>
*/
public function getUnaryOperators(): array
{
return $this->extensionSet->getUnaryOperators();
}

/**
* @internal
*
* @return array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
*/
public function getBinaryOperators(): array
public function getOperators(): Operators
{
return $this->extensionSet->getBinaryOperators();
return $this->extensionSet->getOperators();
}

private function updateOptionsHash(): void
Expand Down
Loading
Loading