krixon/rules

将文本规则转换为规范对象

3.0.0 2020-03-08 20:13 UTC

README

Build Status Coverage Status Code Climate Latest Stable Version Latest Unstable Version License

用于定义和构建规范模式对象的一种简单语言。

先决条件

  • PHP 7.2+

安装

通过 composer 安装

要使用 Composer 安装此库,请运行以下命令

$ composer require krixon/rules

您可以在 Packagist 上找到此库。

从源代码安装

# HTTP
$ git clone https://github.com/krixon/rules.git
# SSH
$ git clone git@github.com:krixon/rules.git

支持的语法

有关规则语法的详细信息,请参阅语法文档

使用方法

使用此库的主要任务是实现 BaseCompiler::generate()。此方法有以下签名

public function generate(ComparisonNode $comparison) : Specification

其任务是生成一个从 ComparisonNode AST 对象生成的 Specification 对象。

ComparisonNode 由一个 IdentifierNode 组成,用于识别应该检查规范的数据,以及一个包含要比较的值的 LiteralNode。它还包含有关比较类型(等于,大于等)的信息。

例如,假设您有一个可以应用于 User 对象的以下规范

class EmailAddressMatches implements Specification
{
    private $email;
    
    
    public function __construct(string $email)
    {
        $this->email = $email;
    }
    
    
    public function isSatisfiedBy($value) : bool
    {
        return $value instanceof User && $value->hasEmailAddress($this->email);
    }
}

您可以为此规范定义一个规则,例如 email is "karl.rixon@gmail.com"

在这个规则中,email 是一个标识符,它引用用户的电子邮件地址。如何解释给定的标识符取决于您。字符串值 email 在解析期间被转换为 IdentifierNode AST 节点。此节点可以通过 ComparisonNode::identifier() 访问。

比较操作符是 is,这意味着“等于”。您可以使用 ComparisonNode::isEquals()ComparisonNode::isLessThan() 等来确定比较类型。

最后,karl.rixon@gmail.com 在解析期间被转换为 StringNode AST 节点。此节点可以通过 ComparisonNode::value() 访问。

基于以上内容,BaseCompiler::generate() 方法可能如下实现

class MyCompiler extends BaseCompiler
{
  public function generate(ComparisonNode $comparison) : Specification
  {
      $identifier = $comparison->identifierFullName();
      
      if (strtolower($indentifier) !== 'email') {
          throw CompilerError::unknownIdentifier($identifier);
      }
      
      if (!$comparison->isEquals()) {
          throw CompilerError::unsupportedComparisonType($comparison->type(), $identifier);
      }
  
      return new EmailAddressMatches($comparison->literalValue());
  }
}

委托生成任务

虽然简单情况下扩展 BaseCompiler 很方便,但当您需要支持许多规范时,它会变得复杂。在这种情况下,您可能希望将生成工作委托给专用服务。

为此提供了 DelegatingCompiler 类。要使用它,首先创建一个实现 SpecificationGenerator 接口的类,该接口定义了一个方法

public function attempt(ComparisonNode $comparison) : ?Specification;

这与 BaseCompiler::generate() 非常相似,但是返回 Specification 是可选的。

然后,将您的类的实例注册到 DelegatingCompiler

$generator = new EmailAddressGenerator();
$compiler  = new DelegatingCompiler($generator);

当调用 DelegatingCompiler::compile() 时,DelegatingCompiler 将遍历所有已注册的生成器,并对每个 ComparisonNode 调用 SpecificationGenerator::attempt()

通过 DelegatingCompiler 的构造函数提供的所有 SpecificationGenerator 具有相同的 0 优先级,但它们也可以注册为具有显式优先级

$generator = new EmailAddressGenerator();
$compiler  = new DelegatingCompiler();

$compiler->register($generator, 100); // Priority of 100.

具有更高优先级的 SpecificationGenerator 将首先被调用。

该库提供了一些内置规范和相应的生成器,如果需要可以使用。这些也易于通过自定义逻辑进行扩展。

否定比较

ComparisonNode不暴露否定比较,如不等于不匹配。然而,通过在比较运算符前添加not,这种否定比较在语言中是受支持的。

email not is "karl.rixon@gmail.com"
address.county not matches "/(east|west)\s+sussex/i"
age not > 5

您无需编写任何代码来处理这些情况,因为编译器将根据非否定比较生成一个Specification,然后将其结果包装在一个Not规范中,该规范简单地反转被包装的Specification返回的Specification::isSatisfiedBy的结果。

可以使用简写语法not is,只需省略is即可。

email not "karl.rixon@gmail.com"

贡献

请参阅CONTRIBUTING.md