DEV Community

david duymelinck
david duymelinck

Posted on

PHP fun: How to do operator overrides

I just saw

and the thing that stood out to me was

api_check = is_admin() | (
    is_active()
    & account_older_than(30)
    & ~is_banned()
    & from_country(["NL", "BE"])
    & (credit_score_above(650) | has_override())
Enter fullscreen mode Exit fullscreen mode

The reason is because this is tied to a class.

class Predicate[T]:
    """
    A composable predicate that supports &, |, and ~ operators.
    Wraps a function (T -> bool).
    """

    def __init__(self, fn: PredicateFn[T]):
        self.fn = fn

    def __call__(self, obj: T) -> bool:
        return self.fn(obj)

    def __and__(self, other: Predicate[T]) -> Predicate[T]:
        return Predicate(lambda x: self(x) and other(x))

    def __or__(self, other: Predicate[T]) -> Predicate[T]:
        return Predicate(lambda x: self(x) or other(x))

    def __invert__(self) -> Predicate[T]:
        return Predicate(lambda x: not self(x))
Enter fullscreen mode Exit fullscreen mode

So I was thinking how can I do that in PHP.
And the answer is the Symfony expression language component
The component makes it possible to do.

$expr = 'is_admin($user) || (
    is_active($user)
    && account_older_than($user, 30)
    && !is_banned($user)
    && from_country($user, ["NL", "BE"])
    && (credit_score_above($user, 650) || has_override($user))
)';

$lang = new ExpressionLanguage();

// Register functions (compiler -> PHP code; evaluator -> runtime callable)
$lang->register('is_admin',
    fn() => '($user["is_admin"] ?? false)',
    fn(array $vars) => $vars['user']['is_admin'] ?? false
);

$lang->register('is_active',
    fn() => '($user["is_active"] ?? false)',
    fn(array $vars) => $vars['user']['is_active'] ?? false
);

// more register calls

$user = [
    'is_admin' => false,
    'is_active' => true,
    'created_at' => (new DateTime('-40 days'))->format('Y-m-d H:i:s'),
    'banned' => false,
    'country' => 'NL',
    'credit_score' => 660,
    'override' => false,
];

// Evaluate
$result = $lang->evaluate($expr, ['user' => $user]);
Enter fullscreen mode Exit fullscreen mode

The register method is a bit clunky because the second argument is called when the compile method executes, and the third argument is called when the evaluate method is executed.

But the goal, having rules as data, is achieved.
It would have been nicer if PHP had operator magic methods like Python.
At least the PHP version can enforce type checking, which is not the case for the Python code.

For people who want to know more about the Python overloading methods go to Real Python.

Top comments (0)