提问人:rela589n 提问时间:2/4/2022 最后编辑:rela589n 更新时间:2/7/2022 访问量:119
PHP 调用类 Closure with bound '$this' for decoration of dependent methods
PHP call class Closure with bound `$this` for decoration of dependent methods
问:
比以前,我越来越频繁地遇到难以扩展的供应商代码。即使每个必要的类都有接口,它也没有多大帮助。我看到的最糟糕的情况是当一个公共方法使用另一个公共方法而不是专用类时。
举个简单的例子,让我们想象一下在线商店应用程序和实体。通常,Doctrine 被用作 ORM,我们将有一个存储库类。它的界面如下所示:Order
interface ProductRepository
{
public function findReferenceBySku(string $sku): Product;
public function findIdBySku(string $sku): int;
}
方法将对数据库进行一次查询,以便通过其 SKU 找到产品的相应 ID。while 方法将返回存储在工作单元的标识映射中的现有对象或生成代理类。代理是必要的,因为对于我们的特定用例,我们不希望加载所有产品属性(大约一百个),但实体是必要的,以便将其用作相关实体(例如)。findIdBySku
findReferenceBySku
Product
Price
现在,让我们看起来像这样:Product
class Product
{
public function __construct(
public int $id,
) { }
}
显然,使用数据库的存储库实现将使得使用 .findReferenceBySku
findIdBySku
class ProductDatabaseRepository implements ProductRepository
{
public function findReferenceBySku(string $sku): Product
{
$id = $this->findIdBySku($sku);
return $this->getProductReference($id);
}
private function getProductReference(int $id)
{
// check entity manager for existing object
// and generate proxy if not found
return new Product($id);
}
public function findIdBySku(string $sku): int
{
$this->queryDatabase();
return match($sku) {
'sku1' => 1,
};
}
protected function queryDatabase()
{
// note this method is not public
var_dump('query database');
}
}
在价格加载期间,原来是源有重复的产品SKU,每次数据库查询时都会一次又一次地将SKU与ID相关联。
为了解决这样的问题,我们将装饰原始存储库,并将缓存逻辑添加到装饰器中。
$repository = (new ProductCachedRepository(new ProductDatabaseRepository()));
var_dump('call1', $repository->findReferenceBySku('sku1'));
var_dump('call2', $repository->findReferenceBySku('sku1')); // cache hit
var_dump('call2', $repository->findReferenceBySku('sku1')); // cache hit
最后一步是实现装饰器类 - 这是所有问题出现的地方!我们不能坚持明显的实现,即将调用传递到内部存储库。装饰对象不会使用我们的扩展方法,这给我们带来了很多努力的零利润。ProductCachedRepository::findReferenceBySku()
findIdBySku()
class ProductCachedRepository implements ProductRepository
{
private array $cachedIds = [];
public function __construct(
private ProductRepository $inner
) {}
public function findReferenceBySku(string $sku): Product
{
// not able to delegate this logic directly to inner class
// (loss of findIdBySku() overridden logic)
// return $this->inner->findReferenceBySku($sku);
// as well not able call decorated method with substitude this
// return $this->inner->findReferenceBySku(...)->call($this, $sku);
// therefore copy-paste like this
// $id = $this->findIdBySku($sku);
// return $this->getProductReference($id);
// the only missing thing is the way to create closure from method,
// forget bounded $this and call it with new one
// just like creation of closure identical to method
// with only difference - it is allowed to bind anywhere
// return (function (string $sku): Product {
// $id = $this->findIdBySku($sku);
//
// return $this->getProductReference($id);
//})->call($this, $sku);
}
public function findIdBySku(string $sku): int
{
var_dump('check cache');
return $this->cachedIds[$sku]
??= $this->inner->findIdBySku($sku);
}
public function __call($name, $arguments)
{
// all not public methods of inner repository
// which are not declared in current class
// are delegated to inner repository
return (fn() => $this->$name(...$arguments))
->call($this->inner);
}
}
有没有与方法相同的创建闭包方法。我首先想到的是,但是它失败并发出警告,并且不会使用 new 调用方法。return $this->inner->findReferenceBySku(...)->call($this, $sku);
$this
Warning: Cannot bind method ProductDatabaseRepository::findReferenceBySku() to object of class ProductCachedRepository
.
你对此事有什么想法吗?您将如何解决这个问题?
答: 暂无答案
评论
$this->inner->findReferenceBySku(...)->call($this, $sku);