b2pweb /
bdf-collections
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Bdf\Collection\Stream; |
||
| 4 | |||
| 5 | use Bdf\Collection\Stream\Accumulator\AccumulatorInterface; |
||
| 6 | use Bdf\Collection\Stream\Collector\CollectorInterface; |
||
| 7 | use Bdf\Collection\Util\Functor\Consumer\ConsumerInterface; |
||
| 8 | use Bdf\Collection\Util\Functor\Predicate\PredicateInterface; |
||
| 9 | use Bdf\Collection\Util\Functor\Transformer\TransformerInterface; |
||
| 10 | use Bdf\Collection\Util\OptionalInterface; |
||
| 11 | use Iterator; |
||
| 12 | |||
| 13 | /** |
||
| 14 | * Stream apply operations on each elements of a Collection |
||
| 15 | * |
||
| 16 | * A stream instance can only be used once. It has two types of methods : |
||
| 17 | * - Transformation methods which return a new stream for applying transformations on elements. Any methods can be called after a transformation method |
||
| 18 | * - Terminal methods which iterate over the stream and "close" the stream. After calling a terminal method, no more methods can be called |
||
| 19 | * |
||
| 20 | * The transformations will be applied only when a termination method is called. So a stream should be used like : |
||
| 21 | * - Call one or more transformations |
||
| 22 | * - Finish processing with a terminal method |
||
| 23 | * |
||
| 24 | * <code> |
||
| 25 | * $collection->stream() // Create the stream from the collection |
||
| 26 | * ->map(...) // Apply transformations |
||
| 27 | * ->filter(...) |
||
| 28 | * ->forEach(...) // Terminate the stream |
||
| 29 | * ; |
||
| 30 | * </code> |
||
| 31 | * |
||
| 32 | * @template T |
||
| 33 | * @template K |
||
| 34 | * |
||
| 35 | * @extends Iterator<K, T> |
||
| 36 | */ |
||
| 37 | interface StreamInterface extends Iterator |
||
| 38 | { |
||
| 39 | /** |
||
| 40 | * Apply $transformer to each values of the stream |
||
| 41 | * |
||
| 42 | * <code> |
||
| 43 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 44 | * $stream |
||
| 45 | * ->map(function ($element, $key) { return $element * 2; }) |
||
| 46 | * ->toArray() // [2, 4, 6] |
||
| 47 | * ; |
||
| 48 | * </code> |
||
| 49 | * |
||
| 50 | * @template R |
||
| 51 | * @param callable(T, K=):R $transformer The element transformer. |
||
| 52 | * Should take the element as first parameter an return the transformed element |
||
| 53 | * The transformer may have (if relevant) the key as second parameter |
||
| 54 | * |
||
| 55 | * @return StreamInterface<R, K> |
||
| 56 | * |
||
| 57 | * @see TransformerInterface |
||
| 58 | */ |
||
| 59 | public function map(callable $transformer): StreamInterface; |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Apply $function to each values of the stream for generates keys |
||
| 63 | * |
||
| 64 | * The return type of the function is not checked, and duplicate keys, or illegal array offset may be generated. |
||
| 65 | * In such cases, toArray(), or other collector methods may have undefined behavior (others stream methods can be used safely). |
||
| 66 | * For remove elements with same result of the function, use distinct(), or IndexingBy collector. |
||
| 67 | * |
||
| 68 | * <code> |
||
| 69 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 70 | * $stream |
||
| 71 | * ->map(function ($element, $key) { return $element * 2; }) |
||
| 72 | * ->toArray() // [2 => 1, 4 => 2, 6 => 3] |
||
| 73 | * ; |
||
| 74 | * |
||
| 75 | * // Apply transformation to the key (snake_case to PascalCase) |
||
| 76 | * $stream = new ArrayStream(['first_name' => 'John', 'last_name' => 'Doe']); |
||
| 77 | * $stream |
||
| 78 | * ->mapKey(function ($e, $key) { |
||
| 79 | * return Streams::wrap(explode('_', $key))->map(function ($k) { return ucfirst($k); })->collect(new Joining()); |
||
| 80 | * }) |
||
| 81 | * ->toArray() // ['FirstName' => 'John', 'LastName' => 'Doe'] |
||
| 82 | * ; |
||
| 83 | * </code> |
||
| 84 | * |
||
| 85 | * @template R |
||
| 86 | * @param callable(T, K):R $function The key generator. |
||
| 87 | * Should take the element as first parameter, the key as second parameter, and return the new key |
||
| 88 | * |
||
| 89 | * @return StreamInterface<T, R> |
||
| 90 | * |
||
| 91 | * @see TransformerInterface |
||
| 92 | */ |
||
| 93 | public function mapKey(callable $function): StreamInterface; |
||
| 94 | |||
| 95 | /** |
||
| 96 | * Filter the stream |
||
| 97 | * |
||
| 98 | * <code> |
||
| 99 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 100 | * $stream |
||
| 101 | * ->filter(function ($element, $key) { return $element % 2 !== 0; }) |
||
| 102 | * ->toArray() // [1, 3] |
||
| 103 | * ; |
||
| 104 | * </code> |
||
| 105 | * |
||
| 106 | * @param callable(T, K=):bool $predicate The predicate function. |
||
| 107 | * Take the element as first parameter and should return a boolean (true for keeping element, or false for skipping) |
||
| 108 | * May take the key as second parameter (if relevant) |
||
| 109 | * |
||
| 110 | * @return StreamInterface<T, K> |
||
| 111 | * |
||
| 112 | * @see PredicateInterface |
||
| 113 | */ |
||
| 114 | public function filter(callable $predicate): StreamInterface; |
||
| 115 | |||
| 116 | /** |
||
| 117 | * Filter stream elements to get only distinct elements |
||
| 118 | * Two elements are considered as equals when there hash are equals : `$hashFunction($e1) === $hashFunction($e2)` |
||
| 119 | * |
||
| 120 | * If the hash function is not provided, elements will be compared using : |
||
| 121 | * - If it's an Hashable object, the Hashable::hash() method |
||
| 122 | * - In other case, compare with value AND type |
||
| 123 | * |
||
| 124 | * By default, int(123) and string('123') are not considered as equal, and will be keep into the distinct stream |
||
| 125 | * |
||
| 126 | * <code> |
||
| 127 | * $stream = new ArrayStream([4, 8, 1, 4, 1]); |
||
| 128 | * $stream->distinct(); // [4, 8, 1] |
||
| 129 | * |
||
| 130 | * $stream = new ArrayStream([[1, 2], [2, 3], [2, 1]]); |
||
| 131 | * $stream->distinct(function ($e) { sort($e); return json_encode($e); }); // [[1, 2], [2, 3]] |
||
| 132 | * </code> |
||
| 133 | * |
||
| 134 | * @param callable(T):array-key|null $hashFunction The hash function. Take as parameter the element, and return the hash value as string |
||
| 135 | * |
||
| 136 | * @return StreamInterface<T, K> |
||
| 137 | */ |
||
| 138 | public function distinct(?callable $hashFunction = null): StreamInterface; |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Order stream elements |
||
| 142 | * |
||
| 143 | * <code> |
||
| 144 | * $stream = new ArrayStream([8, 4, 5, 3]); |
||
| 145 | * $stream->sort()->toArray(); // [3, 4, 5, 8] |
||
| 146 | * |
||
| 147 | * $stream |
||
| 148 | * ->sort(function ($a, $b) { return ([$a % 2, $a] <=> [$b % 2, $b]); }) |
||
| 149 | * ->toArray() // [4, 8, 3, 5] |
||
| 150 | * ; |
||
| 151 | * |
||
| 152 | * // Sort keeping keys |
||
| 153 | * $stream = new ArrayStream([ |
||
| 154 | * 'foo' => 3, |
||
| 155 | * 'bar' => 42, |
||
| 156 | * 'baz' => 9 |
||
| 157 | * ]); |
||
| 158 | * |
||
| 159 | * $stream->sort(null, true)->toArray(); |
||
| 160 | * // [ 'foo' => 3, |
||
| 161 | * // 'baz' => 9, |
||
| 162 | * // 'bar' => 42 ] |
||
| 163 | * </code> |
||
| 164 | * |
||
| 165 | * /!\ Unlike other transformations, the elements are fetched before execution of the terminal method |
||
| 166 | * |
||
| 167 | * Ex : |
||
| 168 | * <code> |
||
| 169 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 170 | * |
||
| 171 | * // Display : map(1) forEach(1) map(2) forEach(2) map(3) forEach(3) |
||
| 172 | * $stream |
||
| 173 | * ->map(function ($e) { echo "map($e) "; return $e; }) |
||
| 174 | * ->forEach(function ($e) { echo "forEach($e) "; }) |
||
| 175 | * ; |
||
| 176 | * |
||
| 177 | * // Display : map(1) map(2) map(3) forEach(1) forEach(2) forEach(3) |
||
| 178 | * $stream |
||
| 179 | * ->map(function ($e) { echo "map($e) "; return $e; }) |
||
| 180 | * ->sort() // Sort fetch the map stream |
||
| 181 | * ->forEach(function ($e) { echo "forEach($e) "; }) |
||
| 182 | * ; |
||
| 183 | * </code> |
||
| 184 | * |
||
| 185 | * @param callable(T,T):int|null $comparator The comparator, or null to use default comparison. |
||
| 186 | * Take the two values to compare as parameters and should return an integer : |
||
| 187 | * - $comparator($a, $b) < 0 => $a < $b |
||
| 188 | * - $comparator($a, $b) == 0 => $a == $b |
||
| 189 | * - $comparator($a, $b) > 0 => $a > $b |
||
| 190 | * |
||
| 191 | * @param boolean $preserveKeys If true, the keys will be kept, else the values will be indexed by an increment integer |
||
| 192 | * |
||
| 193 | * @return StreamInterface<T, array-key> |
||
| 194 | */ |
||
| 195 | public function sort(callable $comparator = null, bool $preserveKeys = false): StreamInterface; |
||
| 196 | |||
| 197 | /** |
||
| 198 | * Concatenate a new stream after the current stream |
||
| 199 | * The current stream will be the first executed stream, and the concatenated one will be executed after |
||
| 200 | * |
||
| 201 | * /!\ The current stream must not be an infinite stream |
||
| 202 | * |
||
| 203 | * <code> |
||
| 204 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 205 | * $stream |
||
| 206 | * ->concat(new ArrayStream([4, 5, 6]), false) |
||
| 207 | * ->toArray() // [1, 2, 3, 4, 5, 6] |
||
| 208 | * ; |
||
| 209 | * </code> |
||
| 210 | * |
||
| 211 | * @param StreamInterface<T, mixed> $stream The stream to concat |
||
| 212 | * @param bool $preserveKeys Preserve the stream keys, or use integer increment index |
||
| 213 | * |
||
| 214 | * @return StreamInterface<T, mixed> |
||
| 215 | */ |
||
| 216 | public function concat(StreamInterface $stream, bool $preserveKeys = true): StreamInterface; |
||
| 217 | |||
| 218 | /** |
||
| 219 | * Create a stream resulting of concatenation of each elements content extracted by $transformer |
||
| 220 | * This method reduce by one the depth of multidimensional stream |
||
| 221 | * |
||
| 222 | * Example: |
||
| 223 | * <code> |
||
| 224 | * $stream = new ArrayStream([ |
||
| 225 | * ['values' => [1, 2]], |
||
| 226 | * ['values' => 3], |
||
| 227 | * ['values' => new ArrayStream([4, 5])], |
||
| 228 | * ]); |
||
| 229 | * |
||
| 230 | * $stream->flatMap(function ($e) { return $e['values']; })->toArray(); // [1, 2, 3, 4, 5] |
||
| 231 | * </code> |
||
| 232 | * |
||
| 233 | * (i) Each transformed elements will be transformed to a Stream using Streams::wrap() |
||
| 234 | * Empty array and null will be transformed to an EmptyStream, array to an array stream, etc... |
||
| 235 | * For ensure that no transformation is applied, the transformer should return a StreamInterface |
||
| 236 | * |
||
| 237 | * This method is equivalent with : |
||
| 238 | * <code> |
||
| 239 | * $stream |
||
| 240 | * ->map($transformer) |
||
| 241 | * ->map(function ($e) { return Streams::wrap($e); }) |
||
| 242 | * ->reduce( |
||
| 243 | * function (StreamInterface $a, StreamInterface $b) { return $a->concat($b); }, |
||
| 244 | * EmptyStream::instance() |
||
| 245 | * ) |
||
| 246 | * ; |
||
| 247 | * </code> |
||
| 248 | * |
||
| 249 | * @template R |
||
| 250 | * @param callable(T, K):(StreamInterface<R, mixed>|R[]|R) $transformer The element transformer |
||
| 251 | * Should take the element as first parameter and return the transformed element |
||
| 252 | * The transformer may have (if relevant) the key as second parameter |
||
| 253 | * |
||
| 254 | * @param bool $preserveKeys Preserve the sub-streams keys, or use integer increment index |
||
| 255 | * |
||
| 256 | * @return StreamInterface<R, mixed> |
||
| 257 | * |
||
| 258 | * @see TransformerInterface |
||
| 259 | * @see Streams::wrap() Used to transform each transformed elements to a Stream |
||
| 260 | */ |
||
| 261 | public function flatMap(callable $transformer, bool $preserveKeys = false): StreamInterface; |
||
| 262 | |||
| 263 | /** |
||
| 264 | * Skip the $count first elements of the stream. |
||
| 265 | * Give a count higher than the number of elements of the stream will results of an empty stream. |
||
| 266 | * |
||
| 267 | * Example: |
||
| 268 | * <code> |
||
| 269 | * $stream = new ArrayStream([1, 2, 3, 4]); |
||
| 270 | * $stream->skip(2)->toArray(); // [3, 4] |
||
| 271 | * </code> |
||
| 272 | * |
||
| 273 | * @param int $count Number of elements to skip. Must be a positive number. |
||
| 274 | * |
||
| 275 | * @return StreamInterface<T, K> |
||
| 276 | * |
||
| 277 | * @see StreamInterface::limit() For limit the number of stream's elements |
||
| 278 | */ |
||
| 279 | public function skip(int $count): StreamInterface; |
||
| 280 | |||
| 281 | /** |
||
| 282 | * Limit the number of elements of the stream. |
||
| 283 | * Stop the stream when it reach $count elements. |
||
| 284 | * |
||
| 285 | * Example: |
||
| 286 | * <code> |
||
| 287 | * $stream = new ArrayStream([1, 2, 3, 4]); |
||
| 288 | * $stream->limit(2)->toArray(); // [1, 2] |
||
| 289 | * $stream->limit(2, 1)->toArray(); // [2, 3] |
||
| 290 | * </code> |
||
| 291 | * |
||
| 292 | * @param positive-int|0 $count The maximum number elements |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 293 | * @param positive-int|0 $offset Number of elements to skip at start of the stream |
||
| 294 | * |
||
| 295 | * @return StreamInterface<T, K> |
||
| 296 | * |
||
| 297 | * @see StreamInterface::skip() For skip firsts elements |
||
| 298 | */ |
||
| 299 | public function limit(int $count, int $offset = 0): StreamInterface; |
||
| 300 | |||
| 301 | /** |
||
| 302 | * Iterate over all stream elements. |
||
| 303 | * This method is a terminal method : the stream must not be used after |
||
| 304 | * |
||
| 305 | * <code> |
||
| 306 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 307 | * $stream->forEach(function ($element, $key) { |
||
| 308 | * $element->doSomething(); |
||
| 309 | * }); |
||
| 310 | * </code> |
||
| 311 | * |
||
| 312 | * @param callable(T, K=):void $consumer |
||
| 313 | * |
||
| 314 | * @return void |
||
| 315 | * |
||
| 316 | * @see ConsumerInterface |
||
| 317 | */ |
||
| 318 | public function forEach(callable $consumer): void; |
||
| 319 | |||
| 320 | /** |
||
| 321 | * Aggregate the stream to an array |
||
| 322 | * This method is a terminal method : the stream must not be used after |
||
| 323 | * |
||
| 324 | * <code> |
||
| 325 | * $stream = new ArrayStream([ |
||
| 326 | * 'foo' => 'bar', |
||
| 327 | * 'value' => 42 |
||
| 328 | * ]); |
||
| 329 | * |
||
| 330 | * $stream->toArray() === ['foo' => 'bar', 'value' => 42]; |
||
| 331 | * $stream->toArray(false) === ['bar', 42]; |
||
| 332 | * </code> |
||
| 333 | * |
||
| 334 | * @param bool $preserveKeys True to preserve the keys of the stream, or false for reindex with increment integer. |
||
| 335 | * This parameter must be set to false when stream contains complex keys (not integer or string) |
||
| 336 | * |
||
| 337 | * @return T[] |
||
| 338 | * |
||
| 339 | * @template PK as bool |
||
| 340 | * @psalm-param PK $preserveKeys |
||
| 341 | * @psalm-return (PK is true ? array<K, T> : list<T>) |
||
| 342 | */ |
||
| 343 | public function toArray(bool $preserveKeys = true): array; |
||
| 344 | |||
| 345 | /** |
||
| 346 | * Get the first element of the stream |
||
| 347 | * The element will be wrapped into an Optional for handle empty stream |
||
| 348 | * |
||
| 349 | * <code> |
||
| 350 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 351 | * $stream->first(); // Optional(1); |
||
| 352 | * $stream->filter(function () { return false; })->first(); // Empty Optional |
||
| 353 | * </code> |
||
| 354 | * |
||
| 355 | * @return OptionalInterface<T> |
||
| 356 | */ |
||
| 357 | public function first(): OptionalInterface; |
||
| 358 | |||
| 359 | /** |
||
| 360 | * Reduce all elements of the stream into a single value |
||
| 361 | * This method is a terminal method : the stream must not be used after |
||
| 362 | * |
||
| 363 | * <code> |
||
| 364 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 365 | * $stream->reduce(function ($carry, $item) { return (int) $carry + $item; }); // 6 |
||
| 366 | * $stream->reduce(Accumulators::sum()); // Same as above, but with a functor |
||
| 367 | * </code> |
||
| 368 | * |
||
| 369 | * @template R |
||
| 370 | * |
||
| 371 | * @param callable(R|null,T):R|AccumulatorInterface<T, R> $accumulator The accumulator. |
||
| 372 | * When a callback is given : takes the reduced value as first parameter and the item to accumulate as second parameter. The function must return the reduced value |
||
| 373 | * When an AccumulatorInterface is given as only parameter, the initial value will be $accumulator->initial() |
||
| 374 | * @param R|null $initial The initial value |
||
|
0 ignored issues
–
show
The type
Bdf\Collection\Stream\R was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||
| 375 | * |
||
| 376 | * @return R The reduced value, or $initial if the stream is empty |
||
| 377 | * |
||
| 378 | * @see AccumulatorInterface For functor implementation |
||
| 379 | */ |
||
| 380 | public function reduce(callable $accumulator, $initial = null); |
||
| 381 | |||
| 382 | /** |
||
| 383 | * Collect all elements into a single value |
||
| 384 | * This method is a terminal method : the stream must not be used after |
||
| 385 | * |
||
| 386 | * The behavior of this method is very similar to reduce() but with some differences : |
||
| 387 | * - The collector is not stateless |
||
| 388 | * - It has a finalisation method whereas reduce perform aggregation on each iterations |
||
| 389 | * |
||
| 390 | * @template R |
||
| 391 | * @param CollectorInterface<T, K, R> $collector The collector |
||
| 392 | * |
||
| 393 | * @return R |
||
| 394 | */ |
||
| 395 | public function collect(CollectorInterface $collector); |
||
| 396 | |||
| 397 | /** |
||
| 398 | * Check if all elements of the stream match with the predicate |
||
| 399 | * This method is a terminal method : the stream must not be used after |
||
| 400 | * |
||
| 401 | * Note: An empty stream will always return true |
||
| 402 | * |
||
| 403 | * /!\ One infinite stream, this method may cause an infinite loop |
||
| 404 | * |
||
| 405 | * <code> |
||
| 406 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 407 | * |
||
| 408 | * $stream->allMatch(function ($e) { return $e < 5; }); // true |
||
| 409 | * $stream->allMatch(function ($e) { return $e % 2 === 0; }); // false |
||
| 410 | * </code> |
||
| 411 | * |
||
| 412 | * @param callable(T, K=):bool $predicate The predicate function. |
||
| 413 | * Take the element as first parameter and should return a boolean (true if matching) |
||
| 414 | * May take the key as second parameter (if relevant) |
||
| 415 | * |
||
| 416 | * @return bool |
||
| 417 | * |
||
| 418 | * @see PredicateInterface |
||
| 419 | */ |
||
| 420 | public function matchAll(callable $predicate): bool; |
||
| 421 | |||
| 422 | /** |
||
| 423 | * Check if at least one element of the stream match with the predicate |
||
| 424 | * This method is a terminal method : the stream must not be used after |
||
| 425 | * |
||
| 426 | * /!\ One infinite stream, this method may cause an infinite loop |
||
| 427 | * |
||
| 428 | * <code> |
||
| 429 | * $stream = new ArrayStream([1, 2, 3]); |
||
| 430 | * |
||
| 431 | * $stream->allMatch(function ($e) { return $e % 2 === 0; }); // true : 2 % 2 === 0 |
||
| 432 | * $stream->allMatch(function ($e) { return $e > 5; }); // false |
||
| 433 | * </code> |
||
| 434 | * |
||
| 435 | * @param callable(T, K=):bool $predicate The predicate function. |
||
| 436 | * Take the element as first parameter and should return a boolean (true if matching) |
||
| 437 | * May take the key as second parameter (if relevant) |
||
| 438 | * |
||
| 439 | * @return bool |
||
| 440 | * |
||
| 441 | * @see PredicateInterface |
||
| 442 | */ |
||
| 443 | public function matchOne(callable $predicate): bool; |
||
| 444 | } |
||
| 445 |