koshuang/expression

PHP 的规范模式实现和逻辑表达式。

v2.0.0 2022-09-03 16:45 UTC

This package is auto-updated.

Last update: 2024-09-30 01:32:43 UTC


README

Build Status Latest Stable Version Total Downloads

最新版本: 2.0.0

PHP >= 8.1

这是从 https://github.com/webmozart/expression 分支出来的版本。已升级以支持 PHP 8.1。

这个库实现了 PHP 的 规范模式。您可以使用它通过评估逻辑表达式轻松过滤领域服务的查询结果。

rulerz 相反,这个库首先专注于提供可用的和高效的 PHP API。可以在上面构建一个将字符串表达式转换为 Expression 实例的表达式语言,但当前版本不包括在内。

可以实现访问者,将 Expression 对象转换为 Doctrine 查询等类似对象。

安装

使用 Composer 安装包

$ composer require koshuang/expression

基本用法

在您的服务类的方法中使用 Expression 接口

use Webmozart\Expression\Expression;

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

当从存储库查询人员时,您可以使用 Expr 工厂类创建新的表达式

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

$persons = $repository->findPersons($expr);

存储库实现可以使用 evaluate() 方法将单个人员与标准进行匹配

class PersonRepositoryImpl implements PersonRepository
{
    private $persons = [];

    public function findPersons(Expression $expr)
    {
        return Expr::filter($this->persons, $expr);
    }
}

访问者 可以构建以将表达式转换为其他类型的规范,例如 Doctrine 查询构建器。

领域表达式

扩展现有表达式以构建特定领域的表达式

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(),
]));

以下部分将详细介绍核心表达式。

表达式

Expr 类可以创建以下表达式

选择器

对于数组和对象等复合值,您通常只想匹配该值的一部分(如数组键或 getter 的结果)与表达式。您可以使用 选择器 选择评估的部分。

当评估数组时,使用 key() 选择器匹配数组键的值

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

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

每个选择器方法接受一个表达式作为最后一个参数,该表达式应该用于评估选择值。

当评估对象时,使用 property()method() 评估属性值和方法调用的结果

$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

method() 选择器还接受要传递给方法的参数。在评估表达式之前传递参数

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

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

您可以将选择器嵌套以评估嵌套对象或数组的表达式

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

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

以下表列出了所有可用的选择器

count() 选择器接受数组和 Countable 对象。

量词

量词应用于集合,并测试表达式是否匹配一定数量的元素。其中一个著名的量词是所有量词

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

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

量词接受数组和 Traversable 实例。以下表列出了所有可用的量词

逻辑运算符

您可以使用 not() 否定表达式

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

您可以使用and*()方法使用"and"连接多个表达式

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

"or"运算符也可以这样做

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

您可以在选择器中使用and/or

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

如果您想混合使用"and"和"or"运算符,请使用andX()orX()来添加嵌套表达式

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

测试

为了确保PHPUnit正确比较Expression对象,您应该在PHPUnit引导文件中注册ExpressionComparator

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

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

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

请确保该文件已在phpunit.xml.dist中正确注册

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

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

ExpressionComparator确保PHPUnit通过逻辑等价而不是通过对象相等来比较不同的Expression实例。例如,以下Expression在逻辑上是等价的,但作为对象并不相等

// Logically equivalent
$c1 = Expr::notNull()->andSame(35);
$c2 = Expr::same(35)->andNotNull();

$c1 == $c2;
// => false

$c1->equivalentTo($c2);
// => true

// Also logically equivalent
$c1 = Expr::same(35);
$c2 = Expr::oneOf([35]);

$c1 == $c2;
// => false

$c1->equivalentTo($c2);
// => true

表达式转换

在某些情况下,您可能希望将表达式转换为其他表示形式。一个主要的例子是将表达式转换为Doctrine查询。

您可以通过实现自定义的ExpressionVisitor来进行转换。访问者的方法enterExpression()leaveExpression()会在表达式树的每个节点上被调用

use Webmozart\Expression\Traversal\ExpressionVisitor;

class QueryBuilderVisitor implements ExpressionVisitor
{
    private $qb;

    public function __construct(QueryBuilder $qb)
    {
        $this->qb = $qb;
    }

    public function enterExpression(Expression $expr)
    {
        // configure the $qb...
    }

    public function leaveExpression(Expression $expr)
    {
        // configure the $qb...
    }
}

使用ExpressionTraverser与您的访问者遍历表达式

public function expressionToQueryBuilder(Expression $expr)
{
    $qb = new QueryBuilder();

    $traverser = new ExpressionTraverser();
    $traverser->addVisitor(new QueryBuilderVisitor($qb));
    $traverser->traverse($expr);

    return $qb;
}

作者

贡献

对包的贡献总是受欢迎的!

  • 报告问题
  • 您可以在包的Git仓库中获取源代码。

支持

如果您遇到问题,请发送邮件至bschussek@gmail.com或在Twitter上@webmozart。

许可证

本包所有内容均受MIT许可证许可。