improved / 可迭代的
与数组、迭代器和其它可遍历对象交互的函数
Requires
- php: >=7.2.0
- improved/type: ~0.1.0
Requires (Dev)
- jasny/php-code-quality: ~2.3
- phpstan/phpstan: ~0.11.0
Provides
README
可迭代的
函数式风格的操作,如数组上的map-reduce转换,迭代器 和其它 可遍历 对象。
这些函数与它们的 array_*
对应函数不同,因为它们可以与任何类型的可迭代对象一起工作,而不仅仅是数组。如果您不熟悉PHP中的迭代器和生成器,请首先阅读"什么是迭代器?"这一段。
该库支持过程式和面向对象编程范式。
安装
composer require improved/iterable
方法
通用方法
链式方法
映射
map(callable $callback)
mapKeys(callable $callback)
apply(callable $callback)
chunk(int $size)
group(callable $callback)
unwind(int|string $column[, int|string|null $mapKey[, bool $preserveKeys]])
flatten()
fill(mixed $value)
column(int|string|null $valueColumn[, int|string|null $keyColumn])
project(array $mapping)
reshape(array $columns)
values()
keys()
setKeys(Traversable $keys)
flip()
过滤
filter(callable $matcher)
cleanup()
unique([callable $callback])
uniqueKeys()
limit(int $size)
slice(int $offset[, int $size])
before(callable $matcher[, bool $include])
after(callable $matcher[, bool $include])
排序
类型处理
其他方法
查找
first([bool $required])
last([bool $required])
find(callable $matcher)
findKey(callable $matcher)
min([callable $compare])
max([callable $compare])
聚合
count(): int
reduce(callable $callback[, mixed $initial])
sum(): int|float
average(): int|float
concat([string $glue]): string
构建方法
示例
所有函数和对象都在 Improved
命名空间中。可以将命名空间别名设置为 i
或单独导入每个函数。
use Improved as i; $filteredValues = i\iterable_filter($values, function($value) { return is_int($value) && $value > 10; }); $uniqueValues = i\iterable_unique($filteredValues); $mappedValues = i\iterable_map($uniqueValues, function($value) { return $value * $value - 1; }); $firstValues = i\iterable_slice($mappedValues, 0, 10); $result = i\iterable_to_array($firstValues);
或者使用迭代器管道。
use Improved\IteratorPipeline\Pipeline; $result = Pipeline::with($values) ->filter(function($value) { return is_int($value) && $value < 10; }) ->unique() ->map(function($value) { return $value * $value - 1; }) ->limit(10) ->toArray();
用法
此库提供了创建流的实用方法。
Pipeline
以数组或 Traversable
对象作为源参数。可以使用静态的 with()
方法代替 new
。
use Improved\IteratorPipeline\Pipeline; Pipeline::with([ new Person("Max", 18), new Person("Peter", 23), new Person("Pamela", 23) ]); $dirs = new Pipeline(new \DirectoryIterator('some/path'));
管道使用 PHP 生成器,它们是单向且不可重绕的。这意味着管道只能使用一次。
PipelineBuilder
可以使用 PipelineBuilder
来创建管道的蓝图。构建器包含 Pipeline
的映射方法,而不包含其他方法。
可以使用静态的 Pipeline::build()
方法作为创建构建器的语法糖。
use Improved\IteratorPipeline\Pipeline; $blueprint = Pipeline::build() ->checkType('string') ->filter(function(string $value): bool) { strlen($value) > 10; }); // later $pipeline = $blueprint->with($iterable);
PipelineBuilder
是一个不可变对象,每个方法调用都会创建构建器的新副本。
或者可以调用管道构建器,它会创建一个管道并在上面调用 toArray()
。
use Improved\IteratorPipeline\Pipeline; $unique = Pipeline::build() ->unique() ->values(); $result = $unique($values);
可以使用 then()
方法来组合两个管道构建器。
use Improved\IteratorPipeline\Pipeline; $first = Pipeline::build()->unique()->values(); $second = Pipeline::build()->map(function($value) { return ucwords($value); }); $titles = $first->then($second); $result = $titles($values);
自定义管道类
与 PipelineBuilder
不同,Pipeline
不是一个不可变对象。只有最新步骤返回的 iterable
是与管道相关的并保留下来。因此,您可以扩展 Pipeline
类,并使用任何链式方法,而对象不会改变。
但是,如果一个步骤返回一个 Pipeline
对象(包括扩展管道的任何对象),则 then
方法将返回该对象而不是 $this
。这可以用于在管道或管道构建器中稍后注入自定义类。
use Improved\IteratorPipeline\Pipeline; class MyPipeline extends Pipeline { function product() { $product = 1; foreach ($this->iterable as $value) { $product *= $value; } return $product; } }
从您的自定义类开始
$product = (new MyPipeline)->column('amount')->product();
在现有管道中
$pipeline = (new Pipeline)->column('amount'); $product = $pipeline ->then(function(iterable $iterable) { return new MyPipeline($iterable); }) ->product();
在管道构建器中
$builder = (new PipelineBuilder) ->then(function(iterable $iterable) { return new MyPipeline($iterable); }) ->column('amount') ->product();
这是获取 PipelineBuilder
返回自定义 Pipeline
类的唯一方法,而不需要创建自定义 PipelineBuilder
。
方法引用
then
then()
方法在管道中定义一个新的步骤。它接受一个回调,该回调必须返回一个 Generator
或其他 Traversable
。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red']) ->then(function(\Traversable $values): \Generator { foreach ($values as $key => $value) { yield $key[0] => "$value $key"; } }) ->toArray(); // ['a' => 'green apple', 'b' => 'blue berry', 'c' => 'red cherry']
它可以用来应用自定义(外部)迭代器。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red']) ->then(function(\Traversable $values): \Iterator { return new MyCustomIterator($values); });
getIterator
管道实现了 IteratorAggregate
接口。这意味着它是可遍历的。或者您可以使用 getIterator()
。
toArray
将迭代器的元素复制到一个数组中。
Pipeline::with(["one", "two", "three"]) ->toArray();
walk
遍历迭代器,不捕获值。这在 apply()
之后特别有用。
Pipeline::with($objects) ->apply(function($object, $key) { $object->type = $key; }) ->walk();
映射
map
使用回调函数将每个元素映射到值。
Pipeline::with([3, 2, 2, 3, 7, 3, 6, 5]) ->map(function(int $i): int { return $i * $i; }) ->toArray(); // [9, 4, 4, 9, 49, 9, 36, 25]
回调函数的第二个参数是键。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red']) ->map(function(string $value, string $key): string { return "{$value} {$key}"; }) ->toArray(); // ['apple' => 'green apple', 'berry' => 'blue berry', 'cherry' => 'red cherry']
mapKeys
使用回调函数将每个元素的键映射到新的键。
回调函数的第二个参数是值,第三个是键。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red']) ->mapKeys(function(string $value, string $key): string { return subst($key, 0, 1); }) ->toArray(); // ['a' => 'green', 'b' => 'blue', 'c' => 'red']
apply
将回调函数应用于迭代器中的每个元素。回调函数返回的任何值都被忽略。
$persons = [ 'client' => new Person("Max", 18), 'seller' => new Person("Peter", 23), 'lawyer' => new Person("Pamela", 23) ]; Pipeline::with($persons) ->apply(function(Person $value, string $key): void { $value->role = $key; }) ->toArray();
chunk
将可迭代对象分成指定大小的块。
Pipeline::with(['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']) ->chunk(4); // <iterator>[ // <iterator>['I', 'II', 'III', 'IV'], // <iterator>['V', 'VI', 'VII', 'VIII'], // <iterator>['IX', 'X'] // ]
块是迭代器而不是数组。键被保留。
group
按组名(键)和元素数组(值)对迭代器的元素进行分组。
Pipeline::with(['apple', 'berry', 'cherry', 'apricot']) ->group(function(string $value): string { return $value[0]; }) ->toArray(); // ['a' => ['apple', 'apricot'], 'b' => ['berry'], 'c' => ['cherry']]
第二个参数是键。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red', 'apricot' => 'orange']) ->group(function(string $value, string $key): string { return $key[0]; }) ->toArray(); // ['a' => ['apple' => 'green', 'apricot' => 'orange'], 'b' => ['berry' => 'blue'], 'c' => ['cherry' => 'red']]
flatten
遍历所有子迭代器并将它们连接起来。
$groups = [ ['one', 'two'], ['three', 'four', 'five'], [], ['six'], 'seven' ]; Pipeline::with($groups) ->flatten() ->toArray(); // ['one', 'two', 'three', 'four', 'five', 'six', 'seven']
默认情况下,键被删除,替换为递增计数器(因此是一个数字数组)。通过将 true
作为第二个参数传递,保留键。
unwind
为每个元素解构可迭代属性/项目。结果是每个可迭代属性中的一个元素。您必须指定要解构的列。
$elements = [ ['ref' => 'a', 'numbers' => ['I' => 'one', 'II' => 'two']], ['ref' => 'b', 'numbers' => 'three'], ['ref' => 'c', 'numbers' => []] ]; Pipeline::with($elements) ->unwind('numbers') ->toArray(); // [ // ['ref' => 'a', 'numbers' => 'one'], // ['ref' => 'a', 'numbers' => 'two'], // ['ref' => 'b', 'numbers' => 'three'], // ['ref' => 'c', 'numbers' => null] // ]
第二个参数是可选的,接受一个列名,将其添加到每个元素中。
Pipeline::with($elements) ->unwind('numbers', 'nrkey') ->toArray(); // [ // ['ref' => 'a', 'numbers' => 'one', 'nrkey' => 'I'], // ['ref' => 'a', 'numbers' => 'two', 'nrkey' => 'II], // ['ref' => 'b', 'numbers' => 'three', 'nrkey' => null], // ['ref' => 'c', 'numbers' => null, 'nrkey' => null] // ]
默认情况下,结果迭代器中的每个新元素是一个数字序列。为了保留键,将 true
作为第三个参数传递。请注意,这将导致重复的键。
fill
设置可迭代对象的所有值。不要触摸键。
这可以与 flip
结合使用,类似于 array_fill_keys
。
$fields = ['foo', 'bar', 'qux']; Pipeline::with($fields) ->flip() ->fill(42) ->toArray(); // ['foo' => 42, 'bar' => 42, 'qux' => 42]
column
返回单个列/属性的值。每个元素应该是一个数组或对象。
$rows = [ ['one' => 'uno', 'two' => 'dos', 'three' => 'tres', 'four' => 'cuatro', 'five' => 'cinco'], ['one' => 'yi', 'two' => 'er', 'three' => 'san', 'four' => 'si', 'five' => 'wu'], ['one' => 'één', 'two' => 'twee', 'three' => 'drie', 'five' => 'vijf'] ]; Pipeline::with($rows) ->column('three') ->toArray(); // ['tres', 'san', 'drie']
通过指定键创建键/值对。
$rows = [ ['one' => 'uno', 'two' => 'dos', 'three' => 'tres', 'four' => 'cuatro', 'five' => 'cinco'], ['one' => 'yi', 'two' => 'er', 'three' => 'san', 'four' => 'si', 'five' => 'wu'], ['one' => 'één', 'two' => 'twee', 'three' => 'drie', 'five' => 'vijf'] ]; Pipeline::with($rows) ->column('three', 'two') ->toArray(); // ['dos' => 'tres', 'er' => 'san', 'twee' -=> 'drie']
或者,您也可以仅指定键列,使用 null
作为值列,以保留值未修改。
如果一个元素没有指定键,则键和/或值将为 null
。
project
将迭代器中的每个元素投影到相关的(或数字)数组。每个元素应该是一个数组或对象。
对于投影,必须提供一个映射 [新键 > 旧键]
。
$rows = [ ['one' => 'uno', 'two' => 'dos', 'three' => 'tres', 'four' => 'cuatro', 'five' => 'cinco'], ['one' => 'yi', 'two' => 'er', 'three' => 'san', 'four' => 'si', 'five' => 'wu'], ['one' => 'één', 'two' => 'twee', 'three' => 'drie', 'five' => 'vijf'] ]; Pipeline::with($rows) ->project(['I' => 'one', 'II' => 'two', 'II' => 'three', 'IV' => 'four']) ->toArray(); // [ // ['I' => 'uno', 'II' => 'dos', 'III' => 'tres', 'IV' => 'cuatro'], // ['I' => 'yi', 'II' => 'er', 'III' => 'san', 'IV' => 'si'], // ['I' => 'één', 'II' => 'twee', 'III' => 'drie', 'IV' => null] // ]
如果一个元素没有指定键,其值将是 null
。
投影数组的键的顺序始终与映射的顺序相同。映射也可以是一个数值数组。
reshape
重塑迭代器中的每个元素,添加或删除属性或键。
该方法接受一个以列名为键的数组。值可以是布尔值,指定该列是否保留或删除。或者列可以是字符串或整数,用于重命名列名(键)。
未指定的列保持不变。这具有与 'column' => true
相同的效果。
$rows = [ ['one' => 'uno', 'two' => 'dos', 'three' => 'tres', 'four' => 'cuatro', 'five' => 'cinco'], ['three' => 'san', 'two' => 'er', 'five' => 'wu', 'four' => 'si'], ['two' => 'twee', 'four' => 'vier'] ]; Pipeline::with($rows) ->reshape(['one' => true, 'two' => false, 'three' => 'III', 'four' => 0]) ->toArray(); // [ // ['one' => 'uno', 'five' => 'cinco', 'III' => 'tres', 0 => 'cuatro'], // ['five' => 'wu', 'III' => 'san', 0 => 'si'], // [0 => 'vier'] // ];
注意,与 project()
不同,数组或对象会被修改。如果元素没有特定的键,则忽略它。如果元素不是对象或数组,则保持不变。
values
保留值,丢弃键。键变成一个递增数字。这类似于 array_values
。
Pipeline::with(['I' => 'one', 'II' => 'two', 'III' => 'three', 'IV' => 'four']) ->values() ->toArray(); // ['one', 'two', 'three', 'four']
keys
使用键作为值。键变成一个递增数字。这类似于 array_keys
。
Pipeline::with(['I' => 'one', 'II' => 'two', 'III' => 'three', 'IV' => 'four']) ->keys() ->toArray(); // ['I', 'II', 'III', 'IV']
setKeys
使用另一个迭代器作为键,当前迭代器作为值。
Pipeline::with(['one', 'two', 'three', 'four']) ->setKeys(['I', 'II', 'III', 'IV']) ->toArray(); // ['I' => 'one', 'II' => 'two', 'III' => 'three', 'IV' => 'four']
键可以是任何类型,且不需要是唯一的。
从迭代器生成的元素数量仅取决于键的数量。如果键的数量多于值,则默认值将为 null
。如果值的数量多于键,则额外的值不会被返回。
flip
使用值作为键,反之亦然。
Pipeline::with(['one' => 'uno', 'two' => 'dos', 'three' => 'tres', 'four' => 'cuatro']) ->flip() ->toArray(); // ['uno' => 'one', 'dos' => 'two', 'tres' => 'three', 'cuatro' => 'four']
值和键可以是任何类型,且不需要是唯一的。
过滤
filter
根据标准删除元素。
回调函数是必需的,应返回布尔值。
Pipeline::with([3, 2, 2, 3, 7, 3, 6, 5]) ->filter(function(int $i): bool { return $i % 2 === 0; // is even }) ->toArray(); // [1 => 2, 2 => 2, 6 => 6]
回调函数的第二个参数是键。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red', 'apricot' => 'orange']) ->filter(function(string $value, string $key): bool { return $key[0] === 'a'; }) ->toArray(); // ['apple' => 'green', 'apricot' => 'orange']
cleanup()
从可迭代对象中过滤掉 null
值或 null
键。
Pipeline::with(['one', 'two', null, 'four', null]) ->cleanup() ->toArray(); // [0 => 'one', 1 => 'two', 3 => 'four']
对于迭代器,键可以是任何类型。具有 null
键的元素也会被过滤掉。
unique
过滤唯一元素。
Pipeline::with(['foo', 'bar', 'qux', 'foo', 'zoo']) ->unique() ->toArray(); // [0 => 'foo', 1 => 'bar', 2 => qux, 4 => 'zoo']
您可以传递一个回调,该回调应返回一个值。基于该值进行过滤将基于该值。
$persons = [ new Person("Max", 18), new Person("Peter", 23), new Person("Pamela", 23) ]; Pipeline::with($persons) ->unique(function(Person $value): int { return $value->age; }) ->toArray(); // [0 => Person {'name' => "Max", 'age' => 18}, 1 => Person {'name' => "Peter", 'age' => 23}]
所有值都存储以供参考。回调函数还可以用于序列化和散列值。
Pipeline::with($persons) ->unique(function(Person $value): int { return hash('sha256', serialize($value)); }); });
第二个参数是键。
Pipeline::with(['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red', 'apricot' => 'orange']) ->unique(function(string $value, string $key): string { return $key[0]; }) ->toArray(); // ['apple' => 'green', 'berry' => 'blue', 'cherry' => 'red']
使用严格比较(===
),因此 '10' 和 10 不会匹配。
uniqueKeys
迭代器的键不必是唯一的(也不必是标量)。这与关联数组不同。
uniqueKeys()
方法过滤掉重复的键。
$someGenerator = function($max) { for ($i = 0; $i < $max; $i++) { $key = substr(md5((string)$i), 0, 1); // char [0-9a-f] yield $key => $i; } }; Pipeline::with($someGenerator(1000)) ->uniqueKeys() ->toArray(); // ['c' => 0, 'e' => 3, 'a' => 4, 1 => 6, 8 => 7, 4 => 9, 'd' => 10, 6 => 11 9 => 15 7 => 17, // 3 => 21, 'b' => 22, 0 => 27, 'f' => 44, 2 => 51, 5 => 91]
limit
获取迭代器的第一个元素。
Pipeline::with([3, 2, 2, 3, 7, 3, 6, 5]) ->limit(3) ->toArray(); // [3, 2, 2]
slice
使用偏移量获取元素的一个有限子集。
Pipeline::with([3, 2, 2, 3, 7, 3, 6, 5]) ->slice(3) ->toArray(); // [3, 7, 3, 6, 5]
您也可以指定一个限制。
Pipeline::with([3, 2, 2, 3, 7, 3, 6, 5]) ->slice(3, 2) ->toArray(); // [3, 7]
before
找到匹配项之前获取元素。
Pipeline::with(['apple' => 'green', 'berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']) ->before(function($value, $key) { return $value === 'red'; }) ->toArray(); // ['apple' => 'green']
第二个参数是键。
Pipeline::with(['apple' => 'green', 'berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']) ->before(function($value, $key) { return $key === 'berry'; }) ->toArray(); // ['apple' => 'green']
可选地,匹配的值可以包含在结果中。
Pipeline::with(['apple' => 'green', 'berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']) ->before(function($value) { return $value === 'red'; }) ->toArray(); // ['apple' => 'green', 'berry' => 'red']
after
找到匹配项之后获取元素。
Pipeline::with(['apple' => 'green', 'berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']) ->before(function($value, $key) { return $value === 'red'; }) ->toArray(); // ['cherry' => 'red', 'apricot' => 'orange']
第二个参数是键。
Pipeline::with(['apple' => 'green', 'berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']) ->before(function($value, $key) { return $key === 'berry'; }) ->toArray(); // ['cherry' => 'red', 'apricot' => 'orange']
可选地,匹配的值可以包含在结果中。
Pipeline::with(['apple' => 'green', 'berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']) ->before(function($value) { return $value === 'red'; }) ->toArray(); // ['berry' => 'red', 'cherry' => 'red', 'apricot' => 'orange']
排序
排序需要遍历迭代器以索引所有元素。
sort
创建一个具有排序元素的迭代器。
Pipeline::with(["Charlie", "Echo", "Bravo", "Delta", "Foxtrot", "Alpha"]) ->sort() ->toArray(); // ["Alpha", "Beta", "Charlie", "Delta", "Echo", "Foxtrot"]
除了使用默认排序外,还可以传递回调作为用户定义的比较函数。
Pipeline::with(["Charlie", "Echo", "Bravo", "Delta", "Foxtrot", "Alpha"]) ->sort(function($a, $b): int { return strlen($a) <=> strlen($b) ?: $a <=> $b; }) ->toArray(); // ["Echo", "Alpha", "Bravo", "Delta", "Charlie", "Foxtrot"]
如果 str1 小于 str2,则回调必须返回 < 0;如果 str1 大于 str2,则返回 > 0;如果它们相等,则返回 0。
sortKeys
创建一个按键排序元素的迭代器。
Pipeline::with(["Charlie" => "three", "Bravo" => "two", "Delta" => "four", "Alpha" => "one"]) ->sortKeys() ->toArray(); // ["Alpha" => "one", "Bravo" => "two", "Charlie" => "three", "Delta" => "four"]
可以传递回调作为用户定义的比较函数。
Pipeline::with(["Charlie" => "three", "Bravo" => "two", "Delta" => "four", "Alpha" => "one"]) ->sortKeys(function($a, $b): int { return strlen($a) <=> strlen($b) ?: $a <=> $b; }) ->toArray(); // ["Alpha" => "one", "Bravo" => "two", "Delta" => "four", "Charlie" => "three"]
reverse
创建一个顺序相反的元素迭代器。键被保留。
Pipeline::with(range(5, 10)) ->reverse() ->toArray(); // [5 => 10, 4 => 9, 3 => 8, 2 => 7, 1 => 6, 0 => 5]
类型处理
typeCheck
使用 type_check
验证一个值是否具有特定类型。如果可迭代的任何元素与类型不匹配,将抛出 TypeError
。
Pipeline::with($values) ->typeCheck(['int', 'float']) ->toArray();
类型可以是任何PHP类型,如伪类型 iterable
或 callable
,类名或资源类型。对于资源,请使用资源类型,加上 "resource",例如 "stream resource"
。
作为第二个参数,可以传递一个 Throwable
对象,这可以是 Exception
或 Error
。
错误消息可以包含最多三个 sprintf 占位符。第一个 %s
用值的类型替换。第二个用于键的描述。第三个通常不需要,但指定时用给定的类型(s)替换。
Pipeline::with($values) ->expectType('int', new \UnexpectedValue('Element %2$s should be an integer, %1$s given')) ->toArray();
可以向类添加一个问号以接受 null,例如 "?string"
与使用 ["string", "null"]
相似。
typeCast
将值强制转换为特定类型。此方法使用 type_cast
。
如果值不能转换,将抛出 TypeError
。类似于 typeCheck()
,第二个参数可以传递一个带消息的 Throwable
。
与 typeCheck
不同,只能指定一个类型。可以向类添加一个问号以接受 null,例如 ?string
将尝试将所有内容转换为字符串,除了 null
。
查找
这些方法通过遍历迭代器并返回单个元素。
first
获取第一个元素。
Pipeline::with(["one", "two", "three"]) ->first(); // "one"
可选地,如果可迭代为空,可以抛出 RangeException
。
last
获取最后一个元素。
Pipeline::with(["one", "two", "three"]) ->last(); // "three"
find
查找符合条件的第一元素。如果没有找到元素,则返回 null
。
Pipeline::with(["one", "two", "three"]) ->find(function(string $value): bool { return substr($value, 0, 1) === 't'; }); // "two"
可以在回调中使用键。
Pipeline::with(["one" => "uno", "two" => "dos", "three" => "tres"]) ->find(function(string $value, string $key): bool { return substr($key, 0, 1) === 't'; }); // "dos"
findKey
查找符合条件的第一元素并返回键(而不是值)。如果没有找到元素,则返回 null
。
Pipeline::with(["I" => "one", "II" => "two", "III" => "three"]) ->find(function(string $value): bool { return substr($value, 0, 1) === 't'; }); // "II"
可以在回调中使用键。
Pipeline::with(["one" => "uno", "two" => "dos", "three" => "tres"]) ->find(function(string $value, string $key): bool { return substr($key, 0, 1) === 't'; }); // "two"
hasAny
检查是否有任何元素符合给定的条件。
Pipeline::with(["one", "two", "three"]) ->hasAny(function(string $value): bool { return substr($value, 0, 1) === 't'; }); // true
回调类似于 find
。
hasAll
检查所有元素是否都符合给定的条件。
Pipeline::with(["one", "two", "three"]) ->hasAny(function(string $value): bool { return substr($value, 0, 1) === 't'; }); // false
回调类似于 find
。
hasNone
检查没有元素符合给定的条件。这是 hasAny()
的逆操作。
Pipeline::with(["one", "two", "three"]) ->hasNone(function(string $value): bool { return substr($value, 0, 1) === 't'; }); // false
回调类似于 find
。
min
根据给定的比较器返回最小元素。
Pipeline::with([99.7, 24, -7.2, -337, 122.0])) ->min(); // -337
可以传递一个可调用对象以用于自定义比较逻辑。
Pipeline::with([99.7, 24, -7.2, -337, 122.0]) ->min(function($a, $b) { return abs($a) <=> abs($b); }); // -7.2
max
根据给定的比较器返回最大元素。
Pipeline::with([99.7, 24, -7.2, -337, 122.0])) ->max(); // 122.0
可以传递一个可调用对象以用于自定义比较逻辑。
Pipeline::with([99.7, 24, -7.2, -337, 122.0]) ->max(function($a, $b) { return abs($a) <=> abs($b); }); // -337
聚合
遍历所有元素并将其归约到单个值。
count
返回元素的数量。
Pipeline::with([2, 8, 4, 12])) ->count(); // 4
reduce
使用回调将所有元素归约到单个值。
Pipeline::with([2, 3, 4]) ->reduce(function(int $product, int $value): int { return $product * $value; }, 1); // 24
第三个参数是键
Pipeline::with(['I' => 'one, 'II' => 'two', 'III' => 'three']) ->reduce(function(string $list, string $value, string $key): string { return $list . sprintf("{%s:%s}", $key, $value); }, ''); // "{I:one}{II:two}{III:three}"
sum
计算数字的总和。如果没有元素存在,结果为 0。
Pipeline::with([2, 8, 4, 12]) ->sum(); // 26
average
计算算术平均值。如果没有元素存在,结果为 NAN
。
Pipeline::with([2, 8, 4, 12])) ->average(); // 6.5
concat
将输入元素连接起来,按照指定的分隔符分隔,按遇到顺序。
这类似于在常规数组上使用 implode。
Pipeline::with(["hello", "sweet", "world"]) ->concat(" - "); // "hello - sweet - world"
stub
stub()
方法是一个占位步骤,它不执行任何操作,但稍后可以用 unstub()
替换。
PipelineBuilder stub(string name)
PipelineBuilder unstub(string name, callable $callable, mixed ...$args)
这些方法仅在管道构建器中存在。
$blueprint = Pipeline::build() ->expectType('string') ->stub('process'); ->sort(); // Later $pipeline = $blueprint ->unstub('process', i\iterable_map, i\function_partial(i\string_convert_case, ___, i\STRING_UPPERCASE)));
迭代器是什么?
迭代器是可遍历的对象。这意味着当你在foreach
循环中使用它们时,你不是在遍历对象的属性。相反,每次通过循环时都会调用current()
、key()
和valid()
方法。
current()
方法返回当前值,key()
方法返回当前键,valid()
方法检查我们是否应该继续循环。
在下面的示例中,我们扩展了IteratorIterator
来覆盖current()
类。
use Improved as i; class UpperIterator extends IteratorIterator { public function current() { return i\string_case_convert(parent::current(), i\STRING_UPPERCASE); } }; class NoSpaceIterator extends IteratorIterator { public function current() { return i\string_replace(parent::current(), " ", ""); } }; $data = get_some_data(); $iterator = new NoSpaceIterator(new UpperIterator(new ArrayIterator($data)));
此时没有任何代码执行。 neither string_case_convert
nor string_replace
。只有当我们循环时,这些函数才会被调用。
foreach ($iterator as $cleanValue) { echo $cleanValue; }
这将与执行以下操作相同
foreach ($data as $value) { $upperValue = i\string_case_convert($value, i\STRING_UPPERCASE); $cleanValue = i\string_replace($upper, " ", ""); echo $cleanValue; }
与数组的区别
当处理数组时,我们倾向于对每个操作进行循环。看看array_map
$upperData = array_map(function($value) { return i\string_case_convert($value, i\STRING_UPPERCASE); }, $data); $cleanData = array_map(function($value) { return i\string_replace($value, i\STRING_UPPERCASE); }, $upperData); foreach ($cleanData as $cleanValue) { echo $cleanValue; }
它类似于
$upperData = []; $cleanData = []; foreach ($data as $value) { $upperData[] = i\string_case_convert($value, i\STRING_UPPERCASE); } foreach ($upperData as $upperValue) { $cleanData[] = i\string_replace($upperValue, i\STRING_UPPERCASE); } foreach ($cleanData as $cleanValue) { echo $cleanValue; }
当然,我们可以组合这些运算符并在简单循环中应用它们,而不使用迭代器。然而,这将所有逻辑耦合在一起。如果一个方法返回所有大写值,另一个与它无关的方法(在另一个类中)可能删除空格。对于迭代器,这无关紧要。
迭代器键
对于迭代器,键不需要是字符串或整数,可以是任何类型,也不需要是唯一的。
将键作为数组或对象并保持值为一个标量非常方便。这样,你可以执行链接操作,如大小写转换等。另一个应用是将子对象按父对象分组。
生成器
生成器是PHP在您使用yield
语法时自动创建的特殊迭代器。
function iterable_first_word(iterable $values): Generator { foreach ($values as $key => $value) { $word = i\string_before($value, " "); yield $key => $word; } }
PHP 7.2+对生成器的优化程度很高,提高了性能并节省了内存。这使得生成器比自定义迭代器更可取,因为自定义迭代器可能较慢。
意外的生成器行为
如果您添加了return
语句,函数仍然会返回一个生成器对象。您可以使用Generator->getReturn()
方法获取该结果,但这通常不是预期的。
function get_values(iterable $values) { if (is_array($values)) { return array_values($values); } foreach ($values as $value) { yield $value; } }
以下代码不会按预期工作。它不会返回一个数组,而总是返回一个Generator
对象。
请注意,在get_values
函数中的任何代码都不会执行,直到我们开始循环
function iterable_first_word(iterable $values): Generator { var_dump($values); foreach ($values as $key => $value) { yield $key => i\string_before($value, " "); } } $words = iterable_first_word($values); // Nothing is outputted yet foreach ($words as $word) { // Now we get the var_dump() as the function is executed till yield // ... }
单向迭代器
一些迭代器(包括生成器)是单向迭代器,这意味着您只能遍历它们一次。
function numbers_to($count) { for ($i = 1; $i <= $count; $i++) { yield $i; } } $oneToTen = numbers_to(10); foreach ($oneToTen as $number) { echo $number; } // The following loop will cause an error to be thrown. foreach ($oneToTen as $number) { foo($number); }
这在使用iterable_
函数和Pipeline
对象时会产生影响。虽然可以使用PipelineBuilder
来克服这个问题。
现在你知道了 :-)