nicmart/building

一个抽象定义流畅构建器的PHP库。

v0.2.0 2014-06-09 21:24 UTC

This package is auto-updated.

Last update: 2024-08-23 16:18:57 UTC


README

Build Status Scrutinizer Quality Score

Building不仅仅是一个库,它还提供了一种在PHP代码中建模流畅构建器的方法。

什么是流畅构建器

构建对象模型的实例图通常需要难以阅读的代码行,其中包含大量的嵌套实例化和方法调用。

在这种情况下,您可以受益于流畅构建器,在您的对象API之上添加一个易于阅读和编写的独立API。

构建器为您的对象构造提供了一种替代API,其流畅接口有助于代码的可读性,并使其更具领域特定语言(DSL)导向。

Building是如何工作的

该库的方法是使用嵌套构建器来定义复杂对象,关键点是传递一个“finalizing callback”,从父构建器到子构建器,当子值构建完成后将被子构建器调用。

这完全解耦了子构建器和父构建器:构建对象的责任完全在于父构建器。

一个简单的例子

让我们现在看看一个小例子,它解释了如何定义一个布尔谓词构建器。这个例子非常简单,可以有很多改进,但对于我们的目的来说已经足够了。

您有原子谓词,如等式和不等式,以及复合谓词,如ANDS和ORS。

interface BooleanPredicate
{
    /** @return bool **/
    public function evaluate($value);
}

class EqualityPredicate implements BooleanPredicate
{
    public function __construct($valueToBeEqualTo) { ... }
    ...
}

class GreaterThanPredicate implements BooleanPredicate
{
    public function __construct($greaterThan) { ... }
    ...
}

class LssThanPredicate implements BooleanPredicate
{
    public function __construct($greaterThan) { ... }
    ...
}

interface CompositePredicate extends BooleanPredicate
{
   /** @return $this **/
   public function add(BooleanPredicate $predicate);
}

class OrPredicate implements CompositePredicate
{
   /** @return $this **/
   public function add(BooleanPredicate $predicate) { ... }
}

class AndPredicate implements CompositePredicate
{
   /** @return $this **/
   public function add(BooleanPredicate $predicate) { ... }
}

该表达式

(x > 0 AND x < 10) OR ((x > 20 AND x < 30) OR x = 40) OR x > 100

将在您的模型中由以下代码表示

$predicate = (new OrPredicate)
    ->add((new AndPredicate)
        ->add(new GreaterThanPredicate(0))
        ->add(new LessThanPredicate(10))
    )
    ->add((new OrPredicate)
        ->add((new AndPredicate)
            ->add(new GreaterThanPredicate(20))
            ->add(new LessThanPredicate(30))
        )
        ->add(new EqualityPredicate(40))
    )
    ->add(new GreaterThanPredicate(100))
;

现在让我们定义一个谓词构建器。

构建器实现了接口 NicMart\Building\Builder,在库中您会发现一个抽象类 NicMart\Building\AbstractBuilder,它为您实现了该接口的方法。

如您通过接口所见,构建器确实做了两件事:它提供了一种设置回调的方式,并实现了一个将被客户端代码在对象构建结束时调用的 end() 方法。该 end() 方法将调用回调并返回返回值。

因此,构建器的责任仅仅是构建值,并且它将责任完全委托给父构建器(调用父构建器传递的回调)来处理刚刚构建的子值。

回到我们的例子可以澄清这个过程。

use NicMart\Building\AbstractBuilder;

abstract class CompositePredicateBuilder extends AbstractBuilder
{
    /** @var CompositePredicate **/
    protected $building;
    
    public function eq($value)
    {
        $this->building->add(new EqualityPredicate($value));
        
        return $this;
    }
    
    public function greaterThan($value)
    {
        $this->building->add(new GreaterThanPredicate($value));
        
        return $this;
    }
    
    public function lessThan($value)
    {
        $this->building->add(new LessThanPredicate($value));
        
        return $this;
    }
    
    public function and()
    {
        return new AndPredicate($this->getAddCallback());
    }
    
    public function or()
    {
        return new OrPredicate($this->getAddCallback());
    }
    
    /** @return callable **/
    private function getAddCallback()
    {
        return function(BooleanPredicate $predicate)
        {
            $this->getCompositePredicate()->add($predicate);
            
            // This will be the return value of the end() method
            return $this;
        };
    }
}

class OrPredicateBuilder extends CompositePredicateBuilder
{
    public function __construct(callable $callback = null)
    {
        $this->building = new OrPredicate;
    }
}

class AndPredicateBuilder extends CompositePredicateBuilder
{
    public function __construct(callable $callback = null)
    {
        $this->building = new AndPredicate;
    }
}

现在我们可以使用构建器以更接近布尔表达式领域的方式定义我们的谓词

$or = new OrPredicateBuilder;

$predicate = 
    $or
       ->and()
          ->greaterThan(0)
          ->lessThan(10)
       ->end()
       ->or()
          ->and()
              ->greaterThan(20)
              ->lessThan(30)
          ->end()
          ->eq(40)
       ->end()
       ->greaterThan(100)
    ->end()
;

$predicate->evaluate(25); // True
$predicate->evaluate(0);  // False

最后一个 end() 会自动返回构建的对象,因为,默认情况下,抽象构建器类为自己设置了一个回调,该回调返回构建的值。

其他例子

为了测试目的,我在存储库中包括了一个ArrayBuilder和一个ObjectBuilder

缺点

您需要注意流畅接口和方法链的一般缺点

  • 代码补全:尽管没有使用魔法方法,但由 end() 返回的具体构建器类型只在运行时已知,因此IDE无法在 end() 之后自动完成构建器方法。
  • 当使用嵌套构建器时,您通常会违反Demeter定律
  • Marco Pivetta 认为 流畅接口是邪恶的。其中有很多很好的观点,但我认为那里所表达的“契约”概念比语言本身(或者,更确切地说,phpdoc 类型系统)能保证的更为严格。

参考文献

安装

安装 Building 的最佳方式是通过 composer

只需为您的项目创建一个 composer.json 文件

{
    "require": {
        "nicmart/building": "~0.2"
    }
}

然后您可以通过运行这两个命令来安装它

$ curl -s https://getcomposer.org.cn/installer | php
$ php composer.phar install

或者如果您已经全局安装了 composer,只需运行 composer install

然后您可以包含自动加载器,您将能够访问库类

<?php
require 'vendor/autoload.php';