From be142b98849053e8c816e60f188a874de8c82ca3 Mon Sep 17 00:00:00 2001 From: Deeka Wong Date: Wed, 5 Feb 2025 20:18:54 +0800 Subject: [PATCH 1/3] Init v3.2 (#7278) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a8d56c3..bc408cf 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "pull-request": "https://github.com/hyperf/hyperf/pulls" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/container": "^1.0 || ^2.0" }, "autoload": { From 3cb4214605f0e6f43f769619c8af260c7fee164a Mon Sep 17 00:00:00 2001 From: Deeka Wong Date: Thu, 13 Mar 2025 14:54:35 +0800 Subject: [PATCH 2/3] Added `Hyperf\Pipeline\Pipeline::finally()`. (#7247) --- src/Pipeline.php | 25 ++++++++- tests/PipelineTest.php | 119 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/Pipeline.php b/src/Pipeline.php index 78b05bc..4b9966d 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -36,6 +36,11 @@ class Pipeline */ protected string $method = 'handle'; + /** + * The final callback to be executed after the pipeline ends regardless of the outcome. + */ + protected ?Closure $finally = null; + public function __construct(protected ContainerInterface $container) { } @@ -78,7 +83,13 @@ public function then(Closure $destination) { $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)); - return $pipeline($this->passable); + try { + return $pipeline($this->passable); + } finally { + if ($this->finally) { + ($this->finally)($this->passable); + } + } } /** @@ -89,6 +100,18 @@ public function thenReturn() return $this->then(fn ($passable) => $passable); } + /** + * Set a final callback to be executed after the pipeline ends regardless of the outcome. + * + * @return $this + */ + public function finally(Closure $callback) + { + $this->finally = $callback; + + return $this; + } + /** * Get the final piece of the Closure onion. */ diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index 9bcb42f..0b7d7a1 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -12,6 +12,7 @@ namespace HyperfTest\Pipeline; +use Exception; use Hyperf\Context\ApplicationContext; use Hyperf\Pipeline\Pipeline; use HyperfTest\Pipeline\Stub\FooPipeline; @@ -19,6 +20,7 @@ use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use stdClass; /** * @internal @@ -211,6 +213,123 @@ public function testHandleCarry() $this->assertSame($id + 6, $result); } + public function testPipelineFinally() + { + $pipeTwo = function ($piped, $next) { + $_SERVER['__test.pipe.two'] = $piped; + + $next($piped); + }; + + $result = (new Pipeline($this->getContainer())) + ->send('foo') + ->through([PipelineTestPipeOne::class, $pipeTwo]) + ->finally(function ($piped) { + $_SERVER['__test.pipe.finally'] = $piped; + }) + ->then(function ($piped) { + return $piped; + }); + + $this->assertSame(null, $result); + $this->assertSame('foo', $_SERVER['__test.pipe.one']); + $this->assertSame('foo', $_SERVER['__test.pipe.two']); + $this->assertSame('foo', $_SERVER['__test.pipe.finally']); + + unset($_SERVER['__test.pipe.one'], $_SERVER['__test.pipe.two'], $_SERVER['__test.pipe.finally']); + } + + public function testPipelineFinallyMethodWhenChainIsStopped() + { + $pipeTwo = function ($piped) { + $_SERVER['__test.pipe.two'] = $piped; + }; + + $result = (new Pipeline($this->getContainer())) + ->send('foo') + ->through([PipelineTestPipeOne::class, $pipeTwo]) + ->finally(function ($piped) { + $_SERVER['__test.pipe.finally'] = $piped; + }) + ->then(function ($piped) { + return $piped; + }); + + $this->assertSame(null, $result); + $this->assertSame('foo', $_SERVER['__test.pipe.one']); + $this->assertSame('foo', $_SERVER['__test.pipe.two']); + $this->assertSame('foo', $_SERVER['__test.pipe.finally']); + + unset($_SERVER['__test.pipe.one'], $_SERVER['__test.pipe.two'], $_SERVER['__test.pipe.finally']); + } + + public function testPipelineFinallyOrder() + { + $std = new stdClass(); + + $result = (new Pipeline($this->getContainer())) + ->send($std) + ->through([ + function ($std, $next) { + $std->value = 1; + + return $next($std); + }, + function ($std, $next) { + ++$std->value; + + return $next($std); + }, + ])->finally(function ($std) { + $this->assertSame(3, $std->value); + + ++$std->value; + })->then(function ($std) { + ++$std->value; + + return $std; + }); + + $this->assertSame(4, $std->value); + $this->assertSame(4, $result->value); + } + + public function testPipelineFinallyWhenExceptionOccurs() + { + $std = new stdClass(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('My Exception: 1'); + + try { + (new Pipeline($this->getContainer())) + ->send($std) + ->through([ + function ($std, $next) { + $std->value = 1; + + return $next($std); + }, + function ($std) { + throw new Exception('My Exception: ' . $std->value); + }, + ])->finally(function ($std) { + $this->assertSame(1, $std->value); + + ++$std->value; + })->then(function ($std) { + $std->value = 0; + + return $std; + }); + } catch (Exception $e) { + $this->assertSame('My Exception: 1', $e->getMessage()); + $this->assertSame(2, $std->value); + + throw $e; + } + } + protected function getContainer() { $container = Mockery::mock(ContainerInterface::class); From 53334d4f2d56dcca88a9e6697fc31f3013e0792b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=93=AD=E6=98=95?= <715557344@qq.com> Date: Wed, 10 Sep 2025 17:49:02 +0800 Subject: [PATCH 3/3] Fixed error macroable version for pipeline. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9bc25cb..dfd29e5 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ }, "require": { "php": ">=8.2", - "hyperf/macroable": "~3.1.0", + "hyperf/macroable": "~3.2.0", "psr/container": "^1.0 || ^2.0" }, "autoload": {