add pipeline

This commit is contained in:
Kacper Donat 2018-07-16 21:47:06 +02:00
parent 17c96b2834
commit 5c640b578c
6 changed files with 165 additions and 3 deletions

View File

@ -0,0 +1,48 @@
<?php
/**
* Copyright 2018 Kacper Donat
*
* @author Kacper "Kadet" Donat <kacper@kadet.net>
*
* 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));
}
}

View File

@ -0,0 +1,13 @@
<?php
/**
* Copyright 2018 Kacper Donat
*
* @author Kacper "Kadet" Donat <kacper@kadet.net>
*
* Full license available in separate LICENSE file
*/
namespace Kadet\Functional\Utils;
class UnpackedArgs extends \ArrayObject { }

View File

@ -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();
}
}
function pipe(...$functions)
{
return new FunctionPipeline(...$functions);
}

View File

@ -78,4 +78,4 @@ function reflect(callable $callable): \ReflectionFunctionAbstract
}
}
define(__NAMESPACE__.'\\_', symbol());
define(__NAMESPACE__.'\\_', symbol('placeholder'));

View File

@ -0,0 +1,95 @@
<?php
/**
* Copyright 2018 Kacper Donat
*
* @author Kacper "Kadet" Donat <kacper@kadet.net>
*
* 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'));
}
}

View File

@ -8,4 +8,4 @@
*/
require_once __DIR__.'/../vendor/autoload.php';
require_once __DIR__.'/RandomDataTrait.php';
require_once __DIR__.'/RandomDataTrait.php';