mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 02:46:29 +00:00
made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
* 3.0.0 (2019-XX-XX)
|
||||
|
||||
* made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats
|
||||
* removed the "filter" tag
|
||||
* added type hints everywhere
|
||||
* changed Environment::resolveTemplate() to always return a TemplateWrapper instance
|
||||
|
||||
@@ -907,31 +907,101 @@ function twig_in_filter($value, $compare)
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
if (\is_array($compare)) {
|
||||
return \in_array($value, $compare, \is_object($value) || \is_resource($value));
|
||||
} elseif (\is_string($compare) && (\is_string($value) || \is_int($value) || \is_float($value))) {
|
||||
return '' === $value || false !== strpos($compare, (string) $value);
|
||||
} elseif ($compare instanceof \Traversable) {
|
||||
if (\is_object($value) || \is_resource($value)) {
|
||||
foreach ($compare as $item) {
|
||||
if ($item === $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($compare as $item) {
|
||||
if ($item == $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (\is_string($compare)) {
|
||||
if (\is_string($value) || \is_int($value) || \is_float($value)) {
|
||||
return '' === $value || false !== strpos($compare, (string) $value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_iterable($compare)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\is_object($value) || \is_resource($value)) {
|
||||
if (!is_array($compare)) {
|
||||
foreach ($compare as $item) {
|
||||
if ($item === $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($value, $compare, true);
|
||||
}
|
||||
|
||||
foreach ($compare as $item) {
|
||||
if (0 === twig_compare($value, $item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two values using a more strict version of the PHP non-strict comparison operator.
|
||||
*
|
||||
* @see https://wiki.php.net/rfc/string_to_number_comparison
|
||||
* @see https://wiki.php.net/rfc/trailing_whitespace_numerics
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function twig_compare($a, $b)
|
||||
{
|
||||
// int <=> string
|
||||
if (is_int($a) && is_string($b)) {
|
||||
$b = trim($b);
|
||||
if (!is_numeric($b)) {
|
||||
return (string) $a <=> $b;
|
||||
}
|
||||
if ((int) $b == $b) {
|
||||
return $a <=> (int) $b;
|
||||
} else {
|
||||
return (float) $a <=> (float) $b;
|
||||
}
|
||||
}
|
||||
if (is_string($a) && is_int($b)) {
|
||||
$a = trim($a);
|
||||
if (!is_numeric($a)) {
|
||||
return $a <=> (string) $b;
|
||||
}
|
||||
if ((int) $a == $a) {
|
||||
return (int) $a <=> $b;
|
||||
} else {
|
||||
return (float) $a <=> (float) $b;
|
||||
}
|
||||
}
|
||||
|
||||
// float <=> string
|
||||
if (is_float($a) && is_string($b)) {
|
||||
if (is_nan($a)) {
|
||||
return 1;
|
||||
}
|
||||
if (!is_numeric($b)) {
|
||||
return (string) $a <=> $b;
|
||||
}
|
||||
|
||||
return (float) $a <=> $b;
|
||||
}
|
||||
if (is_float($b) && is_string($a)) {
|
||||
if (is_nan($b)) {
|
||||
return 1;
|
||||
}
|
||||
if (!is_numeric($a)) {
|
||||
return $a <=> (string) $b;
|
||||
}
|
||||
|
||||
return (float) $a <=> $b;
|
||||
}
|
||||
|
||||
// fallback to <=>
|
||||
return $b <=> $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a trimmed string.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,23 @@ use Twig\Compiler;
|
||||
|
||||
class EqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('0 === twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('==');
|
||||
|
||||
@@ -15,6 +15,23 @@ use Twig\Compiler;
|
||||
|
||||
class GreaterBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('-1 === twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('>');
|
||||
|
||||
@@ -15,6 +15,23 @@ use Twig\Compiler;
|
||||
|
||||
class GreaterEqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('0 >= twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('>=');
|
||||
|
||||
@@ -15,6 +15,23 @@ use Twig\Compiler;
|
||||
|
||||
class LessBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('1 === twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('<');
|
||||
|
||||
@@ -15,6 +15,23 @@ use Twig\Compiler;
|
||||
|
||||
class LessEqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('0 <= twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('<=');
|
||||
|
||||
@@ -15,6 +15,23 @@ use Twig\Compiler;
|
||||
|
||||
class NotEqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('0 !== twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('!=');
|
||||
|
||||
@@ -278,6 +278,67 @@ class Twig_Tests_Extension_CoreTest extends \PHPUnit\Framework\TestCase
|
||||
[[], new \ArrayIterator([1, 2]), 3],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideCompareCases
|
||||
*/
|
||||
public function testCompare($expected, $a, $b)
|
||||
{
|
||||
$this->assertSame($expected, twig_compare($a, $b));
|
||||
$this->assertSame($expected, -twig_compare($b, $a));
|
||||
}
|
||||
|
||||
public function testCompareNAN()
|
||||
{
|
||||
$this->assertSame(1, twig_compare(NAN, 'NAN'));
|
||||
$this->assertSame(1, twig_compare('NAN', NAN));
|
||||
$this->assertSame(1, twig_compare(NAN, 'foo'));
|
||||
$this->assertSame(1, twig_compare('foo', NAN));
|
||||
}
|
||||
|
||||
public function provideCompareCases()
|
||||
{
|
||||
return [
|
||||
[0, 'a', 'a'],
|
||||
|
||||
// from https://wiki.php.net/rfc/string_to_number_comparison
|
||||
[0, 0, '0'],
|
||||
[0, 0, '0.0'],
|
||||
|
||||
[-1, 0, 'foo'],
|
||||
[1, 0, ''],
|
||||
[0, 42, ' 42'],
|
||||
[-1, 42, '42foo'],
|
||||
|
||||
[0, '0', '0'],
|
||||
[0, '0', '0.0'],
|
||||
[1, '0', 'foo'],
|
||||
[-1, '0', ''],
|
||||
[0, '42', ' 42'],
|
||||
[1, '42', '42foo'],
|
||||
|
||||
[0, 42, '000042'],
|
||||
[0, 42, '42.0'],
|
||||
[0, 42.0, '+42.0E0'],
|
||||
[0, 0, '0e214987142012'],
|
||||
|
||||
[0, '42', '000042'],
|
||||
[0, '42', '42.0'],
|
||||
[0, '42.0', '+42.0E0'],
|
||||
[0, '0', '0e214987142012'],
|
||||
|
||||
[0, 42, ' 42'],
|
||||
[0, 42, '42 '],
|
||||
[-1, 42, '42abc'],
|
||||
[-1, 42, 'abc42'],
|
||||
[-1, 0, 'abc42'],
|
||||
|
||||
[0, INF, 'INF'],
|
||||
[0, -INF, '-INF'],
|
||||
[0, INF, '1e1000'],
|
||||
[0, -INF,'-1e1000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function foo_escaper_for_test(Environment $env, $string, $charset)
|
||||
|
||||
@@ -16,10 +16,10 @@ Twig supports the in operator
|
||||
{{ true in [0, 1] ? 'OK' : 'KO' }}
|
||||
{{ '0' in [0, 1] ? 'OK' : 'KO' }}
|
||||
{{ '0' in [1, 0] ? 'OK' : 'KO' }}
|
||||
{{ '' in [0, 1] ? 'OK' : 'KO' }}
|
||||
{{ '' in [1, 0] ? 'OK' : 'KO' }}
|
||||
{{ 0 in ['', 1] ? 'OK' : 'KO' }}
|
||||
{{ 0 in [1, ''] ? 'OK' : 'KO' }}
|
||||
{{ '' in [0, 1] ? 'KO' : 'OK' }}
|
||||
{{ '' in [1, 0] ? 'KO' : 'OK' }}
|
||||
{{ 0 in ['', 1] ? 'KO' : 'OK' }}
|
||||
{{ 0 in [1, ''] ? 'KO' : 'OK' }}
|
||||
|
||||
{{ '' in 'foo' ? 'OK' : 'KO' }}
|
||||
{{ 0 in 'foo' ? 'KO' : 'OK' }}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Twig supports the in operator when using iterators
|
||||
--TEMPLATE--
|
||||
{{ foo in iter ? 'OK' : 'KO' }}
|
||||
--DATA--
|
||||
$foo = new TwigTestFoo();
|
||||
$bar = new TwigTestFoo();
|
||||
|
||||
$foo->position = $bar;
|
||||
$bar->position = $foo;
|
||||
|
||||
return ['foo' => $foo, 'iter' => new \ArrayIterator([$bar, $foo])]
|
||||
--EXPECT--
|
||||
OK
|
||||
Reference in New Issue
Block a user