PHP 调用类 Closure with bound '$this' for decoration of dependent methods

PHP call class Closure with bound `$this` for decoration of dependent methods

提问人:rela589n 提问时间:2/4/2022 最后编辑:rela589n 更新时间:2/7/2022 访问量:119

问:

比以前,我越来越频繁地遇到难以扩展的供应商代码。即使每个必要的类都有接口,它也没有多大帮助。我看到的最糟糕的情况是当一个公共方法使用另一个公共方法而不是专用类时。

举个简单的例子,让我们想象一下在线商店应用程序和实体。通常,Doctrine 被用作 ORM,我们将有一个存储库类。它的界面如下所示:Order

interface ProductRepository 
{
    public function findReferenceBySku(string $sku): Product;

    public function findIdBySku(string $sku): int;
}

方法将对数据库进行一次查询,以便通过其 SKU 找到产品的相应 ID。while 方法将返回存储在工作单元的标识映射中的现有对象或生成代理类。代理是必要的,因为对于我们的特定用例,我们不希望加载所有产品属性(大约一百个),但实体是必要的,以便将其用作相关实体(例如)。findIdBySkufindReferenceBySkuProductPrice

现在,让我们看起来像这样:Product

class Product
{
    public function __construct(
        public int $id,
    ) { }
}

显然,使用数据库的存储库实现将使得使用 .findReferenceBySkufindIdBySku

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.

你对此事有什么想法吗?您将如何解决这个问题?

PHP 学说-ORM 闭包装饰

评论

0赞 Cerad 2/4/2022
考虑编写一个简单但完整的示例,并将其推送到公共 github 存储库。根据您的描述,我无法完全了解问题出在哪里。但是运行某些东西并查看实际错误可能会有所帮助。
0赞 rela589n 2/5/2022
这里的问题是在这一行 - php 无法将方法闭包绑定到其他实例。上面的所有代码都是问题最不可能的完整示例。但是,是的,我会将其发布到存储库。$this->inner->findReferenceBySku(...)->call($this, $sku);
0赞 rela589n 2/7/2022
你可以在这里玩 3v4l.org/CFE1u

答: 暂无答案