├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── docs ├── daydream.php └── features.php ├── phpunit.xml ├── src ├── Concept │ ├── EmitTrait.php │ ├── Emittable.php │ └── Value.php ├── Filter │ ├── AppendTrait.php │ ├── ChunkTrait.php │ ├── EachTrait.php │ ├── FilesTrait.php │ ├── FilterTrait.php │ ├── InfiniteTrait.php │ ├── LimitTrait.php │ ├── MapTrait.php │ ├── SkipTrait.php │ ├── SleepTrait.php │ ├── StopIfTrait.php │ └── ValuesTrait.php ├── Iterator │ ├── AppendIterator.php │ ├── ChunkIterator.php │ ├── MapIterator.php │ ├── PlainArrayIterator.php │ ├── SkipIterator.php │ └── ValuesIterator.php ├── Pipe.php ├── PipeIterator.php ├── PipenessTrait.php └── functions.php └── tests ├── AppendTest.php ├── BaseTestCase.php ├── BasicTest.php ├── ChunkTest.php ├── EachTest.php ├── EmittableTest.php ├── FilesTest.php ├── FilterTest.php ├── InfiniteTest.php ├── LimitTest.php ├── MapTest.php ├── OuterIteratorTest.php ├── PipeIterationTest.php ├── SkipTest.php ├── SleepTest.php ├── StopIfTest.php ├── TestCase ├── BaseIteratorTestCase.php ├── CallbackTestCase.php └── TestListener.php ├── Tools ├── TestIterator.php └── TestIteratorAggregate.php ├── ValueTest.php └── ValuesTest.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | phpcodesniffer: 4 | enabled: true 5 | phpmd: 6 | enabled: true 7 | ratings: 8 | paths: 9 | - "**.php" 10 | - "**.module" 11 | - "**.inc" 12 | exclude_paths: ["tests"] 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # MISC # 40 | ######## 41 | 42 | nbproject 43 | vendor 44 | composer.lock 45 | .idea 46 | playground 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 5.6 6 | - 5.5 7 | - hhvm 8 | 9 | install: 10 | - composer require satooshi/php-coveralls:~0.6@stable 11 | 12 | before_script: 13 | - curl -s http://getcomposer.org/installer | php 14 | - php composer.phar install --dev 15 | - mkdir -p build/logs 16 | 17 | script: 18 | - phpunit --coverage-clover build/logs/clover.xml 19 | 20 | after_success: 21 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php vendor/bin/coveralls -v; fi;' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pipes 2 | ============== 3 | [![Build Status](https://travis-ci.org/tacone/pipes.svg)](https://travis-ci.org/tacone/pipes) 4 | [![Coverage Status](https://img.shields.io/coveralls/tacone/pipes.svg)](https://coveralls.io/r/tacone/pipes) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tacone/pipes/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/tacone/pipes/?branch=master) 6 | 7 | Pipes is a thin wrapper around PHP SPL iterators and generators. 8 | 9 | With Pipes, you can write code like this: 10 | 11 | ```php 12 | $result = p($array)->filter(function($v) { 13 | return $v % 2; 14 | }) 15 | ->each('var_dump') 16 | ->limit(100) 17 | ->toArray(); 18 | ``` 19 | 20 | Unlike many collection libraries (such as underscore.php or 21 | Laravel's Illuminate/Collection) each step will be executed 22 | sequentially for each item. For instance, in the sample code 23 | above, only the first 101 even numbers would be printed 24 | (the 101th will reach each() but not be passed through limit()) 25 | 26 | The advantages are: 27 | - you can traverse enourmous arrays using less memory 28 | - you don't execute unnecessary operations when you need just 29 | a subset. 30 | - you can control the flow 31 | - the resulting code is neat and pretty 32 | 33 | Of course this is just the beginning. 34 | 35 | 36 | ## Features 37 | 38 | The current feature set is pretty minimal. Below is a syntetic description 39 | of the current methods. Keep in mind they are all documented and available 40 | for your favorite IDE auto-completion. 41 | 42 | ```php 43 | // filters 44 | p($array)->chunk($size); // spits chunks like array_chunk 45 | p($array)->each($function); // executes $function for each element 46 | p($array)->filter($function); // uses \FilterIteratorCallback 47 | p()->values(); // returns all the elements, reindexing the keys 48 | 49 | // flow control 50 | p($array)->limit($skip = 0, $max); // uses \LimitIterator 51 | p($array)->skip($num); // skips the first $num elements 52 | p($array)->continueIf($callback); // continues as long as callback gives true 53 | p($array)->stopIf($callback); // stops if the callback is true 54 | p($array)->sleep($seconds); // sleeps for $seconds seconds at each iteration 55 | 56 | // transformators 57 | p($array)->map($function); // sort of array_map. Take a look to the tests. 58 | 59 | // iterator factories 60 | p()->files($globPattern); // uses \GlobIterator, accepts the same args 61 | 62 | // terminators 63 | p()->toArray(); // returns a key=>value array. Last key wins. 64 | p()->toValues(); // returns an indexed array. No key collision. 65 | 66 | // variants 67 | p()->toIterator(); // returns a full IteratorIterator/OuterIterator Pipe 68 | p()->toTraversable(); // returns an IteratorAggregate Pipe (much faster) 69 | ``` 70 | 71 | ## Example: 72 | 73 | Here is a quasi real world example: a simple scraper that handles retries, 74 | failures, etc. 75 | 76 | ```php 77 | $website = new Website(); 78 | 79 | $initialState = new stdClass(); 80 | $initialState->categoryId = 1; 81 | $initialState->postId = 1; 82 | $initialState->tries = 1; 83 | $initialState->failures = 0; 84 | 85 | $context = clone $initialState; 86 | 87 | $pipe = p([$context]) 88 | // transform the single item array into an infinite stream of 89 | // items consisting of the same context instance 90 | ->infinite() 91 | // make sure not to loop more than 100 times, we don't need 92 | // too much data 93 | ->limit(100) 94 | // call some method to download the HTML of the page 95 | ->map([$website, 'downloadPage']) 96 | // pass the HTML to some method to turn it into a database record 97 | ->map([$website, 'pageToRecord']) 98 | // perform some custom check or transformation if you wish 99 | ->map(function ($record) use ($website, $context) { 100 | return $record; 101 | }) 102 | // dump every record to the screen or to your custom logger 103 | ->each(function () use ($context) { 104 | var_dump($context); 105 | }) 106 | // here is a sample logic for handling retries, failures and enumeration 107 | // the beauty here is you keep all the state into a single external object 108 | // that you can easily serialize and save somewhere. 109 | ->each(function ($record) use ($context, $initialState) { 110 | if ($record['page_found']) { 111 | $context->postId++; 112 | $context->tries = $initialState->tries; 113 | $context->failures = $initialState->failures; 114 | return; 115 | } 116 | 117 | if ($context->tries < 3) { 118 | $context->tries++; 119 | return; 120 | } 121 | 122 | $context->postId++; 123 | $context->failures++; 124 | $context->tries = $initialState->tries; 125 | 126 | if ($context->failures >= 3) { 127 | $context->categoryId++; 128 | $context->postId = 1; 129 | $context->failures = $initialState->failures; 130 | $context->tries = $initialState->tries; 131 | } 132 | }) 133 | // don't save the errors in the database 134 | ->filter(function ($record) { 135 | return !empty($record['page_found']); 136 | }) 137 | // save the record in the database 138 | ->each(function ($record) { 139 | // .. up to you! 140 | }) 141 | // sleep for 1.5 seconds to avoid bringing down the website 142 | ->sleep(1.5); 143 | 144 | // Everything ok, isn't it? Notice than nothing happened yet. 145 | // $pipe is now an aggregate iterator, which does not do anything 146 | // until you cycle on it or you call toArray(); 147 | 148 | foreach ($pipe as $record) { 149 | // you can leave this empty or insert your custom logic here 150 | } 151 | 152 | // if you don't need logic, you may just run it with 153 | $results = $pipe->toArray(); 154 | 155 | // and if you do it again, it will run again :) 156 | $results = $pipe->toArray(); 157 | ``` 158 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pipes/pipes", 3 | "description": "A thin wrapper over PHP iterators", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "tacone", 8 | "email": "known-only@to.me" 9 | } 10 | ], 11 | "autoload": { 12 | "psr-4": { 13 | "Pipes\\": "src/", 14 | "Pipes\\Test\\": "tests/" 15 | }, 16 | "files": ["src/functions.php"] 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "@stable" 20 | }, 21 | "prefer-stable" : true, 22 | "minimum-stability": "dev" 23 | } 24 | -------------------------------------------------------------------------------- /docs/daydream.php: -------------------------------------------------------------------------------- 1 | getUsers())->limit(1000)->map(function($user) { 9 | // process the user 10 | return p::emit($user['id'], $user); 11 | })->each(function($userData){ 12 | // save the data 13 | }); 14 | 15 | // alternative (PHP 5.5) syntax 16 | p($this)->map(function($value, &$key){ 17 | $key = 4545; 18 | }); 19 | 20 | // alternative (PHP 5.5) syntax 21 | p($this)->map(function($value, $key){ 22 | yield $key => $value; 23 | }); 24 | 25 | p($this)->map('$k=$v*2'); 26 | 27 | 28 | /** 29 | * process max 100 users at once, 1000 times 30 | */ 31 | 32 | p($this->getUsers())->chunk(100)->limit(1000)->each(function($usersChunk) { 33 | foreach ( $usersChunk as $user ) 34 | { 35 | print "$user->id \n"; 36 | } 37 | }); 38 | 39 | /** 40 | * Push and next 41 | */ 42 | 43 | $p()->push($user)->next(); // $user 44 | 45 | /* 46 | * auto-next 47 | */ 48 | 49 | $p($array)->each('myfunc')->autonext(); // loops to the end 50 | $p->push($item); // each will be executed 51 | 52 | $a = new ReflectionFunction(''); 53 | //$a->get 54 | 55 | 56 | 57 | 58 | /** 59 | * distributing the effort 60 | */ 61 | p($this->getUsers())->shard(3, function($v, $k, Pipe $p){ 62 | echo "shard n. {$p->getShard()}: $k => $v \n"; 63 | }); 64 | 65 | // in memory sqlite? 66 | p($files)->where('size > 1000 AND mtime < NOW()-1000 ORDER BY extension, filename'); 67 | -------------------------------------------------------------------------------- /docs/features.php: -------------------------------------------------------------------------------- 1 | next(); 9 | p()->current(); 10 | p()->valid(); 11 | p()->rewind(); 12 | p()->getInnerIterator(); 13 | 14 | // pipes 15 | p()->chunk($num); // ok 16 | p()->each($function, $step = 1); //ok 17 | p()->filter($function); //ok 18 | p()->limit($skip = 0, $max); //ok 19 | p()->map($function); //okdi 20 | 21 | // factory methods 22 | p()->emit($key = null, $value); //ok 23 | p()->flags($flag1, $flag2 =null, $flagN = null); 24 | 25 | // terminals 26 | p()->toValues(); // ok. outputs an indexed array. 27 | p()->toArray(); // ok. outputs an array. Keys preserved (last key wins) 28 | p()->toIterator(); // ok. outputs an array. Keys preserved (last key wins) 29 | p()->reduce($function = null); // outputs an array. Keys preserved. Conflicts handled by $function 30 | 31 | 32 | // timed pipes 33 | p()->maxTime($seconds); //also floats 0.001 etc 34 | p()->wait($seconds, $function = null); // !$function ? wait again 35 | 36 | // push to other queues (array, queues, chains) 37 | p()->filter($function, $queue); 38 | p()->map($function, $queue); 39 | 40 | // accumulators 41 | p()->groupBy($function); 42 | p()->sort(); 43 | 44 | // queues 45 | p()->queues->sqlite('queue.db'); 46 | p()->queues->file('queue.txt'); 47 | p()->queues->json('queue.json'); // one json per line 48 | p()->queues->post($url, $moreParams); 49 | 50 | // anonymous pipes 51 | $func = p()->filter($foo)->map($bar); 52 | $result = $func('hello'); 53 | 54 | $func->wrap($array); 55 | 56 | 57 | p($array)->map('p()->emit($k, $v)'); 58 | 59 | // --- advanced stuff 60 | 61 | // caching 62 | p()->keep(3)->each(function(){ 63 | $previous = p()->kept(-1); // also -2, -3 64 | }); 65 | 66 | // map reduce 67 | p()->shard($function); 68 | p()->reduce($shard); 69 | 70 | 71 | /* 72 | 73 | Figure out how to: 74 | 75 | - properly replace an inneriterator with another in PipeIterator 76 | - properly append new iterms in PipeIterator 77 | - how to make PipeIterator a simple reference to a Pipe Instance 78 | 79 | - Pipe iterator should not wrap the Pipe instance but iterate on its 80 | inner iterator. 81 | - Thus, a new Pipe iterator should be initialized for each chainWith 82 | operation. 83 | - Pipe iterator should keep an instance of its pipe for easy retrieval. 84 | - Each chain operation on Pipe iterator should be passed on to its 85 | Pipe parent, but should be reflected on the iterator (?) 86 | 87 | Should we return a new Pipe instance for each chainWith? 88 | $pipe = p($collection)->filter('isEmpty'); 89 | $map = $pipe->map('myfunc')->each('func')->limit(3); 90 | $discard = $pipe->each('logme'); 91 | $result = $map->toArray(); 92 | $discarded = $discard->toArray(); 93 | 94 | */ -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests/ 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ./src 32 | 33 | ./docs 34 | ./vendor 35 | ./tests 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Concept/EmitTrait.php: -------------------------------------------------------------------------------- 1 | = 2) { 12 | $emit = new Emittable($value); 13 | $emit->setKey($key); 14 | } 15 | 16 | return $emit; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Concept/Emittable.php: -------------------------------------------------------------------------------- 1 | key) { 12 | throw new \LogicException('Emitted value has no key defined'); 13 | } 14 | 15 | return $this->key->getValue(); 16 | } 17 | public function setKey($key) 18 | { 19 | $this->key = new Value($key); 20 | } 21 | public function hasKey() 22 | { 23 | return is_object($this->key); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Concept/Value.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | public function getValue() 14 | { 15 | return $this->value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Filter/AppendTrait.php: -------------------------------------------------------------------------------- 1 | getInnerIterator(); 19 | // } 20 | // 21 | // if (is_a($me, '\\Pipes\\Iterator\\AppendIterator')) { 22 | // $appendIterator = $me; 23 | // } else { 24 | // $appendIterator = new AppendIterator(); 25 | // $appendIterator->append($me->toIterator()); 26 | // } 27 | // 28 | // $appendIterator->append($iterator); 29 | // 30 | // return $this->getRoot()->chainWith($appendIterator); 31 | // } 32 | //} 33 | 34 | -------------------------------------------------------------------------------- /src/Filter/ChunkTrait.php: -------------------------------------------------------------------------------- 1 | chainWith(new \Pipes\Iterator\ChunkIterator($this->getIterator(), $size)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Filter/EachTrait.php: -------------------------------------------------------------------------------- 1 | chainWith( 29 | new \CallbackFilterIterator($this->getIterator(), 30 | function () use ($______callback, $______allArgs 31 | ) { 32 | call_user_func_array($______callback, $______allArgs ? func_get_args() : [func_get_arg(0)]); 33 | 34 | return true; 35 | })); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Filter/FilesTrait.php: -------------------------------------------------------------------------------- 1 | 12 | * $files = p->files('/tmp/*.txt'); 13 | * foreach ($files as $f) { 14 | * echo $f .PHP_EOL; 15 | * } 16 | * 17 | * 18 | * Use true to get a SplFileInfo object for each file: 19 | * 20 | * $files = p->files('/tmp/*.txt'); 21 | * 22 | * 23 | * You can also pass any GlobIterator or FilesystemIterator flag 24 | * 25 | * 26 | * $files = p->files('/tmp/*.txt', \FilesystemIterator::FOLLOW_SYMLINKS); 27 | * 28 | * 29 | * You are incoraged to download and use Symfony Finder for any advanced need: 30 | * 31 | * 32 | * $finder = new Finder(); 33 | * $iterator = $finder 34 | * ->files() 35 | * ->name('*.php') 36 | * ->depth(0) 37 | * ->size('>= 1K') 38 | * ->in(__DIR__); 39 | * 40 | * // then you can use your new iterator with pipes :) 41 | * p($iterator)->each(function () { 42 | * //... do stuff 43 | * }); 44 | * 45 | * 46 | * 47 | * @param string $path any file wildcard (ie:/tmp/*.txt). Use the full path! 48 | * @param int $flags any GlobIterator or FilesystemIterator flag 49 | * 50 | * @return \Pipes\Pipe 51 | */ 52 | public function files($path, $flags = \GlobIterator::CURRENT_AS_PATHNAME) 53 | { 54 | if ($flags === true) { 55 | $flags = 0; 56 | } 57 | 58 | return new static (new \GlobIterator($path, $flags)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Filter/FilterTrait.php: -------------------------------------------------------------------------------- 1 | chainWith(new \CallbackFilterIterator($this->toIterator(), $callback)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Filter/InfiniteTrait.php: -------------------------------------------------------------------------------- 1 | chainWith(new \InfiniteIterator($this->toIterator())); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Filter/LimitTrait.php: -------------------------------------------------------------------------------- 1 | p()->limit(5) will return only the first five elements. 13 | * p()->limit(2, 5) will skip 2 element, and return the next 5. 14 | * 15 | * @param $boundary1 16 | * @param bool $boundary2 17 | * 18 | * @return \Pipes\Pipe 19 | */ 20 | public function limit($boundary1, $boundary2 = false) 21 | { 22 | if (func_num_args() == 1 || $boundary2 === false) { 23 | $offset = 0; 24 | $count = $boundary1; 25 | } else { 26 | $offset = $boundary1; 27 | $count = $boundary2; 28 | } 29 | if (!$count) { 30 | return $this->chainWith(new \ArrayIterator([])); 31 | } 32 | 33 | return $this->chainWith(new \LimitIterator($this->toIterator(), $offset, $count)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Filter/MapTrait.php: -------------------------------------------------------------------------------- 1 | p()->emit($key,$value) 16 | * 17 | * p()->map(function ($value, $key, $iterator) { 18 | * return p()->emit($key.'_new', $value); 19 | * }); 20 | * 21 | * 22 | * @param callable $callback 23 | * 24 | * @return \Pipes\Pipe 25 | */ 26 | public function map(callable $______callback) 27 | { 28 | $iterator = $this->getIterator(); 29 | $pipe = $this; 30 | 31 | if (!is_array($______callback)) { 32 | $reflectionFunction = new \ReflectionFunction($______callback); 33 | } else { 34 | $reflectionFunction = new \ReflectionMethod($______callback[0], $______callback[1]); 35 | } 36 | 37 | if ($reflectionFunction->isGenerator()) { 38 | $generator = $______callback; 39 | } else { 40 | $generator = function ($iterator) use ($pipe, $______callback) { 41 | foreach ($iterator as $key => $value) { 42 | // yield $key => $pipe->executeCallback($______callback, true, $value, $key, $iterator); 43 | $value = $pipe->executeCallback($______callback, true, $value, $key, $iterator); 44 | if ($value instanceof Emittable) { 45 | if ($value->hasKey()) { 46 | $key = $value->getKey(); 47 | } 48 | $value = $value->getValue(); 49 | } 50 | yield $key => $value; 51 | } 52 | }; 53 | } 54 | 55 | return $this->chainWith($generator($iterator)); 56 | // return $this->chainWith(new \Pipes\Iterator\MapIterator($this->toIterator(), $callback)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Filter/SkipTrait.php: -------------------------------------------------------------------------------- 1 | chainWith(new \Pipes\Iterator\SkipIterator($this->getIterator(), $num)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Filter/SleepTrait.php: -------------------------------------------------------------------------------- 1 | chainWith( 17 | new \CallbackFilterIterator($this->getIterator(), 18 | function () use ($seconds) { 19 | usleep($seconds * 1000000); 20 | 21 | return true; 22 | })); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Filter/StopIfTrait.php: -------------------------------------------------------------------------------- 1 | getIterator(); 29 | $pipe = $this; 30 | 31 | $generator = function () use ($pipe, $iterator, $______callback, $______allArgs) { 32 | foreach ($iterator as $key => $value) { 33 | if ($pipe->executeCallback($______callback, $______allArgs, $value, $key, $iterator) 34 | ) { 35 | return; 36 | } // @codeCoverageIgnore 37 | yield $key => $value; 38 | } 39 | }; 40 | 41 | return $this->chainWith($generator()); 42 | } 43 | 44 | /** 45 | * Stops the iteration if the callback returns a true-ish value. 46 | * The current element will not be included. 47 | * 48 | * The passed callback will be invoked with the following argument: 49 | * 50 | * - $value (iterator's current()) 51 | * 52 | * If the second parameter is true the following arguments will be added 53 | * to the call: 54 | * 55 | * - $key (iterator's key()) 56 | * - $iterator (the iterator itself) 57 | * 58 | * @param $______callback 59 | * @param bool $______allArgs 60 | * 61 | * @return \Pipes\Pipe 62 | */ 63 | public function continueIf($______callback, $______allArgs = false) 64 | { 65 | $iterator = $this->getIterator(); 66 | $pipe = $this; 67 | 68 | $generator = function () use ($pipe, $iterator, $______callback, $______allArgs) { 69 | foreach ($iterator as $key => $value) { 70 | if (!$pipe->executeCallback($______callback, $______allArgs, $value, $key, $iterator) 71 | ) { 72 | return; 73 | } // @codeCoverageIgnore 74 | yield $key => $value; 75 | } 76 | }; 77 | 78 | return $this->chainWith($generator()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Filter/ValuesTrait.php: -------------------------------------------------------------------------------- 1 | chainWith(new \Pipes\Iterator\ValuesIterator($this->getIterator())); 17 | } 18 | 19 | /* 20 | * Discards the keys, returns an indexed array. 21 | * 22 | * Shortcut for p($iterator)->values->toArray(); 23 | */ 24 | public function toValues() 25 | { 26 | return $this->values()->toArray(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Iterator/AppendIterator.php: -------------------------------------------------------------------------------- 1 | getArrayIterator()->append($iterator); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Iterator/ChunkIterator.php: -------------------------------------------------------------------------------- 1 | size = $size; 34 | } 35 | 36 | public function rewind() 37 | { 38 | parent::rewind(); 39 | $this->next(); 40 | $this->key = 0; 41 | } 42 | 43 | public function next() 44 | { 45 | $this->chunk = array(); 46 | for ($i = 0; $i < $this->size && parent::valid(); ++$i) { 47 | $this->chunk[] = parent::current(); 48 | parent::next(); 49 | } 50 | $this->chunk ? $this->key++ : null; 51 | } 52 | 53 | public function key() 54 | { 55 | return $this->key; 56 | } 57 | 58 | public function current() 59 | { 60 | return $this->chunk; 61 | } 62 | 63 | public function valid() 64 | { 65 | return (bool) $this->chunk; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Iterator/MapIterator.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 15 | // } 16 | // 17 | // protected function map($value, $key) 18 | // { 19 | // $this->result = call_user_func($this->callback, $value, $key, $this); 20 | // } 21 | // 22 | // public function key() 23 | // { 24 | // if (!is_a($this->result, '\\Pipes\\Concept\\Emittable') || !$this->result->hasKey()) { 25 | // return $this->getInnerIterator()->key(); 26 | // } 27 | // 28 | // return $this->result->getKey(); 29 | // } 30 | // 31 | // public function current() 32 | // { 33 | // if (!is_a($this->result, '\\Pipes\\Concept\\Emittable')) { 34 | // return $this->result; 35 | // } 36 | // 37 | // return $this->result->getValue(); 38 | // } 39 | // 40 | // public function accept() 41 | // { 42 | // return true; 43 | // } 44 | // 45 | // public function valid() 46 | // { 47 | // if (!$valid = $this->getInnerIterator()->valid()) { 48 | // return false; 49 | // } 50 | // $this->map( 51 | // $this->getInnerIterator()->current(), 52 | // $this->getInnerIterator()->key() 53 | // ); 54 | // 55 | // return true; 56 | // } 57 | //} 58 | 59 | -------------------------------------------------------------------------------- /src/Iterator/PlainArrayIterator.php: -------------------------------------------------------------------------------- 1 | num = $num; 19 | } 20 | 21 | /** 22 | * Check whether the current element of the iterator is acceptable. 23 | * 24 | * @link http://php.net/manual/en/filteriterator.accept.php 25 | * 26 | * @return bool true if the current element is acceptable, otherwise false. 27 | */ 28 | public function accept() 29 | { 30 | if ($this->skipped >= $this->num) { 31 | return true; 32 | } 33 | 34 | ++$this->skipped; 35 | 36 | return false; 37 | } 38 | 39 | public function rewind() 40 | { 41 | $this->skipped = 0; 42 | parent::rewind(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Iterator/ValuesIterator.php: -------------------------------------------------------------------------------- 1 | key = 0; 17 | } 18 | 19 | public function key() 20 | { 21 | return $this->key; 22 | } 23 | 24 | public function next() 25 | { 26 | parent::next(); 27 | ++$this->key; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Pipe.php: -------------------------------------------------------------------------------- 1 | var = new PlainArrayIterator($var); 18 | } 19 | if ($var instanceof \Traversable) { 20 | $this->var = $var; 21 | } 22 | if (!func_num_args()) { 23 | $this->var = []; 24 | } 25 | } 26 | 27 | /** 28 | * This method is implemented just because it's required by the 29 | * \IteratorAggregate interface. 30 | * 31 | * Returns an instance of the last array/Traversable of the chain 32 | * Don't use this method: it won't return a Pipe instance: 33 | * no more chaining magic. 34 | * It may very well be a plain array. 35 | * 36 | * If you need an iterator, use toIterator() instead. 37 | * 38 | * @return array|\Traversable 39 | */ 40 | public function getIterator() 41 | { 42 | return $this->var; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/PipeIterator.php: -------------------------------------------------------------------------------- 1 | var = $iterator; 31 | 32 | return $this; 33 | } 34 | 35 | public function toArray() 36 | { 37 | $iterator = $this->var; 38 | 39 | return iterator_to_array($iterator, true); 40 | } 41 | 42 | /** 43 | * Returns a complete iterator. 44 | * (an instance of \IteratorIterator which in turn 45 | * implements \OuterIterator). 46 | * 47 | * Use this method if you need to comply with type-hinting 48 | * from external libraries 49 | * 50 | * @return \IteratorIterator 51 | */ 52 | public function toIterator() 53 | { 54 | $iterator = $this->unwrap(); 55 | 56 | if (is_array($iterator)) { 57 | $iterator = new \ArrayIterator($iterator); 58 | } 59 | 60 | $appendIterator = new AppendIterator(); 61 | $appendIterator->append($iterator); 62 | 63 | return new PipeIterator($appendIterator); 64 | } 65 | 66 | // /** 67 | // * @return \Traversable 68 | // */ 69 | // protected function getRoot() 70 | // { 71 | // return $this->getBaseOfChain($this, true); 72 | // } 73 | 74 | /** 75 | * Returns the latest non pipe Iterator/Traversable in the 76 | * chain. 77 | * 78 | * @return \Traversable 79 | */ 80 | public function unwrap() 81 | { 82 | $iterator = func_num_args() ? func_get_arg(0) : $this; 83 | 84 | return $this->getBaseOfChain($iterator); 85 | } 86 | 87 | public function getBaseOfChain($iterator, $pipeInstance = false) 88 | { 89 | $last = $iterator; 90 | while (true) { 91 | switch (true) { 92 | case is_a($iterator, '\\Pipes\\PipeIterator'): 93 | $last = $iterator; 94 | $iterator = $last->getInnerIterator(); 95 | break; 96 | case is_a($iterator, '\\Pipes\\Pipe'): 97 | $last = $iterator; 98 | $iterator = $last->getIterator(); 99 | break; 100 | case is_a($iterator, '\\ArrayIterator'): 101 | $iterator = $iterator->getArrayCopy(); 102 | break; 103 | default: 104 | // --- was used by getRoot() 105 | // if ($pipeInstance) { 106 | // return $last; 107 | // } 108 | 109 | return $iterator; 110 | } 111 | } 112 | } // @codeCoverageIgnore 113 | 114 | protected function executeCallback($______callback, $______allArgs, $value, $key, $iterator) 115 | { 116 | return call_user_func_array( 117 | $______callback, 118 | $______allArgs ? [$value, $key, $iterator] : [$value] 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | associative(); 11 | // $obj = $this->p($array)->append($this->numerics()); 12 | // $expected = $this->associative() + $this->numerics(); 13 | // $this->assertSame($expected, $obj->toArray()); 14 | // } 15 | // 16 | //// public function testAppendedToTheRoot() 17 | //// { 18 | //// $array = $this->associative(); 19 | //// $obj = p($array); 20 | //// 21 | //// $obj->toIterator()->append($this->numerics()); 22 | //// $expected = $this->associative() + $this->numerics(); 23 | //// $this->assertSame($expected, $obj->toArray()); 24 | //// } 25 | //} 26 | 27 | -------------------------------------------------------------------------------- /tests/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | toIterator() : $pipe; 24 | } 25 | 26 | public function run(\PHPUnit_Framework_TestResult $result = null) 27 | { 28 | if ($result === null) { 29 | $result = $this->createResult(); 30 | } 31 | // test every request-format 32 | $first = 0; 33 | foreach ([false, true] as $useToIterator) { 34 | static::$useToIterator = $useToIterator; 35 | if (!$first) { 36 | $this->setUp(); 37 | } 38 | $result->run($this); 39 | if ($first) { 40 | $this->tearDown(); 41 | } 42 | } 43 | 44 | return $result; 45 | } 46 | 47 | protected function numerics() 48 | { 49 | return [1, 2, 3, 5]; 50 | } 51 | 52 | protected function associative() 53 | { 54 | return [ 55 | 'a' => 'apples', 56 | 'b' => 'bananas', 57 | 'c' => 'cherries', 58 | 'd' => 'damsons', 59 | 'e' => 'elderberries', 60 | 'f' => 'figs', 61 | ]; 62 | } 63 | 64 | protected function types() 65 | { 66 | return [ 67 | 'boolean' => true, 68 | 'boolean_false' => false, 69 | 'number' => 12, 70 | 'string' => 'hello world', 71 | 'array' => ['a', 'b', 'c'], 72 | 'float' => 1.7, 73 | 'object' => new \stdclass(), 74 | 'null' => null, 75 | ]; 76 | } 77 | 78 | public function testMe() 79 | { 80 | //todo: strip this method without having phpunit fail all over 81 | } 82 | 83 | public function foreachArray($iterator) 84 | { 85 | $result = []; 86 | foreach ($iterator as $key => $value) { 87 | $result[$key] = $value; 88 | } 89 | 90 | return $result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/BasicTest.php: -------------------------------------------------------------------------------- 1 | numerics(); 12 | $this->assertEquals(p($array)->toArray(), $array); 13 | 14 | list($a, $b, $c, $d) = p($array)->toArray(); 15 | $this->assertEquals($b, 2); 16 | 17 | $result = []; 18 | foreach (p($array)->toArray() as $value) { 19 | $result[] = $value; 20 | } 21 | $this->assertEquals($array, $result); 22 | $this->assertEquals(p($array)->toArray(), $result); 23 | } 24 | 25 | public function testForeach() 26 | { 27 | $array = $this->numerics(); 28 | $obj = p($array); 29 | foreach ($obj as $v) { 30 | $this->assertSame(array_shift($array), $v); 31 | } 32 | } 33 | 34 | public function testDecorate() 35 | { 36 | $array = $this->numerics(); 37 | $pipe = p($array); 38 | $obj = p($pipe); 39 | foreach ($obj as $v) { 40 | $this->assertSame(array_shift($array), $v); 41 | } 42 | } 43 | 44 | public function testChaining() 45 | { 46 | $array = $this->numerics(); 47 | $pipe = p($array)->filter(function () { 48 | return false; 49 | }); 50 | foreach ($pipe as $v) { 51 | $this->fail(); 52 | } 53 | 54 | $array = $this->numerics(); 55 | $pipe = p($array) 56 | ->filter(function () { 57 | return false; 58 | }) 59 | ->filter(function () { 60 | return false; 61 | }) 62 | ; 63 | foreach ($pipe as $v) { 64 | $this->fail(); 65 | } 66 | 67 | $this->assertEquals(get_class(p($array)), get_class($pipe)); 68 | } 69 | 70 | public function testToIterator() 71 | { 72 | $array = $this->associative(); 73 | $this->assertInstanceOf(\Iterator::class, p($array)->toIterator()); 74 | } 75 | 76 | public function testUnwrap() 77 | { 78 | $expected = $this->associative(); 79 | $obj = p($expected); 80 | $this->assertSame($expected, $obj->unwrap()); 81 | 82 | $expected = new TestIteratorAggregate($this->associative()); 83 | $obj = p($expected); 84 | $this->assertSame($expected, $obj->unwrap()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/ChunkTest.php: -------------------------------------------------------------------------------- 1 | associative(); 10 | $obj = p($array)->chunk(3); 11 | $expected = [ 12 | ['apples', 'bananas', 'cherries'] 13 | , 14 | ['damsons', 'elderberries', 'figs'], 15 | ]; 16 | $this->assertSame($expected, $obj->toArray()); 17 | } 18 | 19 | public function testChunkOdd() 20 | { 21 | $array = $this->associative(); 22 | $obj = p($array)->chunk(5); 23 | $expected = [ 24 | ['apples', 'bananas', 'cherries', 'damsons', 'elderberries'] 25 | , 26 | ['figs'], 27 | ]; 28 | $this->assertSame($expected, $obj->toArray()); 29 | } 30 | 31 | public function testException() 32 | { 33 | $this->setExpectedException('\InvalidArgumentException'); 34 | $obj = p($this->associative())->chunk(-3); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/EachTest.php: -------------------------------------------------------------------------------- 1 | associative(); 10 | $counter = 0; 11 | $obj = p($array)->each(function () use (&$counter) { 12 | ++$counter; 13 | }, true); 14 | $obj->toArray(); 15 | $this->assertEquals(6, $counter); 16 | 17 | $array = $this->associative(); 18 | $result = []; 19 | $obj = p($array)->each(function ($value, $key) use (&$result) { 20 | $result[$key] = $value; 21 | }, true); 22 | $obj->toArray(); 23 | $this->assertEquals($array, $result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/EmittableTest.php: -------------------------------------------------------------------------------- 1 | types() as $var) { 10 | $emitted = new \Pipes\Concept\Emittable($var); 11 | $this->assertSame($emitted->getValue(), $var); 12 | $this->assertFalse($emitted->hasKey()); 13 | try { 14 | $emitted->getKey(); 15 | $this->assertTrue(false, 'LogicException expected'); 16 | } catch (\LogicException $e) { 17 | // we expect this 18 | } 19 | } 20 | } 21 | public function testKeyValue() 22 | { 23 | foreach ($this->types() as $key => $var) { 24 | $emitted = new \Pipes\Concept\Emittable($var); 25 | $emitted->setKey($key); 26 | $this->assertSame($emitted->getValue(), $var); 27 | $this->assertSame($emitted->getKey(), $key); 28 | $this->assertTrue($emitted->hasKey()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/FilesTest.php: -------------------------------------------------------------------------------- 1 | associative(); 30 | $path = $this->createTest($array); 31 | $obj = p()->files("$path/*"); 32 | $expected = [ 33 | "$path/apples", 34 | "$path/bananas", 35 | "$path/cherries", 36 | "$path/damsons", 37 | "$path/elderberries", 38 | "$path/figs", 39 | ]; 40 | 41 | $expected = array_combine($expected, $expected); 42 | $this->assertSame($expected, $obj->toArray()); 43 | 44 | $obj = p()->files("$path/*", true); 45 | $result = $obj->toArray(); 46 | $this->assertSame(count($expected), count($result)); 47 | foreach ($result as $fileInfo) { 48 | $this->assertInstanceOf('\\SplFileInfo', $fileInfo); 49 | } 50 | 51 | $path = $this->removeTest($array, $path); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/FilterTest.php: -------------------------------------------------------------------------------- 1 | numerics(); 12 | $obj = p($array)->filter(function ($v, $k) { 13 | return $v % 2; 14 | }); 15 | $result = $obj->toArray(); 16 | $this->assertEquals([ 17 | 0 => 1, 18 | 2 => 3, 19 | 3 => 5, 20 | ], $result); 21 | 22 | // keys should not be preserved 23 | // but we do it when possible 24 | $array = $this->associative(); 25 | $obj = p($array)->filter(function ($v, $k) { 26 | return in_array($k, ['a', 'c']); 27 | }); 28 | $result = $obj->toArray(); 29 | $this->assertEquals([ 30 | 'a' => 'apples', 31 | 'c' => 'cherries', 32 | ], $result); 33 | } 34 | 35 | public function testArguments() 36 | { 37 | $me = $this; 38 | $obj = p(['a' => 3])->filter(function ($v, $k, $pipe) use ($me) { 39 | $me->assertSame(3, $v); 40 | $me->assertSame('a', $k); 41 | $me->assertInstanceOf('\Iterator', $pipe); 42 | $me->assertInstanceOf('\Pipes\PipeIterator', $pipe); 43 | })->toArray(); 44 | } 45 | // public function testAppend() 46 | // { 47 | // $array = $obj = p(['a' => 3])->filter(function ($v, $k, $pipe) { 48 | // if ($v === 3) { 49 | // $pipe->append(['b' => 4]); 50 | // } 51 | // 52 | // return true; 53 | // })->toArray(); 54 | // $this->assertSame(['a' => 3, 'b' => 4], $array); 55 | // } 56 | } 57 | -------------------------------------------------------------------------------- /tests/InfiniteTest.php: -------------------------------------------------------------------------------- 1 | associative(), 0, 2); 10 | $obj = p($array)->infinite(); 11 | 12 | $result = []; 13 | $i = 0; 14 | foreach ($obj as $value) { 15 | $result[] = $value; 16 | ++$i; 17 | if ($i >= 6) { 18 | break; 19 | } 20 | } 21 | 22 | $expected = [ 23 | 'apples', 'bananas', 24 | 'apples', 'bananas', 25 | 'apples', 'bananas', 26 | ]; 27 | 28 | $this->assertSame($expected, $result); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/LimitTest.php: -------------------------------------------------------------------------------- 1 | associative(); 10 | $obj = p($array)->limit(3); 11 | $expected = ['a' => 'apples', 'b' => 'bananas', 'c' => 'cherries']; 12 | $this->assertEquals($expected, $obj->toArray()); 13 | 14 | $obj = p($array)->limit(1, 2); 15 | $expected = ['b' => 'bananas', 'c' => 'cherries']; 16 | $this->assertEquals($expected, $obj->toArray()); 17 | 18 | $obj = p($array)->limit(1, 0); 19 | $this->assertEquals([], $obj->toArray()); 20 | 21 | try { 22 | $obj = p($array)->limit(-2); 23 | $this->fail('\\OutOfRangeException expected'); 24 | } catch (\OutOfRangeException $ex) { 25 | } 26 | } 27 | public function testToIterator() 28 | { 29 | $array = $this->associative(); 30 | 31 | $obj = p($array)->toIterator()->limit(3); 32 | $expected = ['a' => 'apples', 'b' => 'bananas', 'c' => 'cherries']; 33 | $this->assertEquals($expected, $obj->toArray()); 34 | 35 | $obj = p($array)->toIterator()->limit(1, 2); 36 | $expected = ['b' => 'bananas', 'c' => 'cherries']; 37 | $this->assertEquals($expected, $obj->toArray()); 38 | 39 | $obj = p($array)->toIterator()->limit(1, 0); 40 | $this->assertEquals([], $obj->toArray()); 41 | 42 | try { 43 | $obj = p($array)->toIterator()->limit(-2); 44 | $this->fail('\\OutOfRangeException expected'); 45 | } catch (\OutOfRangeException $ex) { 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/MapTest.php: -------------------------------------------------------------------------------- 1 | numerics(); 12 | $obj = p($array)->map(function ($value) { 13 | return $value; 14 | }); 15 | $result = $obj->toArray(); 16 | $this->assertEquals($array, $result); 17 | 18 | $array = $this->associative(); 19 | $obj = p($array)->map(function ($value) { 20 | return strtoupper($value); 21 | }); 22 | $result = $obj->toArray(); 23 | $expected = array_map('strtoupper', $array); 24 | $this->assertEquals($expected, $result); 25 | 26 | // same as previous, but using emit() 27 | $array = $this->associative(); 28 | $obj = p($array)->map(function ($value) { 29 | return p()->emit(strtoupper($value)); 30 | }); 31 | $result = $obj->toArray(); 32 | $expected = array_map('strtoupper', $array); 33 | $this->assertEquals($expected, $result); 34 | 35 | // same as previous, emitting the key 36 | $array = $this->associative(); 37 | $obj = p($array)->map(function ($value, $key) { 38 | return p()->emit(strtoupper($key), strtoupper($value)); 39 | }); 40 | $result = $obj->toArray(); 41 | $expected = array_combine(array_map('strtoupper', array_keys($array)), array_map('strtoupper', $array)); 42 | $this->assertEquals($expected, $result); 43 | } 44 | 45 | /** 46 | * In case of multiple items with the same key, the last one should win. 47 | */ 48 | public function testKeyConflicts() 49 | { 50 | // test with string keys 51 | $array = $this->numerics(); 52 | $obj = p($array)->map(function ($value) { 53 | return p()->emit('a', $value); 54 | }); 55 | $result = $obj->toArray(); 56 | $this->assertEquals([ 57 | 'a' => 5, 58 | ], $result); 59 | 60 | // test with numeric indexes 61 | $array = $this->numerics(); 62 | $obj = p($array)->map(function ($value) { 63 | return p()->emit(0, $value); 64 | }); 65 | $result = $obj->toArray(); 66 | $this->assertEquals([ 67 | 0 => 5, 68 | ], $result); 69 | } 70 | 71 | public function testIterateAGenerator() 72 | { 73 | $array = function () { 74 | $a = 0; 75 | while ($a <= 1e4) { 76 | yield $a++; 77 | } 78 | }; 79 | 80 | $obj = p($array())->map(function ($value) { 81 | return p()->emit(0, $value); 82 | }); 83 | $result = $obj->toArray(); 84 | $this->assertEquals([ 85 | 0 => 1e4, 86 | ], $result); 87 | } 88 | 89 | public function generatorInClassMethod($iterator) { 90 | foreach ($iterator as $key => $value) { 91 | yield $key => $value; 92 | yield $key . '_2' => $value . ':2'; 93 | } 94 | } 95 | 96 | public function testGenerator() 97 | { 98 | $array = $this->associative(); 99 | $result = p($array)->map(function ($iterator) { 100 | foreach ($iterator as $key => $value) { 101 | yield $key => $value; 102 | yield $key . '_2' => $value . ':2'; 103 | } 104 | })->toArray(); 105 | 106 | $expected = [ 107 | 'a' => 'apples', 108 | 'a_2' => 'apples:2', 109 | 'b' => 'bananas', 110 | 'b_2' => 'bananas:2', 111 | 'c' => 'cherries', 112 | 'c_2' => 'cherries:2', 113 | 'd' => 'damsons', 114 | 'd_2' => 'damsons:2', 115 | 'e' => 'elderberries', 116 | 'e_2' => 'elderberries:2', 117 | 'f' => 'figs', 118 | 'f_2' => 'figs:2', 119 | ]; 120 | 121 | $this->assertEquals($expected, $result); 122 | 123 | // let's see if passing a class method works also 124 | 125 | $result = p($array)->map([$this, 'generatorInClassMethod'])->toArray(); 126 | $this->assertEquals($expected, $result); 127 | } 128 | 129 | // public function testAppend() 130 | // { 131 | // $me = $this; 132 | // $array = $obj = p(['a' => 3])->map(function ($v, $k, $pipe) { 133 | // var_dump($pipe); 134 | // if ($v === 3) { 135 | // $pipe->append(['b' => 4]); 136 | // } 137 | // 138 | // return $v; 139 | // })->toArray(); 140 | // $this->assertSame(['a' => 3, 'b' => 4], $array); 141 | // } 142 | } 143 | -------------------------------------------------------------------------------- /tests/OuterIteratorTest.php: -------------------------------------------------------------------------------- 1 | associative()); 12 | $this->assertInstanceOf('\IteratorAggregate', $obj); 13 | } 14 | 15 | protected function getObject() 16 | { 17 | return p($this->associative())->toIterator(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/PipeIterationTest.php: -------------------------------------------------------------------------------- 1 | associative()); 12 | $this->assertInstanceOf('\IteratorAggregate', $obj); 13 | } 14 | 15 | protected function getObject() 16 | { 17 | return p(p($this->associative()))->toIterator(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/SkipTest.php: -------------------------------------------------------------------------------- 1 | associative(); 10 | $obj = p($array)->skip(0); 11 | $expected = [ 12 | 'a' => 'apples', 13 | 'b' => 'bananas', 14 | 'c' => 'cherries', 15 | 'd' => 'damsons', 16 | 'e' => 'elderberries', 17 | 'f' => 'figs', 18 | ]; 19 | $this->assertSame($expected, $obj->toArray()); 20 | 21 | $array = $this->associative(); 22 | $obj = p($array)->skip(2); 23 | $expected = [ 24 | 'c' => 'cherries', 25 | 'd' => 'damsons', 26 | 'e' => 'elderberries', 27 | 'f' => 'figs', 28 | ]; 29 | $this->assertSame($expected, $obj->toArray()); 30 | // and repeat 31 | $this->assertSame($expected, $obj->toArray()); 32 | 33 | $array = $this->associative(); 34 | $obj = p($array)->skip(1000); 35 | $expected = []; 36 | $this->assertSame($expected, $obj->toArray()); 37 | } 38 | 39 | public function testNoRewind() 40 | { 41 | $iterator = new \NoRewindIterator(new \ArrayIterator($this->associative())); 42 | $obj = p($iterator)->skip(2); 43 | $expected = [ 44 | 'c' => 'cherries', 45 | 'd' => 'damsons', 46 | 'e' => 'elderberries', 47 | 'f' => 'figs', 48 | ]; 49 | $this->assertSame($expected, $obj->toArray()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/SleepTest.php: -------------------------------------------------------------------------------- 1 | associative(), 0, 1); 11 | $obj = p($array)->sleep($delay); 12 | $start = microtime(true); 13 | $array2 = $obj->toArray(); 14 | $end = microtime(true); 15 | $this->assertEquals($delay, $end - $start, '', 0.05); 16 | $this->assertEquals($array, $array2); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/StopIfTest.php: -------------------------------------------------------------------------------- 1 | associative(); 10 | 11 | $obj = p($array)->stopIf(function ($value, $key) { 12 | return $key == 'd'; 13 | }, true); 14 | 15 | $expected = ['a' => 'apples', 'b' => 'bananas', 'c' => 'cherries']; 16 | $this->assertSame($expected, $obj->toArray()); 17 | 18 | $obj = p($array)->stopIf(function ($value) { 19 | return $value == 'damsons'; 20 | }); 21 | 22 | $expected = ['a' => 'apples', 'b' => 'bananas', 'c' => 'cherries']; 23 | $this->assertSame($expected, $obj->toArray()); 24 | } 25 | 26 | public function testContinueIf() 27 | { 28 | $array = $this->associative(); 29 | 30 | $obj = p($array)->continueIf(function ($value, $key) { 31 | return $key != 'd'; 32 | }, true); 33 | 34 | $expected = ['a' => 'apples', 'b' => 'bananas', 'c' => 'cherries']; 35 | $this->assertSame($expected, $obj->toArray()); 36 | 37 | $obj = p($array)->continueIf(function ($value) { 38 | return $value != 'damsons'; 39 | }); 40 | 41 | $expected = ['a' => 'apples', 'b' => 'bananas', 'c' => 'cherries']; 42 | $this->assertSame($expected, $obj->toArray()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/TestCase/BaseIteratorTestCase.php: -------------------------------------------------------------------------------- 1 | getObject(); 19 | $this->assertInstanceOf('\Iterator', $obj); 20 | } 21 | 22 | public function assertIteration($iterator, $expected) 23 | { 24 | $this->assertInstanceOf('\IteratorIterator', $iterator); 25 | 26 | $iterator->rewind(); 27 | $this->assertTrue($iterator->valid()); 28 | $this->assertSame($expected[0][0], $iterator->key()); 29 | $this->assertSame($expected[0][1], $iterator->current()); 30 | 31 | $iterator->next(); 32 | $this->assertTrue($iterator->valid()); 33 | $this->assertSame($expected[1][0], $iterator->key()); 34 | $this->assertSame($expected[1][1], $iterator->current()); 35 | 36 | foreach (range(1, 10) as $u) { 37 | $iterator->next(); 38 | } 39 | $this->assertFalse($iterator->valid()); 40 | } 41 | 42 | public function testIteration() 43 | { 44 | $this->assertIteration($this->getObject(), $this->expected); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/TestCase/CallbackTestCase.php: -------------------------------------------------------------------------------- 1 | getMessage().PHP_EOL."[ using toIterator: $useToIterator ]".PHP_EOL; 27 | } 28 | 29 | /** 30 | * A failure occurred. 31 | * 32 | * @param PHPUnit_Framework_Test $test 33 | * @param PHPUnit_Framework_AssertionFailedError $e 34 | * @param float $time 35 | */ 36 | public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) 37 | { 38 | $useToIterator = BaseTestCase::$useToIterator ? 'yes' : 'no'; 39 | echo $e->toString().PHP_EOL."[ using toIterator: $useToIterator ]".PHP_EOL; 40 | } 41 | 42 | /** 43 | * Incomplete test. 44 | * 45 | * @param PHPUnit_Framework_Test $test 46 | * @param Exception $e 47 | * @param float $time 48 | */ 49 | public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) 50 | { 51 | } 52 | 53 | /** 54 | * Skipped test. 55 | * 56 | * @param PHPUnit_Framework_Test $test 57 | * @param Exception $e 58 | * @param float $time 59 | * 60 | * @since Method available since Release 3.0.0 61 | */ 62 | public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) 63 | { 64 | } 65 | 66 | /** 67 | * A test suite started. 68 | * 69 | * @param PHPUnit_Framework_TestSuite $suite 70 | * 71 | * @since Method available since Release 2.2.0 72 | */ 73 | public function startTestSuite(PHPUnit_Framework_TestSuite $suite) 74 | { 75 | } 76 | 77 | /** 78 | * A test suite ended. 79 | * 80 | * @param PHPUnit_Framework_TestSuite $suite 81 | * 82 | * @since Method available since Release 2.2.0 83 | */ 84 | public function endTestSuite(PHPUnit_Framework_TestSuite $suite) 85 | { 86 | } 87 | 88 | /** 89 | * A test started. 90 | * 91 | * @param PHPUnit_Framework_Test $test 92 | */ 93 | public function startTest(PHPUnit_Framework_Test $test) 94 | { 95 | } 96 | } 97 | ?> 98 | -------------------------------------------------------------------------------- /tests/Tools/TestIterator.php: -------------------------------------------------------------------------------- 1 | key.') '.__METHOD__.PHP_EOL; 22 | 23 | return parent::current(); 24 | } 25 | 26 | public function next() 27 | { 28 | echo $this->key.') '.__METHOD__.PHP_EOL; 29 | ++$this->key; 30 | parent::next(); 31 | } 32 | 33 | public function key() 34 | { 35 | echo $this->key.') '.__METHOD__.PHP_EOL; 36 | 37 | return parent::key(); 38 | } 39 | 40 | public function valid() 41 | { 42 | echo $this->key.') '.__METHOD__.PHP_EOL; 43 | 44 | return parent::valid(); 45 | } 46 | 47 | public function rewind() 48 | { 49 | parent::rewind(); 50 | $this->key = 0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Tools/TestIteratorAggregate.php: -------------------------------------------------------------------------------- 1 | inner = $var; 14 | } 15 | 16 | /** 17 | * Retrieve an external iterator. 18 | * 19 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php 20 | * 21 | * @return Traversable An instance of an object implementing Iterator or 22 | * Traversable 23 | * 24 | * @since 5.0.0 25 | */ 26 | public function getIterator() 27 | { 28 | return $this->inner; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/ValueTest.php: -------------------------------------------------------------------------------- 1 | types() as $var) { 10 | $value = new \Pipes\Concept\Value($var); 11 | $this->assertSame($value->getValue(), $var); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/ValuesTest.php: -------------------------------------------------------------------------------- 1 | associative(); 10 | $obj = p($array)->values(); 11 | $expected = [ 12 | 'apples', 13 | 'bananas', 14 | 'cherries', 15 | 'damsons', 16 | 'elderberries', 17 | 'figs', 18 | ]; 19 | $this->assertSame($expected, $obj->toArray()); 20 | } 21 | 22 | public function testRewind() 23 | { 24 | $array = $this->associative(); 25 | $obj = p($array)->values(); 26 | $expected = [ 27 | 'apples', 28 | 'bananas', 29 | 'cherries', 30 | 'damsons', 31 | 'elderberries', 32 | 'figs', 33 | ]; 34 | 35 | $this->assertSame($expected, $this->foreachArray($obj)); 36 | // foreach calls a rewind 37 | $this->assertSame($expected, $this->foreachArray($obj)); 38 | } 39 | public function testToValues() 40 | { 41 | $array = $this->associative(); 42 | $obj = p($array)->toValues(); 43 | $expected = [ 44 | 'apples', 45 | 'bananas', 46 | 'cherries', 47 | 'damsons', 48 | 'elderberries', 49 | 'figs', 50 | ]; 51 | $this->assertSame($expected, $obj); 52 | } 53 | } 54 | --------------------------------------------------------------------------------