From 5c640b578cfef8e117048823f744aea5c8d288ce Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Mon, 16 Jul 2018 21:47:06 +0200 Subject: [PATCH] add pipeline --- src/Utils/FunctionPipeline.php | 48 +++++++++++++++++ src/Utils/UnpackedArgs.php | 13 +++++ src/Utils/index.php | 8 ++- src/functions.php | 2 +- tests/FunctionPipelineTest.php | 95 ++++++++++++++++++++++++++++++++++ tests/bootstrap.php | 2 +- 6 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/Utils/FunctionPipeline.php create mode 100644 src/Utils/UnpackedArgs.php create mode 100644 tests/FunctionPipelineTest.php diff --git a/src/Utils/FunctionPipeline.php b/src/Utils/FunctionPipeline.php new file mode 100644 index 0000000..0bc6c3e --- /dev/null +++ b/src/Utils/FunctionPipeline.php @@ -0,0 +1,48 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Utils; + + +class FunctionPipeline +{ + /** + * @var callable[] + */ + private $functions = []; + + /** + * FunctionPipeline constructor. + * + * @param callable[] $functions + */ + public function __construct(callable ...$functions) + { + $this->functions = $functions; + } + + public function append(callable $callable) + { + return new static(...array_merge($this->functions, [ $callable ])); + } + + public function prepend(callable $callable) + { + return new static($callable, ...$this->functions); + } + + public function __invoke(...$args) + { + return array_reduce($this->functions, function ($arg, $callable) { + return $arg instanceof UnpackedArgs + ? $callable(...$arg->getArrayCopy()) + : $callable($arg); + }, new UnpackedArgs($args)); + } +} \ No newline at end of file diff --git a/src/Utils/UnpackedArgs.php b/src/Utils/UnpackedArgs.php new file mode 100644 index 0000000..534e203 --- /dev/null +++ b/src/Utils/UnpackedArgs.php @@ -0,0 +1,13 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Utils; + + +class UnpackedArgs extends \ArrayObject { } \ No newline at end of file diff --git a/src/Utils/index.php b/src/Utils/index.php index bddbac6..9bad971 100644 --- a/src/Utils/index.php +++ b/src/Utils/index.php @@ -10,6 +10,7 @@ namespace Kadet\Functional; use Kadet\Functional\Utils\CurriedFunction; +use Kadet\Functional\Utils\FunctionPipeline; use Kadet\Functional\Utils\PartiallyAppliedFunction; function partial(callable $callable, ...$args): PartiallyAppliedFunction @@ -25,4 +26,9 @@ function curry(callable $callable, int $threshold = null, ...$args): CurriedFunc function uncurry(CurriedFunction $curried) { return $curried->uncurry(); -} \ No newline at end of file +} + +function pipe(...$functions) +{ + return new FunctionPipeline(...$functions); +} diff --git a/src/functions.php b/src/functions.php index 267b28d..8ac6139 100644 --- a/src/functions.php +++ b/src/functions.php @@ -78,4 +78,4 @@ function reflect(callable $callable): \ReflectionFunctionAbstract } } -define(__NAMESPACE__.'\\_', symbol()); \ No newline at end of file +define(__NAMESPACE__.'\\_', symbol('placeholder')); \ No newline at end of file diff --git a/tests/FunctionPipelineTest.php b/tests/FunctionPipelineTest.php new file mode 100644 index 0000000..cd63545 --- /dev/null +++ b/tests/FunctionPipelineTest.php @@ -0,0 +1,95 @@ + + * + * Full license available in separate LICENSE file + */ + +use \Kadet\Functional as f; + +class FunctionPipelineTest extends \PHPUnit\Framework\TestCase +{ + /** @var \PHPUnit\Framework\MockObject\MockObject */ + private $mock; + + protected function setUp()/* The :void return type declaration that should be here would cause a BC issue */ + { + $this->mock = $this + ->getMockBuilder('stdClass') + ->setMethods(['a', 'b', 'c']) + ->getMock(); + } + + public function testPipelineCalling() + { + $pipeline = f\pipe( + [$this->mock, 'a'], + [$this->mock, 'b'] + ); + + $this->mock->method('a')->willReturn('b'); + $this->mock->method('b')->willReturn('c'); + + $this->mock->expects($this->once())->method('a')->with('a'); + $this->mock->expects($this->once())->method('b')->with('b'); + + $this->assertSame('c', $pipeline('a')); + } + + public function testPipelineForward() + { + $pipeline = f\pipe( + [$this->mock, 'a'] + ); + + $this->mock->method('a')->willReturn('b'); + + $this->mock->expects($this->once())->method('a')->with('a'); + + $this->assertSame('b', $pipeline('a')); + } + + public function testPipelineAppend() + { + $pipeline = f\pipe( + [$this->mock, 'a'], + [$this->mock, 'b'] + ); + + $second = $pipeline->append([$this->mock, 'c']); + + $this->mock->method('a')->willReturn('b'); + $this->mock->method('b')->willReturn('c'); + $this->mock->method('c')->willReturn('d'); + + $this->mock->expects($this->exactly(2))->method('a')->with('a'); + $this->mock->expects($this->exactly(2))->method('b')->with('b'); + $this->mock->expects($this->once())->method('c')->with('c'); + + $this->assertSame('c', $pipeline('a')); + $this->assertSame('d', $second('a')); + } + + public function testPipelinePrepend() + { + $pipeline = f\pipe( + [$this->mock, 'b'], + [$this->mock, 'c'] + ); + + $second = $pipeline->prepend([$this->mock, 'a']); + + $this->mock->method('a')->willReturn('b'); + $this->mock->method('b')->willReturn('c'); + $this->mock->method('c')->willReturn('d'); + + $this->mock->expects($this->exactly(1))->method('a')->with('a'); + $this->mock->expects($this->exactly(2))->method('b')->with('b'); + $this->mock->expects($this->exactly(2))->method('c')->with('c'); + + $this->assertSame('d', $pipeline('b')); + $this->assertSame('d', $second('a')); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 15a3996..cf15534 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -8,4 +8,4 @@ */ require_once __DIR__.'/../vendor/autoload.php'; -require_once __DIR__.'/RandomDataTrait.php'; \ No newline at end of file +require_once __DIR__.'/RandomDataTrait.php';