sanmai / later
Later: 延迟包装对象
Requires
- php: >=7.4
Requires (Dev)
- ergebnis/composer-normalize: ^2.8
- friendsofphp/php-cs-fixer: ^3.35.1
- infection/infection: >=0.27.6
- phan/phan: >=2
- php-coveralls/php-coveralls: ^2.0
- phpstan/phpstan: >=1.4.5
- phpunit/phpunit: >=9.5 <10
- vimeo/psalm: >=2
README
这个经过严格测试的全类型库只需工作。它既不定义也不抛出任何异常。
安装
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(); } }
我们可以看到,原始方法中的这个简单、单行的更改使我们的程序免于创建可能不需要的东西,将此过程推迟到最后一刻,同时避免了任何回调的使用。
类型透明性
为了利用此功能,建议将包含此对象的变量声明为\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) ;