SkillAgentSearch skills...

Expression

Implementation of the Specification pattern and logical expressions for PHP.

Install / Use

/learn @webmozart/Expression
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Webmozart Expression

Build Status Build status Latest Stable Version Total Downloads Dependency Status

Latest release: 1.0.0

PHP >= 5.3.9

This library implements the [Specification Pattern] for PHP. You can use it to easily filter results of your domain services by evaluating logical expressions.

Conversely to [rulerz], this library focuses on providing a usable and efficient PHP API first. An expression language that converts string expressions into Expression instances can be built on top, but is not included in the current release.

Visitors can be implemented that convert Expression objects into Doctrine queries and similar objects.

Installation

Use [Composer] to install the package:

$ composer require webmozart/expression

Basic Usage

Use the [Expression] interface in finder methods of your service classes:

use Webmozart\Expression\Expression;

interface PersonRepository
{
    public function findPersons(Expression $expr);
}

When querying persons from the repository, you can create new expressions with the [Expr] factory class:

$expr = Expr::method('getFirstName', Expr::startsWith('Tho'))
    ->andMethod('getAge', Expr::greaterThan(35));
    
$persons = $repository->findPersons($expr);

The repository implementation can use the evaluate() method to match individual persons against the criteria:

class PersonRepositoryImpl implements PersonRepository
{
    private $persons = [];
    
    public function findPersons(Expression $expr)
    {
        return Expr::filter($this->persons, $expr);
    }
}

Visitors can be built to convert expressions into other types of specifications, such as Doctrine query builders.

Domain Expressions

Extend existing expressions to build domain-specific expressions:

class IsPremium extends Method
{
    public function __construct()
    {
        parent::__construct('isPremium', [], Expr::same(true));
    }
}

class HasPreviousBookings extends Method
{
    public function __construct()
    {
        parent::__construct(
            'getBookings', 
            [], 
            Expr::count(Expr::greaterThan(0))
        );
    }
}

// Check if a customer is premium
if ((new IsPremium())->evaluate($customer)) {
    // ...
}

// Get premium customers with bookings
$customers = $repo->findCustomers(Expr::andX([
    new IsPremium(),
    new HasPreviousBookings(),
]));

The following sections describe the core expressions in detail.

Expressions

The [Expr] class is able to create the following expressions:

Method | Description --------------------------- | -------------------------------------------------------- null() | Check that a value is null notNull() | Check that a value is not null isEmpty() | Check that a value is empty (using empty()) notEmpty() | Check that a value is not empty (using empty()) isInstanceOf($className) | Check that a value is instance of a class (using instanceof) equals($value) | Check that a value equals another value (using ==) notEquals($value) | Check that a value does not equal another value (using !=) same($value) | Check that a value is identical to another value (using ===) notSame($value) | Check that a value does not equal another value (using !==) greaterThan($value) | Check that a value is greater than another value greaterThanEqual($value) | Check that a value is greater than or equal to another value lessThan($value) | Check that a value is less than another value lessThanEqual($value) | Check that a value is less than or equal to another value startsWith($prefix) | Check that a value starts with a given string endsWith($suffix) | Check that a value ends with a given string contains($string) | Check that a value contains a given string matches($regExp) | Check that a value matches a regular expression in($values) | Check that a value occurs in a list of values keyExists($key) | Check that a key exists in a value keyNotExists($key) | Check that a key does not exist in a value true() | Always true (tautology) false() | Always false (contradiction)

Selectors

With composite values like arrays or objects, you often want to match only a part of that value (like an array key or the result of a getter) against an expression. You can select the evaluated parts with a selector.

When you evaluate arrays, use the key() selector to match the value of an array key:

$expr = Expr::key('age', Expr::greaterThan(10));

$expr->evaluate(['age' => 12]);
// => true

Each selector method accepts the expression as last argument that should be evaluated for the selected value.

When evaluating objects, use property() and method() to evaluate the values of properties and the results of method calls:

$expr = Expr::property('age', Expr::greaterThan(10));

$expr->evaluate(new Person(12));
// => true

$expr = Expr::method('getAge', Expr::greaterThan(10));

$expr->evaluate(new Person(12));
// => true

The method() selector also accepts arguments that will be passed to the method. Pass the arguments before the evaluated expression:

$expr = Expr::method('getParameter', 'age', Expr::greaterThan(10));

$expr->evaluate(new Person(12));
// => true

You can nest selectors to evaluate expressions for nested objects or arrays:

$expr = Expr::method('getBirthDate', Expr::method('format', 'Y', Expr::lessThan(2000)));

$expr->evaluate(new Person(12));
// => false

The following table lists all available selectors:

Method | Description ------------------------ | ------------------------------------------------------------------------------- key($key, $expr) | Evaluate an expression for a key of an array method($name, $expr) | Evaluate an expression for the result of a method call property($name, $expr) | Evaluate an expression for the value of a property count($expr) | Evaluate an expression for the count of a collection

The count() selector accepts arrays and Countable objects.

Quantors

Quantors are applied to collections and test whether an expression matches for a certain number of elements. A famous one is the all-quantor:

$expr = Expr::all(Expr::method('getAge', Expr::greaterThan(10)));

$expr->evaluate([new Person(12), new Person(11)]);
// => true

Quantors accept both arrays and Traversable instances. The following table lists all available quantors:

Method | Description ------------------------ | ------------------------------------------------------------------------------- all($expr) | Check that an expression matches for all entries of a collection atLeast($count, $expr) | Check that an expression matches for at least $count entries of a collection atMost($count, $expr) | Check that an expression matches for at most $count entries of a collection exactly($count, $expr) | Check that an expression matches for exactly $count entries of a collection

Logical Operators

You can negate an expression with not():

$expr = Expr::not(Expr::method('getFirstName', Expr::startsWith('Tho')));

You can connect multiple expressions with "and" using the and*() methods:

$expr = Expr::method('getFirstName', Expr::startsWith('Tho'))
    ->andMethod('getAge', Expr::greaterThan(35));

The same is possible for the "or" operator:

$expr = Expr::method('getFirstName', Expr::startsWith('Tho'))
    ->orMethod('getAge', Expr::greaterThan(35));

You can use and/or inside selectors:

$expr = Expr::method('getAge', Expr::greaterThan(35)->orLessThan(20));

If you want to mix and match "and" and "or" operators, use andX() and orX() to add embedded expressions:

$expr = Expr::method('getFirstName', Expr::startsWith('Tho'))
    ->andX(
        Expr::method('getAge', Expr::lessThan(14))
            ->orMethod('isReduced', Expr::same(true))
    );

Testing

To make sure that PHPUnit compares [Expression] objects correctly, you should register the [ExpressionComparator] with PHPUnit in your PHPUnit bootstrap file:

// tests/bootstrap.php
use SebastianBergmann\Comparator\Factory;
use Webmozart\Expression\PhpUnit\ExpressionComparator;

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

Factory::getInstance()->register(new ExpressionComparator());

Make sure the file is registered correctly in phpunit.xml.dist:

<!-- phpunit.xml.dist -->
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="tests/bootstrap.php" colors="true">
    <!-- ... -->
</phpunit>

The [ExpressionComparator] makes sure that PHPUnit compares different [Expression] instances by logical equivalence instead of by object equality. For example, the following [Expression] are logically equivalent, but not equal as objects:

// Logically equivalent

Related Skills

View on GitHub
GitHub Stars214
CategoryProduct
Updated1y ago
Forks10

Languages

PHP

Security Score

80/100

Audited on Feb 16, 2025

No findings