扩展表达语言

I'm attempting to extend Symfony's Expression Language component so that I can use PHP's absolute value (abs) function within my expressions.

I've extended the base class and created a separate provider class for the abs function as outlined in the documentation, but the values that my class is receiving / returning isn't what I am expecting it to.

The expression that I'm trying to evaluate is as follows:

abs(result.getDate().getTimestamp() - match.getDate().getTimestamp()) <= 86400

For example, if the result object had a timestamp of 1483100536 and the match object a timestamp of 1483014137, then you'd expect the absolute value to be 86399 and therefore for the expression to evaluate to true. However, if you use the code as it is below then the expression eventually evaluates to false as the $int value which get's passed into the evaluator function of the provider class isn't something that PHP's absolute function can handle.

The $int variable in the compiler function receives:

 ($result->getDate()->getTimestamp() - $match->getDate()->getTimestamp())

The $int variable in the evaluator function receives:

[
  "result" => Result {#1754}
  "match" => Match {#2161}
]`

Now I don't truly know the inner workings of Symfony's Expression Language, but you'd like to think that it would subtract the two inner values from each other before then passing that to the outer function and then finally comparing it against the given integer.

Is there something that I need to change in my class to enable a custom function to handle expressions within itself?

Extended Base Class

namespace AppBundle\Service\ExpressionLanguage;

use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;

class ExpressionLanguage extends BaseExpressionLanguage
{
    public function __construct($cache = null, array $providers = [])
    {
        // prepend the default provider to let users override it easily
        array_unshift($providers, new StringExpressionLanguageProvider());

        parent::__construct($cache, $providers);
    }
}

Provider Class

When adding / registering a new function it is expected to have the following parts:

  • Name - The name of the function in an expression.
  • Compiler - A function executed when compiling an expression using the function.
  • Evaluator - A function executed when the expression is evaluated.

Class

namespace AppBundle\Service\ExpressionLanguage;

use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

class StringExpressionLanguageProvider implements ExpressionFunctionProviderInterface
{
    public function getFunctions()
    {
        return [
            new ExpressionFunction('abs', function ($int) {
                return sprintf('(is_int(%1$d) ? abs(%1$d) : %1$d)', $int);
            }, function ($int) {
                if (!is_int($int)) {
                    return $int;
                }

                return abs($int);
            }),
        ];
    }
}

There is a difference between the parameters of the evaluator and compiler functions:

Compiler:  function ($value) { }
Evaluator: function (array $variables, $value) { }

As you can see, the evaluator receives a list of all available variables as the first argument. That's exactly what you discovered in the dump of $int. A fix is easy in this case, just make sure the parameters are correct:

new ExpressionFunction('abs', function ($int) {
    return sprintf('(is_int(%1$d) ? abs(%1$d) : %1$d)', $int);
}, function (array $variables, $int) {
    if (!is_int($int)) {
        return $int;
    }

    return abs($int);
}),