diff --git a/src/Decorator.php b/src/Decorator.php new file mode 100644 index 0000000..89323ba --- /dev/null +++ b/src/Decorator.php @@ -0,0 +1,16 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional; + + +interface Decorator +{ + public function getDecorated(); +} \ No newline at end of file diff --git a/src/Predicate/ClosurePredicate.php b/src/Predicate/ClosurePredicate.php index 985aa6a..1522773 100644 --- a/src/Predicate/ClosurePredicate.php +++ b/src/Predicate/ClosurePredicate.php @@ -10,7 +10,9 @@ namespace Kadet\Functional\Predicate; -class ClosurePredicate extends AbstractPredicate +use Kadet\Functional\Decorator; + +class ClosurePredicate extends AbstractPredicate implements Decorator { /** * @var \Closure @@ -31,4 +33,9 @@ class ClosurePredicate extends AbstractPredicate { return ($this->closure)(...$args); } + + public function getDecorated() + { + return $this->closure; + } } \ No newline at end of file diff --git a/src/Predicates/comparisons.php b/src/Predicates/comparisons.php index 6322f22..2de7d9d 100644 --- a/src/Predicates/comparisons.php +++ b/src/Predicates/comparisons.php @@ -16,7 +16,6 @@ use function Kadet\Functional\predicate; * @param $expected * * @return \Kadet\Functional\Predicate - * @throws \Exception */ function equals($expected): Predicate { @@ -29,7 +28,6 @@ function equals($expected): Predicate * @param $expected * * @return \Kadet\Functional\Predicate - * @throws \Exception */ function same($expected): Predicate { @@ -42,7 +40,6 @@ function same($expected): Predicate * @param $expected * * @return \Kadet\Functional\Predicate - * @throws \Exception */ function gt($expected): Predicate { @@ -55,7 +52,6 @@ function gt($expected): Predicate * @param $expected * * @return \Kadet\Functional\Predicate - * @throws \Exception */ function ge($expected): Predicate { @@ -68,7 +64,6 @@ function ge($expected): Predicate * @param $expected * * @return \Kadet\Functional\Predicate - * @throws \Exception */ function lt($expected): Predicate { @@ -81,7 +76,6 @@ function lt($expected): Predicate * @param $expected * * @return \Kadet\Functional\Predicate - * @throws \Exception */ function le($expected): Predicate { diff --git a/src/Reflection/ReflectionAppliedParameter.php b/src/Reflection/ReflectionAppliedParameter.php new file mode 100644 index 0000000..d58603c --- /dev/null +++ b/src/Reflection/ReflectionAppliedParameter.php @@ -0,0 +1,27 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Reflection; + + +class ReflectionAppliedParameter extends \ReflectionParameter +{ + private $value; + + public function __construct($function, string $parameter, $value) + { + parent::__construct($function, $parameter); + $this->value = $value; + } + + public function getValue() + { + return $this->value; + } +} \ No newline at end of file diff --git a/src/Reflection/ReflectionPartiallyAppliedFunction.php b/src/Reflection/ReflectionPartiallyAppliedFunction.php new file mode 100644 index 0000000..30321e7 --- /dev/null +++ b/src/Reflection/ReflectionPartiallyAppliedFunction.php @@ -0,0 +1,58 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Reflection; + + +use Kadet\Functional\Utils\PartiallyAppliedFunction; + +class ReflectionPartiallyAppliedFunction extends \ReflectionFunction +{ + public $applied; + + public function __construct(PartiallyAppliedFunction $applied) + { + parent::__construct($applied->getDecorated()); + $this->applied = $applied; + } + + public function getNumberOfParameters() + { + return max(parent::getNumberOfParameters() - count($this->applied->getArguments()), 0); + } + + public function getNumberOfRequiredParameters() + { + return max(parent::getNumberOfRequiredParameters() - count($this->applied->getArguments()), 0); + } + + public function getNumberOfAppliedParameters() + { + return count($this->applied->getArguments()); + } + + public function getAppliedParameters() + { + $applied = $this->applied->getArguments(); + $parameters = array_intersect_key(parent::getParameters(), $applied); + + return array_map(function (\ReflectionParameter $parameter) use ($applied) { + return new ReflectionAppliedParameter( + $this->applied->getDecorated(), + $parameter->name, + $applied[$parameter->getPosition()] + ); + }, $parameters); + } + + public function getParameters() + { + return array_diff_key(parent::getParameters(), $this->applied->getArguments()); + } +} \ No newline at end of file diff --git a/src/Utils/PartiallyAppliedFunction.php b/src/Utils/PartiallyAppliedFunction.php index 2376400..0a9c70c 100644 --- a/src/Utils/PartiallyAppliedFunction.php +++ b/src/Utils/PartiallyAppliedFunction.php @@ -10,8 +10,11 @@ namespace Kadet\Functional\Utils; use const Kadet\Functional\_ as PLACEHOLDER; +use Kadet\Functional\Decorator; +use function Kadet\Functional\not; +use function Kadet\Functional\Predicates\same; -class PartiallyAppliedFunction +class PartiallyAppliedFunction implements Decorator { protected $callable; protected $arguments = []; @@ -30,6 +33,11 @@ class PartiallyAppliedFunction } } + public function getArguments() + { + return array_filter($this->arguments, not(same(PLACEHOLDER))); + } + public function __invoke(...$args) { $arguments = $this->prepareArguments($args); @@ -50,4 +58,9 @@ class PartiallyAppliedFunction array_values($args) ); } + + public function getDecorated() + { + return $this->callable; + } } \ No newline at end of file diff --git a/tests/PartialApplicationTest.php b/tests/PartialApplicationTest.php index dacf4a4..f356478 100644 --- a/tests/PartialApplicationTest.php +++ b/tests/PartialApplicationTest.php @@ -48,10 +48,27 @@ class PartialApplicationTest extends \PHPUnit\Framework\TestCase public function testPartialApplicationOfAppliedFunction() { - $applied = f\partial('array_merge', ['a'], _, ['c']); $double = f\partial($applied, _, ['d']); $this->assertEquals(['a', 'b', 'c', 'd', 'e'], $double(['b'], ['e'])); } + + public function testDecoration() + { + $x = function() {}; + $applied = f\partial($x); + + $this->assertSame($applied->getDecorated(), $x); + } + + public function testArgumentProviding() + { + $applied = f\partial('array_merge', ['a'], _, ['c']); + + $this->assertSame( + [0 => ['a'], 2 => ['c']], + $applied->getArguments() + ); + } } \ No newline at end of file diff --git a/tests/ReflectionPartialAppliedFunctionTest.php b/tests/ReflectionPartialAppliedFunctionTest.php new file mode 100644 index 0000000..ea6027f --- /dev/null +++ b/tests/ReflectionPartialAppliedFunctionTest.php @@ -0,0 +1,70 @@ + + * + * Full license available in separate LICENSE file + */ + +use Kadet\Functional as f; +use const Kadet\Functional\_; +use Kadet\Functional\Reflection\ReflectionAppliedParameter; +use Kadet\Functional\Reflection\ReflectionPartiallyAppliedFunction; + + +class ReflectionPartialAppliedFunctionTest extends \PHPUnit\Framework\TestCase +{ + private $function; + + protected function setUp() + { + $this->function = f\partial(function (array $a, array $b, array $c, array $d = ['d']) { + return array_merge($a, $b, $c, $d); + }, ['a'], _, ['c']); + } + + public function testNumberOfParameters() + { + $reflection = new ReflectionPartiallyAppliedFunction($this->function); + + $this->assertSame(2, $reflection->getNumberOfParameters()); + } + + public function testNumberOfRequiredParameters() + { + $reflection = new ReflectionPartiallyAppliedFunction($this->function); + + $this->assertSame(1, $reflection->getNumberOfRequiredParameters()); + } + + public function testNumberOfAppliedParameters() + { + $reflection = new ReflectionPartiallyAppliedFunction($this->function); + + $this->assertSame(2, $reflection->getNumberOfAppliedParameters()); + } + + public function testParameters() + { + $reflection = new ReflectionPartiallyAppliedFunction($this->function); + $parameters = $reflection->getParameters(); + + $this->assertContainsOnlyInstancesOf(ReflectionParameter::class, $parameters); + $this->assertCount(2, $parameters); + $this->assertEquals([1, 3], array_keys($parameters)); + } + + public function testAppliedParameters() + { + $reflection = new ReflectionPartiallyAppliedFunction($this->function); + $parameters = $reflection->getAppliedParameters(); + + $this->assertContainsOnlyInstancesOf(ReflectionAppliedParameter::class, $parameters); + $this->assertCount(2, $parameters); + $this->assertEquals([0, 2], array_keys($parameters)); + $this->assertEquals([0 => ['a'], 2 => ['c']], array_map(function(ReflectionAppliedParameter $parameter) { + return $parameter->getValue(); + }, $parameters)); + } +} \ No newline at end of file