From 6698f30687c1b03b35a7d960d4258eb637919c53 Mon Sep 17 00:00:00 2001 From: "Jesus E. Franco Martinez" Date: Thu, 21 Nov 2019 10:40:46 -0600 Subject: [PATCH 1/2] Add create_assoc function --- composer.json | 4 +- src/Functional/CreateAssoc.php | 62 +++++++++++++ src/Functional/Functional.php | 10 +++ tests/Functional/CreateAssocTest.php | 128 +++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/Functional/CreateAssoc.php create mode 100644 tests/Functional/CreateAssocTest.php diff --git a/composer.json b/composer.json index d0a8aa52..627c5e33 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "require-dev": { "phpunit/phpunit": "~6", "squizlabs/php_codesniffer": "~3.0", - "friendsofphp/php-cs-fixer": "^2.14" + "friendsofphp/php-cs-fixer": "^2.14", + "apantle/hashmapper": "^1.3" }, "autoload": { "psr-4": {"Functional\\": "src/Functional"}, @@ -35,6 +36,7 @@ "src/Functional/Concat.php", "src/Functional/Contains.php", "src/Functional/Converge.php", + "src/Functional/CreateAssoc.php", "src/Functional/Curry.php", "src/Functional/CurryN.php", "src/Functional/Difference.php", diff --git a/src/Functional/CreateAssoc.php b/src/Functional/CreateAssoc.php new file mode 100644 index 00000000..4e486f48 --- /dev/null +++ b/src/Functional/CreateAssoc.php @@ -0,0 +1,62 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional; + +/** + * Return a function that applies a set of functions to build an + * associative array from a single input (scalar, object or sequence) + * + * @param array $specs associative array of keys and functions that map to the target + * @param object|null $tameme + * @return callable + */ +function create_assoc(array $specs, $tameme = null): callable +{ + return function ($input, $optional = null) use ($specs, $tameme) { + $mecapal = []; + + foreach ($specs as $target => $mapper) { + $mecapal[$target] = is_unary($mapper) + ? \call_user_func($mapper, $input) + : \call_user_func($mapper, $input, $optional, $tameme) + ; + } + + return $mecapal; + }; +} + +/** + * determine if the callable passed expect only one argument + * @param callable $callable + * @return bool + * @throws \ReflectionException + */ +function is_unary($callable) +{ + if (!\is_callable($callable)) { + return false; + } + $reflector = new \ReflectionFunction($callable); + return \boolval($reflector->getNumberOfParameters() === 1); +} diff --git a/src/Functional/Functional.php b/src/Functional/Functional.php index 17ae6211..8fdd31f0 100644 --- a/src/Functional/Functional.php +++ b/src/Functional/Functional.php @@ -54,6 +54,11 @@ final class Functional */ const converge = '\Functional\converge'; + /** + * @see \Functional\create_assoc + */ + const create_assoc = '\Functional\create_assoc'; + /** * @see \Functional\curry */ @@ -169,6 +174,11 @@ final class Functional */ const identical = '\Functional\identical'; + /** + * @see \Functional\is_unary + */ + const is_unary = '\Functional\is_unary'; + /** * @see \Functional\if_else */ diff --git a/tests/Functional/CreateAssocTest.php b/tests/Functional/CreateAssocTest.php new file mode 100644 index 00000000..473270fe --- /dev/null +++ b/tests/Functional/CreateAssocTest.php @@ -0,0 +1,128 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional\Tests; + +use Functional\Functional; + +use function Functional\create_assoc; +use function Apantle\HashMapper\hashMapper; + +class CreateAssocTest extends AbstractTestCase +{ + public function testBasicCreateAssoc() + { + $input = new \DateTimeImmutable(); + + $expected = [ + 'targetA' => $input, + 'targetB' => $input + ]; + + $actual = create_assoc([ + 'targetA' => Functional::id, + 'targetB' => Functional::id, + ])($input); + + $this->assertEquals($expected, $actual); + $this->assertEquals($input->format('U'), $actual['targetA']->format('U')); + $this->assertEquals($input->format('U'), $actual['targetB']->format('U')); + } + + public function testExpectsTameme() + { + $input = [ 1, 2, 3 ]; + + $expected = [ + 'targetA' => [ 4 => 1, 5 => 2, 6 => 3 ], + 'targetB' => [ 5 => 2 ] + ]; + + $tameme = new class extends \ArrayObject {}; + + $actual = create_assoc([ + 'targetA' => function ($member, $input = null, $tameme) { + $build = \array_reduce($member, function ($accum, $num) { + $accum[$num + 3] = $num; + return $accum; + }, []); + + $tameme['prev'] = $build; + return $build; + }, + 'targetB' => function ($member, $input, $tameme) { + $prev = $tameme['prev']; + $build = []; + foreach($prev as $key => $val) { + if($key % 2 !== 0) { + $build[$key] = $val; + } + } + return $build; + } + ], $tameme)($input); + + $this->assertEquals($expected, $actual); + } + + public function testReceivesOptionalArgument() + { + $input = [ + 'vendor' => 'tzkmx', + 'utility' => 'unfold' + ]; + + $expected = [ + 'vendorName' => 'tzkmx', + 'vendorLen' => 5, + 'serialized' => 'a:2:{s:6:"vendor";s:5:"tzkmx";s:7:"utility";s:6:"unfold";}', + 'utility' => 'unfold', + 'utilLen' => 6, + 'package' => [ 'tzkmx/unfold' => $input ] + ]; + + $tameme = new class extends \ArrayObject {}; + + $actual = hashMapper([ + 'vendor' => [ '...', create_assoc([ + 'vendorName' => 'strval', + 'vendorLen' => 'strlen', + 'serialized' => function ($member, $hash, $tameme) { + $tameme['name'] = $member; + return \serialize($hash); + } + ], $tameme) + ], + 'utility' => [ '...', create_assoc([ + 'utility' => 'strval', + 'utilLen' => 'strlen', + 'package' => function ($member, $hash, $tameme) { + $name = $tameme['name']; + return [ "$name/$member" => $hash ]; + } + ], $tameme) + ] + ])($input); + + $this->assertEquals($expected, $actual); + } +} From 4d2b7fb4c5c6a128fd13c57116b1eb8d9670d024 Mon Sep 17 00:00:00 2001 From: "Jesus E. Franco Martinez" Date: Mon, 25 Nov 2019 16:20:55 -0600 Subject: [PATCH 2/2] Fix coding style in CreateAssocTest --- tests/Functional/CreateAssocTest.php | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/Functional/CreateAssocTest.php b/tests/Functional/CreateAssocTest.php index 473270fe..73fa36d2 100644 --- a/tests/Functional/CreateAssocTest.php +++ b/tests/Functional/CreateAssocTest.php @@ -56,11 +56,12 @@ public function testExpectsTameme() 'targetA' => [ 4 => 1, 5 => 2, 6 => 3 ], 'targetB' => [ 5 => 2 ] ]; - + // phpcs:disable $tameme = new class extends \ArrayObject {}; - + // phpcs:enable + $_irrelevant = null; $actual = create_assoc([ - 'targetA' => function ($member, $input = null, $tameme) { + 'targetA' => function ($member, $_irrelevant, $tameme) { $build = \array_reduce($member, function ($accum, $num) { $accum[$num + 3] = $num; return $accum; @@ -70,14 +71,14 @@ public function testExpectsTameme() return $build; }, 'targetB' => function ($member, $input, $tameme) { - $prev = $tameme['prev']; - $build = []; - foreach($prev as $key => $val) { - if($key % 2 !== 0) { - $build[$key] = $val; - } - } - return $build; + $prev = $tameme['prev']; + $build = []; + foreach ($prev as $key => $val) { + if ($key % 2 !== 0) { + $build[$key] = $val; + } + } + return $build; } ], $tameme)($input); @@ -99,9 +100,9 @@ public function testReceivesOptionalArgument() 'utilLen' => 6, 'package' => [ 'tzkmx/unfold' => $input ] ]; - + // phpcs:disable $tameme = new class extends \ArrayObject {}; - + // phpcs:enable $actual = hashMapper([ 'vendor' => [ '...', create_assoc([ 'vendorName' => 'strval',