diff --git a/src/PluginChain.php b/src/PluginChain.php new file mode 100644 index 0000000..220ba36 --- /dev/null +++ b/src/PluginChain.php @@ -0,0 +1,65 @@ +plugins = $plugins; + $this->clientCallable = $clientCallable; + $this->maxRestarts = (int) ($options['max_restarts'] ?? 0); + } + + private function createChain(): callable + { + $lastCallable = $this->clientCallable; + $reversedPlugins = array_reverse($this->plugins); + + foreach ($reversedPlugins as $plugin) { + $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable) { + return $plugin->handleRequest($request, $lastCallable, $this); + }; + } + + return $lastCallable; + } + + public function __invoke(RequestInterface $request): Promise + { + if ($this->restarts > $this->maxRestarts) { + throw new LoopException('Too many restarts in plugin client', $request); + } + + ++$this->restarts; + + return $this->createChain()($request); + } +} diff --git a/src/PluginClient.php b/src/PluginClient.php index f935b0d..0d330b1 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -4,7 +4,6 @@ namespace Http\Client\Common; -use Http\Client\Common\Exception\LoopException; use Http\Client\Exception as HttplugException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; @@ -44,11 +43,11 @@ final class PluginClient implements HttpClient, HttpAsyncClient private $options; /** - * @param ClientInterface|HttpAsyncClient $client - * @param Plugin[] $plugins + * @param ClientInterface|HttpAsyncClient $client An HTTP async client + * @param Plugin[] $plugins A plugin chain * @param array $options { * - * @var int $max_restarts + * @var int $max_restarts * } */ public function __construct($client, array $plugins = [], array $options = []) @@ -120,32 +119,11 @@ private function configure(array $options = []): array /** * Create the plugin chain. * - * @param Plugin[] $pluginList A list of plugins + * @param Plugin[] $plugins A plugin chain * @param callable $clientCallable Callable making the HTTP call */ - private function createPluginChain(array $pluginList, callable $clientCallable): callable + private function createPluginChain(array $plugins, callable $clientCallable): callable { - $firstCallable = $lastCallable = $clientCallable; - - while ($plugin = array_pop($pluginList)) { - $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) { - return $plugin->handleRequest($request, $lastCallable, $firstCallable); - }; - - $firstCallable = $lastCallable; - } - - $firstCalls = 0; - $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) { - if ($firstCalls > $this->options['max_restarts']) { - throw new LoopException('Too many restarts in plugin client', $request); - } - - ++$firstCalls; - - return $lastCallable($request); - }; - - return $firstCallable; + return new PluginChain($plugins, $clientCallable, $this->options); } } diff --git a/tests/PluginChainTest.php b/tests/PluginChainTest.php new file mode 100644 index 0000000..95fdfab --- /dev/null +++ b/tests/PluginChainTest.php @@ -0,0 +1,93 @@ +func = $func; + } + + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + ($this->func)($request, $next, $first); + + return $next($request); + } + }; + } + + public function testChainShouldInvokePluginsInReversedOrder(): void + { + $pluginOrderCalls = []; + + $plugin1 = $this->createPlugin(static function () use (&$pluginOrderCalls) { + $pluginOrderCalls[] = 'plugin1'; + }); + $plugin2 = $this->createPlugin(static function () use (&$pluginOrderCalls) { + $pluginOrderCalls[] = 'plugin2'; + }); + + $request = $this->prophesize(RequestInterface::class); + $responsePromise = $this->prophesize(Promise::class); + + $clientCallable = static function () use ($responsePromise) { + return $responsePromise->reveal(); + }; + + $pluginOrderCalls = []; + + $plugins = [ + $plugin1, + $plugin2, + ]; + + $pluginChain = new PluginChain($plugins, $clientCallable); + + $result = $pluginChain($request->reveal()); + + $this->assertSame($responsePromise->reveal(), $result); + $this->assertSame(['plugin1', 'plugin2'], $pluginOrderCalls); + } + + public function testShouldThrowLoopExceptionOnMaxRestarts(): void + { + $this->expectException(LoopException::class); + + $request = $this->prophesize(RequestInterface::class); + $responsePromise = $this->prophesize(Promise::class); + $calls = 0; + $clientCallable = static function () use ($responsePromise, &$calls) { + ++$calls; + + return $responsePromise->reveal(); + }; + + $pluginChain = new PluginChain([], $clientCallable, ['max_restarts' => 2]); + + $pluginChain($request->reveal()); + $this->assertSame(1, $calls); + $pluginChain($request->reveal()); + $this->assertSame(2, $calls); + $pluginChain($request->reveal()); + $this->assertSame(3, $calls); + $pluginChain($request->reveal()); + } +}