diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index 81c5d7e7..d943077b 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -627,6 +627,101 @@ function zm_internal_errcode($code): string return "[ErrCode:{$code}] "; } +if (!function_exists('chain')) { + define('CARRY', '{carry}'); + + /** + * 链式调用对象方法 + * + * 如需使用上一步的返回值作为参数,请使用 CARRY 常量替换对应的参数值 + * + * @param object $object 需要进行链式调用的对象 + * @return object 链式操作对象,可当成传入的对象使用 + */ + function chain(object $object): object + { + return new class($object) { + /** + * 最后一次执行的方法的返回值 + * + * @var mixed + */ + protected $return; + + /** + * 需要进行链式调用的对象 + * + * @var object + */ + protected $wrapped; + + /** + * 构造链式调用匿名类 + */ + public function __construct(object $object) + { + $this->wrapped = $object; + } + + /** + * 代理所有调用 + */ + public function __call(string $name, array $arguments) + { + if (($index = array_search(CARRY, $arguments, true)) !== false) { + $arguments[$index] = $this->return; + } + $this->return = $this->wrapped->{$name}(...$arguments); + return $this; + } + + /** + * 返回最后执行结果 + */ + public function __toString() + { + return (string) $this->return; + } + + /** + * 允许调用最后返回结果 + */ + public function __invoke() + { + return $this->return; + } + + /** + * 使用链式操作对象作为参数,执行回调 + */ + public function tap(callable $callback): self + { + $callback($this->wrapped); + return $this; + } + }; + } +} + +if (!function_exists('stopwatch')) { + /** + * 执行回调函数并返回平均执行耗时 + * + * @param int $times 执行次数 + * @return float 平均执行耗时 + */ + function stopwatch(callable $callback, int $times = 1): float + { + $total = 0; + for ($i = 0; $i < $times; ++$i) { + $start = microtime(true); + $callback(); + $total += microtime(true) - $start; + } + return $total / $times; + } +} + /** * 将可能为数组的参数转换为字符串 * diff --git a/tests/ZM/GlobalFunctionsTest.php b/tests/ZM/GlobalFunctionsTest.php new file mode 100644 index 00000000..a786995a --- /dev/null +++ b/tests/ZM/GlobalFunctionsTest.php @@ -0,0 +1,43 @@ +getMockBuilder(\stdClass::class) + ->addMethods(['foo', 'bar', 'baz']) + ->getMock(); + $mock->expects($this->once()) + ->method('foo') + ->willReturn('foo'); + $mock->expects($this->once()) + ->method('bar') + ->with('foo') + ->willReturn('bar'); + $mock->expects($this->once()) + ->method('baz') + ->with('bar') + ->willReturn('baz'); + + $result = chain($mock)->foo()->bar(CARRY)->baz(CARRY); + + $this->assertEquals('baz', $result); + } + + public function testStopwatch(): void + { + $time = stopwatch(static function () { + usleep(10000); + }); + $this->assertLessThan(0.1, $time); + } +}