improved/可迭代的

与数组、迭代器和其它可遍历对象交互的函数

v0.1.4 2019-06-06 02:25 UTC

This package is auto-updated.

Last update: 2024-09-12 04:28:04 UTC


README

improved PHP library

可迭代的

PHP Scrutinizer Code Quality Code Coverage Packagist Stable Version Packagist License

函数式风格的操作,如数组上的map-reduce转换,迭代器 和其它 可遍历 对象。

这些函数与它们的 array_* 对应函数不同,因为它们可以与任何类型的可迭代对象一起工作,而不仅仅是数组。如果您不熟悉PHP中的迭代器和生成器,请首先阅读"什么是迭代器?"这一段。

该库支持过程式和面向对象编程范式。

安装

composer require improved/iterable

方法

通用方法

链式方法

映射

过滤

排序

类型处理

其他方法

查找

聚合

构建方法

示例

所有函数和对象都在 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类型,如伪类型 iterablecallable,类名或资源类型。对于资源,请使用资源类型,加上 "resource",例如 "stream resource"

作为第二个参数,可以传递一个 Throwable 对象,这可以是 ExceptionError

错误消息可以包含最多三个 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来克服这个问题。

现在你知道了 :-)