装饰器和方法链

Decorators and method chaining

目标:我想 "decorate" Laravel 的查询生成器具有附加功能(不直接修改它)。

示例问题:我会尽量保持简短。我在我的装饰器上实现了一个 get 方法:

public function get($columns = ['*'])
{
    return $this->cache->get(implode('.', $columns), function () use ($columns) {
        return $this->queryBuilder->get($columns);
    });
}

我还将对装饰器上未实现的方法的所有调用委托给查询生成器。

public function __call($method, $parameters)
{
    return call_user_func_array([$this->queryBuilder, $method], $parameters);
}

如您所料,直接调用装饰器时工作正常。但几乎每个人都习惯在使用查询生成器时将方法链接在一起。

$queryBuilder = (new CachingDecorator( new QueryBuilder , $app['cache.store'] ));

// get all users
$queryBuilder->from('users')->get();

// get one user
$queryBuilder->from('users')->first(); // <-- delegates to get() internally

问题:上面直接调用的结果没有被缓存。显然是因为 from 方法 returns 是 Laravel 查询生成器的一个实例,而不是我的装饰器。

问题:是否有一些有用的模式可以帮助解决这个问题?或者这是装饰器模式的限制?

我的第一个想法是尝试将 $this 绑定到另一个对象,就像您在 Javascript 中所做的那样。我不认为 PHP 允许这样做。

我能想到的最佳解决方案涉及 class 将查询构建器对象映射到它的装饰器,and/or 某种基本装饰器,它重新实现几乎所有方法在查询生成器对象中(不是这个对象的粉丝,因为它完全抛弃了 window 的 DRY 原则)。

附加说明:我知道我可以通过不将方法调用链接在一起来回避这个问题。没脑子吧?除非要求团队中的每个开发人员避免将他们的调用链接在一起是不合理的。我宁愿解决这个问题,也不愿回避它。

您应该 return 来自 __call 方法的装饰器:

public function __call($method, $parameters)
{
    $result = call_user_func_array([$this->queryBuilder, $method], $parameters);

    return $result === $this->queryBuilder ? $this : $result;
}

如果您使用的是 PHP 5.6+,您可以使用扩展运算符稍微清理一下:

public function __call($method, $parameters)
{
    $result = $this->queryBuilder->$method(...$parameters);

    return $result === $this->queryBuilder ? $this : $result;
}