sanmai/later

Later: 延迟包装对象

维护者

详细信息

github.com/sanmai/later

源代码

问题

资助包维护!
sanmai

0.1.4 2023-10-24 00:25 UTC

This package is auto-updated.

Last update: 2024-09-18 05:47:00 UTC


README

Latest Stable Version CI Coverage Status Type Coverage

这个经过严格测试的全类型库只需工作。它既不定义也不抛出任何异常。

安装

composer require sanmai/later

最新版本需要PHP 7.4或更高版本。

使用

要使用此模式,您需要一个生成器函数,生成您想要延迟生成的单个类型项。将其传递给later(),这是一个返回Deferred对象的静态包装器

例如

use function Later\later;

$deferred = later(function () {
    $deepThought = new DeepThought();
    $deepThought->solveTheQuestion();

    yield $deepThought;
});

然后当需要时调用get(),需要多少次就调用多少次

$deferred->get()->getAnswer(); // 42
$deferred->get()->getAnswer(); // same 42

使用生成器而不是传统回调的好处是:语言保证任何生成器恰好使用一次。您可以确保它不会被调用两次。

但这还不是全部:继续阅读。

无需回调

即时创建闭包生成器并不总是方便。而且这些闭包与过于熟悉的回调差别很大。看起来几乎完全一样。

此模式的力量在于其能够使用任何函数,以前返回单个值,而无需任何额外的回调或闭包。

考虑以下差异

 private function makeFooBar()
 {
    //...

-    return $foo;
+    yield $foo;
 }

在混合中添加Later\lazy之后

 use function Later\lazy;

 public function __construct()
 {
-    $this->fooBar = $this->makeFooBar();
+    $this->lazyFooBar = lazy($this->makeFooBar());
 }

 public function usesFooBar()
 {
     if ($fooBarReallyRequired) {
-        $this->fooBar->getResult();
+        $this->lazyFooBar->get()->getResult();
     }
 }

我们可以看到,原始方法中的这个简单、单行的更改使我们的程序免于创建可能不需要的东西,将此过程推迟到最后一刻,同时避免了任何回调的使用。

类型透明性

该库完全类型化。支持PHPStanPsalmPhan

为了利用此功能,建议将包含此对象的变量声明为\Later\Interfaces\Deferred<Type>

在这个例子中,它将是Deferred<DeepThought>

use Later\Interfaces\Deferred;
use function Later\lazy;

final class HyperIntelligentMice
{
    /** @var Deferred<DeepThought> */
    private $supercomputer;

    public function __construct(DeepThought $deepThought)
    {
        $this->supercomputer = lazy(self::updateDeepThought($deepThought));
    }

    /** @return iterable<DeepThought> */
    private static function updateDeepThought(DeepThought $deepThought): iterable
    {
        $deepThought->solveTheQuestion();

        yield $deepThought;
    }

    public function getAnswer(): int
    {
        return $this->supercomputer->get()->getAnswer();
    }
}

按照这种方法,静态分析器将能够理解调用的内容和返回的内容。

贪婪执行

如果程序调用Deferred对象,但不需要延迟评估怎么办?例如,因为结果已经可用,正从缓存中加载。

没问题,有相应的函数

use function Later\now;

$deferred = now($result);
$deferred->get(); // returns $result

这个延迟但非延迟的对象实现了相同的接口,可以用于任何需要正常Deferred对象的地方。

编写测试

底层的Deferred对象对输入类型相当宽松。它将乐意接受任何iterable,而不仅仅是生成器。

这使得它在模拟中使用起来非常容易

use function Later\lazy;

$this->lazyDependency = lazy([
    $myDependency,
]);

对于常数和已知答案,最好使用非延迟变体

use function Later\now;

$this->lazyDependency = now($myDependency);

就是这样。无需通过循环组装闭包等。

如果其他什么都不做,可以为其创建一个通用的模拟

$deferredMock = $this->createMock(\Later\Interfaces\Deferred::class);
$deferredMock
    ->expects($this->once())
    ->method('get')
    ->willReturn($myDependency)
;

API概述