├── .gitignore ├── CHANGELOG.md ├── phpunit.xml.dist ├── composer.json ├── src └── compose.php ├── LICENSE ├── tests └── ComposeTest.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | ### 1.0.1 (2013-04-20) 5 | 6 | * Validate arguments as callable (@bsdlite) 7 | 8 | ### 1.0.0 (2013-04-18) 9 | 10 | * Initial release 11 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | ./tests/ 10 | 11 | 12 | 13 | 14 | 15 | ./src/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "igorw/compose", 3 | "description": "Function composition.", 4 | "keywords": ["functional", "composition"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Igor Wiedler", 9 | "email": "igor@wiedler.ch" 10 | } 11 | ], 12 | "autoload": { 13 | "files": ["src/compose.php"] 14 | }, 15 | "extra": { 16 | "branch-alias": { "dev-master": "1.0-dev" } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/compose.php: -------------------------------------------------------------------------------- 1 | $fn) { 12 | if (!is_callable($fn)) { 13 | throw new \InvalidArgumentException( 14 | sprintf('Argument %d to compose() is not callable.', $i)); 15 | } 16 | } 17 | 18 | $fns = func_get_args(); 19 | $prev = array_shift($fns); 20 | 21 | foreach ($fns as $fn) { 22 | $prev = function ($x) use ($fn, $prev) { 23 | $args = func_get_args(); 24 | return $prev(compose\apply($fn, $args)); 25 | }; 26 | } 27 | 28 | return $prev; 29 | } 30 | 31 | /** @api */ 32 | function pipeline() { 33 | return compose\apply('igorw\compose', array_reverse(func_get_args())); 34 | } 35 | 36 | namespace igorw\compose; 37 | 38 | function apply($fn, $args) { 39 | return call_user_func_array($fn, $args); 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Igor Wiedler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /tests/ComposeTest.php: -------------------------------------------------------------------------------- 1 | assertNull($composed(null)); 17 | $this->assertTrue($composed(true)); 18 | $this->assertFalse($composed(false)); 19 | $this->assertSame('foo', $composed('foo')); 20 | } 21 | 22 | function testComposeWithMultipleFuncs() { 23 | $composed = compose( 24 | function ($x) { return "baz($x)"; }, 25 | function ($x) { return "bar($x)"; }, 26 | function ($x) { return "foo($x)"; } 27 | ); 28 | $this->assertSame('baz(bar(foo(x)))', $composed('x')); 29 | } 30 | 31 | function testComposeWithMultipleArgs() { 32 | $composed = compose( 33 | function ($x) { return "bar($x)"; }, 34 | function ($a, $b, $c) { return "foo($a, $b, $c)"; } 35 | ); 36 | $this->assertSame('bar(foo(a, b, c))', $composed('a', 'b', 'c')); 37 | } 38 | 39 | /** 40 | * @expectedException InvalidArgumentException 41 | */ 42 | function testComposeWithInvalidArg() { 43 | compose('foo'); 44 | } 45 | 46 | /** 47 | * @expectedException InvalidArgumentException 48 | */ 49 | function testComposeWithJustOneInvalidArg() { 50 | $fn = function () {}; 51 | compose($fn, 'foo', $fn); 52 | } 53 | 54 | function testPipelineWithMultipleFuncs() { 55 | $composed = pipeline( 56 | function ($x) { return "foo($x)"; }, 57 | function ($x) { return "bar($x)"; }, 58 | function ($x) { return "baz($x)"; } 59 | ); 60 | $this->assertSame('baz(bar(foo(x)))', $composed('x')); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # igorw/compose 2 | 3 | Function composition. 4 | 5 | Allows you to stitch functions together to form a pipeline. This can be useful 6 | if you have to transform data in many steps and you want to describe those 7 | steps on a high level. 8 | 9 | ## compose 10 | 11 | Generally, function composition means taking two functions `f` and `g`, and 12 | producing a new function `z`, which applies `f` to the result of `g`. 13 | 14 | z = compose(f, g) 15 | ; z(x) => f(g(x)) 16 | 17 | This library provides a `compose` function that does just this. 18 | 19 | $z = igorw\compose($f, $g); 20 | var_dump($z($x)); 21 | 22 | It supports an arbitrary number of functions to be composed via varargs. 23 | 24 | $z = igorw\compose($f, $g, $h, $i); 25 | 26 | The innermost function (the last one in the list) can take an arbitrary number 27 | of arguments, whereas the others may only take a single argument. 28 | 29 | $z = igorw\compose($f, $g); 30 | $z('a', 'b', 'c'); 31 | // => $f($g('a', 'b', 'c')) 32 | 33 | ## pipeline 34 | 35 | `pipeline` is the same as `compose`, but the arguments are reversed. This is 36 | more easy to read in some cases, because you can list the functions in the 37 | order they will be called. 38 | 39 | It is quite similar to a unix pipe in that regard. 40 | 41 | ## Examples 42 | 43 | function transform_data($data) { 44 | return [ 45 | 'name' => $data['firstname'].' '.$data['lastname'], 46 | ]; 47 | } 48 | 49 | $transformJson = igorw\pipeline( 50 | function ($json) { return json_decode($json, true); }, 51 | 'transform_data', 52 | 'json_encode' 53 | ); 54 | 55 | $json = << 65 | // {"name": "Igor Wiedler"} 66 | // {"name": "Beau Simensen"} 67 | --------------------------------------------------------------------------------