nicmart/building 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

nicmart/building

Composer 安装命令:

composer require nicmart/building

包简介

A PHP library that abstracts the definition of fluent builders.

README 文档

README

Build Status Scrutinizer Quality Score

Building is, more than a library, a suggestion on how to model fluent builders in your php code.

What is a fluent builder

Building the object graph of the instances of your object model requires often hard to read lines of code, with a lot of nested instantiations and method calls.

In that case you can benefit from a Fluent Builder, adding an easy-to-read and easy-to-write separate API on top of your object API.

The builder offers an alternative API for the construction of your objects, and its fluent interface helps the code to be readable and more DSL-oriented.

How Building works

The approach of this library is using nested builders to define complex objects, and the key point is passing a "finalizing callback", from the parent builder to the child one, that will be called by the child builder when the subvalue has been built.

This decouples completely the child builder from the parent: the responsability of what to do with the builded object lies completely on the parent builder.

A simple example

Let's see now a little example which explains how to define a builder for boolean predicates. The example is really simple and it could be improved a lot, but it is fine for our purpose.

You have atomic predicates, like equalities and inequalities, and composite ones, like ANDS and 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) { ... }
}

The expression

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

will be represented in your model by the code

$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))
;

Let's now define a builder for our predicates.

A builder implements the interface NicMart\Building\Builder, and in the library you can find an abstract class, NicMart\Building\AbstractBuilder, that implements the methods of that interface for you.

A Builder, as you can see by the interface, does exactly two things: it provides a way to set a callback and implements an end() method that will be called by the client code when the building of the object has ended. The end() method will call the callback and returns the returned value.

In this way the builder's responsability is only to build the value, and it delegates completely to the parent builder (calling the callback passed by the parent builder) the responsability to deal with the just builded subvalue.

Going back to our example will clarify the process.

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;
    }
}

We can now use the builder to define our predicate in a way closer to the domain of boolean expressions:

$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

The last end() automatically returns the builded object because, by default, the abstract builder class sets for itself a callback that returns the builded value.

Other examples

For testing purposes I have included an ArrayBuilder and an ObjectBuilder in the repository.

Drawbacks

You have to be aware of some drawbacks of fluent interfaces and method chaining in general:

  • Code completion: although there is no use of magic methods, the specific builder type returned by the end() is known only at runtime, so IDES fail to autocomplete builder methods after an end().
  • You often violate the Law Of Demeter when using nested builders.
  • Marco Pivetta thinks that fluent interfaces are evil. There are a lot of good points there, but I think that the concept of "Contract" expressed there is much more restrictive than the language itself (or, better, the phpdoc type system) can ensure.

Bibliography

Install

The best way to install Building is through composer.

Just create a composer.json file for your project:

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

Then you can run these two commands to install it:

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

or simply run composer install if you have have already installed the composer globally.

Then you can include the autoloader, and you will have access to the library classes:

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

nicmart/building 适用场景与选型建议

nicmart/building 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 429 次下载、GitHub Stars 达 16, 最近一次更新时间为 2013 年 08 月 26 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 nicmart/building 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 nicmart/building 我们能提供哪些服务?
定制开发 / 二次开发

基于 nicmart/building 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 429
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 16
  • 点击次数: 4
  • 依赖项目数: 3
  • 推荐数: 0

GitHub 信息

  • Stars: 16
  • Watchers: 2
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2013-08-26