集合类提供了一组用于操作数组或 Traversable
对象的工具。如果您以前使用过 underscore.js,那么您对集合类可以做什么会有所了解。
集合实例是不可变的;修改集合将生成一个新的集合。这使得使用集合对象更具可预测性,因为操作是无副作用的。
可以使用数组或 Traversable
对象创建集合。在与 CakePHP 中的 ORM 交互时,您也会与集合交互。集合的简单用法如下
use Cake\Collection\Collection;
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
// Create a new collection containing elements
// with a value greater than one.
$overOne = $collection->filter(function ($value, $key, $iterator) {
return $value > 1;
});
您也可以使用 collection()
帮助函数而不是 new Collection()
$items = ['a' => 1, 'b' => 2, 'c' => 3];
// These both make a Collection instance.
$collectionA = new Collection($items);
$collectionB = collection($items);
帮助函数的优点是,与 (new Collection($items))
相比,它更容易进行链式操作。
Cake\Collection\CollectionTrait
允许您将集合类功能集成到应用程序中任何 Traversable
对象中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
可以使用 each()
和 map()
方法迭代集合或将其转换为新集合。 each()
方法不会创建新集合,但允许您修改集合中的任何对象。
$collection = new Collection($items);
$collection = $collection->each(function ($value, $key) {
echo "Element $key: $value";
});
each()
的返回值将是集合对象。Each 将立即迭代集合,将回调应用于集合中的每个值。
map()
方法将基于回调应用于原始集合中每个对象的输出,创建一个新集合。
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
$new = $collection->map(function ($value, $key) {
return $value * 2;
});
// $result contains [2, 4, 6];
$result = $new->toList();
// $result contains ['a' => 2, 'b' => 4, 'c' => 6];
$result = $new->toArray();
map()
方法将创建一个新的迭代器,该迭代器在迭代时懒惰地创建结果项。
map()
函数最常见的用途之一是从集合中提取单个列。如果您要构建一个包含特定属性值的元素列表,可以使用 extract()
方法。
$collection = new Collection($people);
$names = $collection->extract('name');
// $result contains ['mark', 'jose', 'barbara'];
$result = $names->toList();
与集合类中的许多其他函数一样,您可以使用点分隔路径来指定提取列。
$collection = new Collection($articles);
$names = $collection->extract('author.name');
// $result contains ['Maria', 'Stacy', 'Larry'];
$result = $names->toList();
最后,如果您要查找的属性无法表示为路径,您可以使用回调函数来返回它。
$collection = new Collection($articles);
$names = $collection->extract(function ($article) {
return $article->author->name . ', ' . $article->author->last_name;
});
通常,您需要提取的属性是在深层嵌套在其他结构中的多个数组或对象中存在的公共键。对于这些情况,您可以在路径键中使用 {*}
匹配器。此匹配器在匹配 HasMany 和 BelongsToMany 关联数据时通常很有用。
$data = [
[
'name' => 'James',
'phone_numbers' => [
['number' => 'number-1'],
['number' => 'number-2'],
['number' => 'number-3'],
],
],
[
'name' => 'James',
'phone_numbers' => [
['number' => 'number-4'],
['number' => 'number-5'],
],
],
];
$numbers = (new Collection($data))->extract('phone_numbers.{*}.number');
$result = $numbers->toList();
// $result contains ['number-1', 'number-2', 'number-3', 'number-4', 'number-5']
最后一个示例使用 toList()
,这与其他示例不同,这在我们获取可能包含重复键的结果时很重要。通过使用 toList()
,即使存在重复键,我们也能保证获取所有值。
与 Cake\Utility\Hash::extract()
不同,此方法仅支持 {*}
通配符。所有其他通配符和属性匹配器都不支持。
集合允许您创建一个新集合,该集合由现有集合中的键和值组成。键和值路径都可以使用点符号路径指定。
$items = [
['id' => 1, 'name' => 'foo', 'parent' => 'a'],
['id' => 2, 'name' => 'bar', 'parent' => 'b'],
['id' => 3, 'name' => 'baz', 'parent' => 'a'],
];
$combined = (new Collection($items))->combine('id', 'name');
$result = $combined->toArray();
// $result contains
[
1 => 'foo',
2 => 'bar',
3 => 'baz',
];
您还可以选择使用 groupPath
,根据路径对结果进行分组。
$combined = (new Collection($items))->combine('id', 'name', 'parent');
$result = $combined->toArray();
// $result contains
[
'a' => [1 => 'foo', 3 => 'baz'],
'b' => [2 => 'bar']
];
最后,您可以使用闭包动态地构建键/值/组路径,例如,当使用实体和日期(由 ORM 转换为 I18n\DateTime
实例)时,您可能希望根据日期对结果进行分组。
$combined = (new Collection($entities))->combine(
'id',
function ($entity) { return $entity; },
function ($entity) { return $entity->date->toDateString(); }
);
$result = $combined->toArray();
// $result contains
[
'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN]
'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN]
]
您可以使用 stopWhen()
方法在任何时候停止迭代。在集合中调用它将创建一个新的集合,如果传递的回调对集合中的一个元素返回 true,则该集合将停止产生结果。
$items = [10, 20, 50, 1, 2];
$collection = new Collection($items);
$new = $collection->stopWhen(function ($value, $key) {
// Stop on the first value bigger than 30
return $value > 30;
});
// $result contains [10, 20];
$result = $new->toList();
有时,集合的内部项将包含具有更多项的数组或迭代器。如果要将内部结构展平以对所有元素进行一次迭代,可以使用 unfold()
方法。它将创建一个新的集合,该集合将产生集合中嵌套的每个元素。
$items = [[1, 2, 3], [4, 5]];
$collection = new Collection($items);
$new = $collection->unfold();
// $result contains [1, 2, 3, 4, 5];
$result = $new->toList();
当将回调传递给 unfold()
时,您可以控制从原始集合中的每个项中展开哪些元素。这对于从分页服务中返回数据很有用。
$pages = [1, 2, 3, 4];
$collection = new Collection($pages);
$items = $collection->unfold(function ($page, $key) {
// An imaginary web service that returns a page of results
return MyService::fetchPage($page)->toList();
});
$allPagesItems = $items->toList();
如果您使用的是 PHP 5.5+,则可以在 unfold()
内部使用 yield
关键字,以根据需要为集合中的每个项返回任意数量的元素。
$oddNumbers = [1, 3, 5, 7];
$collection = new Collection($oddNumbers);
$new = $collection->unfold(function ($oddNumber) {
yield $oddNumber;
yield $oddNumber + 1;
});
// $result contains [1, 2, 3, 4, 5, 6, 7, 8];
$result = $new->toList();
当处理集合中大量项目时,可能需要按批次处理元素而不是逐个处理。要将集合拆分为多个特定大小的数组,可以使用 chunk()
函数
$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
$collection = new Collection($items);
$chunked = $collection->chunk(2);
$chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
chunk
函数在进行批处理时特别有用,例如使用数据库结果
$collection = new Collection($articles);
$collection->map(function ($article) {
// Change a property in the article
$article->property = 'changed';
})
->chunk(20)
->each(function ($batch) {
myBulkSave($batch); // This function will be called for each batch
});
与 chunk()
类似,chunkWithKeys()
允许您将集合切分成更小的批次,但保留键。这在对关联数组进行分块时非常有用
$collection = new Collection([
'a' => 1,
'b' => 2,
'c' => 3,
'd' => [4, 5]
]);
$chunked = $collection->chunkWithKeys(2);
$result = $chunked->toList();
// $result contains
[
['a' => 1, 'b' => 2],
['c' => 3, 'd' => [4, 5]]
]
集合允许您根据回调函数的结果过滤并创建新的集合。您可以使用 filter()
来创建一个包含匹配条件回调的元素的新集合
$collection = new Collection($people);
$ladies = $collection->filter(function ($person, $key) {
return $person->gender === 'female';
});
$guys = $collection->filter(function ($person, $key) {
return $person->gender === 'male';
});
filter()
的逆运算是 reject()
。此方法进行负过滤,删除匹配过滤器函数的元素
$collection = new Collection($people);
$ladies = $collection->reject(function ($person, $key) {
return $person->gender === 'male';
});
您可以使用过滤器函数进行真值测试。要查看集合中的每个元素是否都匹配测试,您可以使用 every()
$collection = new Collection($people);
$allYoungPeople = $collection->every(function ($person) {
return $person->age < 21;
});
您可以使用 some()
方法查看集合中是否包含至少一个匹配过滤器函数的元素
$collection = new Collection($people);
$hasYoungPeople = $collection->some(function ($person) {
return $person->age < 21;
});
如果您需要提取一个新的集合,其中只包含包含一组给定属性的元素,则应使用 match()
方法
$collection = new Collection($comments);
$commentsFromMark = $collection->match(['user.name' => 'Mark']);
属性名称可以是点分隔路径。您可以遍历嵌套实体并匹配它们包含的值。当您只需要从集合中获取第一个匹配的元素时,可以使用 firstMatch()
$collection = new Collection($comments);
$comment = $collection->firstMatch([
'user.name' => 'Mark',
'active' => true
]);
如您从上面看到的,match()
和 firstMatch()
都允许您提供多个匹配条件。此外,条件可以针对不同的路径,允许您表达与之匹配的复杂条件。
map()
操作的对应方通常是 reduce
。此函数将帮助您从集合中的所有元素构建单个结果
$totalPrice = $collection->reduce(function ($accumulated, $orderLine) {
return $accumulated + $orderLine->price;
}, 0);
在上面的示例中,$totalPrice
将是集合中包含的所有单价的总和。请注意 reduce()
函数的第二个参数接受您要执行的减少操作的初始值
$allTags = $collection->reduce(function ($accumulated, $article) {
return array_merge($accumulated, $article->tags);
}, []);
要根据属性提取集合的最小值,只需使用 min()
函数。这将返回集合中的完整元素,而不仅仅是找到的最小值
$collection = new Collection($people);
$youngest = $collection->min('age');
echo $youngest->name;
您还可以通过提供路径或回调函数来表达要比较的属性
$collection = new Collection($people);
$personYoungestChild = $collection->min(function ($person) {
return $person->child->age;
});
$personWithYoungestDad = $collection->min('dad.age');
同样的方法也适用于 max()
函数,它将返回集合中具有最高属性值的单个元素
$collection = new Collection($people);
$oldest = $collection->max('age');
$personOldestChild = $collection->max(function ($person) {
return $person->child->age;
});
$personWithOldestDad = $collection->max('dad.age');
最后,sumOf()
方法将返回所有元素属性的总和
$collection = new Collection($people);
$sumOfAges = $collection->sumOf('age');
$sumOfChildrenAges = $collection->sumOf(function ($person) {
return $person->child->age;
});
$sumOfDadAges = $collection->sumOf('dad.age');
计算集合中元素的平均值。可以选择提供匹配器路径或函数来提取值以生成平均值
$items = [
['invoice' => ['total' => 100]],
['invoice' => ['total' => 200]],
];
// $average contains 150
$average = (new Collection($items))->avg('invoice.total');
计算一组元素的中位数。可以选择提供匹配器路径或函数来提取值以生成中位数
$items = [
['invoice' => ['total' => 400]],
['invoice' => ['total' => 500]],
['invoice' => ['total' => 100]],
['invoice' => ['total' => 333]],
['invoice' => ['total' => 200]],
];
// $median contains 333
$median = (new Collection($items))->median('invoice.total');
当集合值在属性上具有相同的值时,它们可以在新集合中按不同的键进行分组
$students = [
['name' => 'Mark', 'grade' => 9],
['name' => 'Andrew', 'grade' => 10],
['name' => 'Stacy', 'grade' => 10],
['name' => 'Barbara', 'grade' => 9]
];
$collection = new Collection($students);
$studentsByGrade = $collection->groupBy('grade');
$result = $studentsByGrade->toArray();
// $result contains
[
10 => [
['name' => 'Andrew', 'grade' => 10],
['name' => 'Stacy', 'grade' => 10]
],
9 => [
['name' => 'Mark', 'grade' => 9],
['name' => 'Barbara', 'grade' => 9]
]
]
与往常一样,可以提供嵌套属性的点分隔路径,也可以提供您自己的回调函数来动态生成组
$commentsByUserId = $comments->groupBy('user.id');
$classResults = $students->groupBy(function ($student) {
return $student->grade > 6 ? 'approved' : 'denied';
});
如果您只想了解每个组的出现次数,可以使用 countBy()
方法。它采用与 groupBy
相同的参数,因此您应该已经很熟悉它
$classResults = $students->countBy(function ($student) {
return $student->grade > 6 ? 'approved' : 'denied';
});
// Result could look like this when converted to array:
['approved' => 70, 'denied' => 20]
在某些情况下,您知道元素对于您要按其进行分组的属性是唯一的。如果您希望每个组只有一个结果,可以使用函数 indexBy()
$usersById = $users->indexBy('id');
// When converted to array result could look like
[
1 => 'markstory',
3 => 'jose_zap',
4 => 'jrbasso'
]
与 groupBy()
函数一样,您也可以使用属性路径或回调
$articlesByAuthorId = $articles->indexBy('author.id');
$filesByHash = $files->indexBy(function ($file) {
return md5($file);
});
可以使用 zip()
方法将不同集合的元素组合在一起。它将返回一个新的集合,其中包含一个数组,将每个集合中位于相同位置的元素分组在一起。
$odds = new Collection([1, 3, 5]);
$pairs = new Collection([2, 4, 6]);
$combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]]
您也可以一次压缩多个集合。
$years = new Collection([2013, 2014, 2015, 2016]);
$salaries = [1000, 1500, 2000, 2300];
$increments = [0, 500, 500, 300];
$rows = $years->zip($salaries, $increments);
$result = $rows->toList();
// $result contains
[
[2013, 1000, 0],
[2014, 1500, 500],
[2015, 2000, 500],
[2016, 2300, 300]
]
正如您已经看到的,zip()
方法对于转置多维数组非常有用。
$data = [
2014 => ['jan' => 100, 'feb' => 200],
2015 => ['jan' => 300, 'feb' => 500],
2016 => ['jan' => 400, 'feb' => 600],
];
// Getting jan and feb data together
$firstYear = new Collection(array_shift($data));
$result = $firstYear->zip($data[0], $data[1])->toList();
// Or $firstYear->zip(...$data) in PHP >= 5.6
// $result contains
[
[100, 300, 400],
[200, 500, 600]
]
集合值可以根据列或自定义函数按升序或降序排序。要从另一个集合的值中创建一个新的排序集合,可以使用 sortBy
。
$collection = new Collection($people);
$sorted = $collection->sortBy('age');
如上所示,您可以通过传递集合值中存在的列或属性的名称来进行排序。您也可以使用点符号指定属性路径。以下示例将按作者姓名对文章进行排序。
$collection = new Collection($articles);
$sorted = $collection->sortBy('author.name');
sortBy()
方法足够灵活,可以让你指定一个提取器函数,该函数可以让你动态地选择用于比较集合中两个不同值的 value。
$collection = new Collection($articles);
$sorted = $collection->sortBy(function ($article) {
return $article->author->name . '-' . $article->title;
});
为了指定集合应该按哪个方向排序,您需要提供 SORT_ASC
或 SORT_DESC
作为第二个参数,分别用于按升序或降序排序。默认情况下,集合按降序排序。
$collection = new Collection($people);
$sorted = $collection->sortBy('age', SORT_ASC);
有时您需要指定要比较的数据类型,以便获得一致的结果。为此,您应该在 sortBy()
函数中提供第三个参数,该参数包含以下常量之一:
SORT_NUMERIC:用于比较数字。
SORT_STRING:用于比较字符串值。
SORT_NATURAL:用于排序包含数字的字符串,并且您希望这些数字按自然方式排序。例如:将“10”显示在“2”之后。
SORT_LOCALE_STRING:根据当前区域设置比较字符串。
默认情况下,使用 SORT_NUMERIC
。
$collection = new Collection($articles);
$sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL);
警告
多次迭代排序集合通常很昂贵。如果您打算这样做,请考虑将集合转换为数组,或者 simply 使用 compile()
方法。
并非所有数据都适合以线性方式表示。集合使构建和扁平化分层或嵌套结构变得更加容易。可以使用 nest()
方法创建嵌套结构,其中子项按父标识符属性分组。
此函数需要两个参数。第一个参数是表示项目标识符的属性。第二个参数是表示父项目的标识符的属性的名称。
$collection = new Collection([
['id' => 1, 'parent_id' => null, 'name' => 'Birds'],
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'],
['id' => 6, 'parent_id' => null, 'name' => 'Fish'],
]);
$nested = $collection->nest('id', 'parent_id');
$result = $nested->toList();
// $result contains
[
[
'id' => 1,
'parent_id' => null,
'name' => 'Birds',
'children' => [
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []],
],
],
[
'id' => 6,
'parent_id' => null,
'name' => 'Fish',
'children' => [
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []],
],
],
];
子元素嵌套在集合中每个项目的 children
属性内。这种数据表示形式有助于渲染菜单或遍历树中直至特定级别的元素。
nest()
的反面是 listNested()
。此方法允许您将树结构扁平化为线性结构。它接受两个参数;第一个参数是遍历模式(asc、desc 或 leaves),第二个参数是包含集合中每个元素的子项的属性的名称。
以先前示例中构建的嵌套集合的输入为例,我们可以将其扁平化。
$result = $nested->listNested()->toList();
// $result contains
[
['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]],
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish']
]
默认情况下,树从根遍历到叶子。您也可以指示它只返回树中的叶元素。
$result = $nested->listNested('leaves')->toList();
// $result contains
[
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => [], ],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => [], ],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => [], ],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => [], ],
]
将树转换为嵌套列表后,您可以使用 printer()
方法配置列表输出的格式。
$result = $nested->listNested()->printer('name', 'id', '--')->toArray();
// $result contains
[
1 => 'Birds',
2 => '--Land Birds',
3 => '--Eagle',
4 => '--Seagull',
6 => 'Fish',
5 => '--Clown Fish',
]
printer()
方法还允许您使用回调来生成键或值。
$nested->listNested()->printer(
function ($el) {
return $el->name;
},
function ($el) {
return $el->id;
}
);
允许您查看集合是否包含任何元素。
$collection = new Collection([]);
// Returns true
$collection->isEmpty();
$collection = new Collection([1]);
// Returns false
$collection->isEmpty();
集合允许您快速检查它们是否包含一个特定值:通过使用 contains()
方法。
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
$hasThree = $collection->contains(3);
比较使用 ===
运算符执行。如果您希望执行更宽松的比较类型,可以使用 some()
方法。
有时您可能希望以随机顺序显示一系列值。为了创建一个新的集合,该集合将以随机位置返回每个值,请使用 shuffle
。
$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]);
// This could return [2, 3, 1]
$collection->shuffle()->toList();
转置集合时,会得到一个新的集合,其中包含由原始列中的每一列组成的行。
$items = [
['Products', '2012', '2013', '2014'],
['Product A', '200', '100', '50'],
['Product B', '300', '200', '100'],
['Product C', '400', '300', '200'],
];
$transpose = (new Collection($items))->transpose();
$result = $transpose->toList();
// $result contains
[
['Products', 'Product A', 'Product B', 'Product C'],
['2012', '200', '300', '400'],
['2013', '100', '200', '300'],
['2014', '50', '100', '200'],
]
对集合进行洗牌在进行快速统计分析时通常很有用。执行此类任务时,另一个常见操作是将一些随机值从集合中提取出来,以便对这些值进行更多测试。例如,如果您想选择 5 个随机用户来对其应用一些 A/B 测试,可以使用 sample()
函数。
$collection = new Collection($people);
// Withdraw maximum 20 random users from this collection
$testSubjects = $collection->sample(20);
sample()
最多将接受您在第一个参数中指定的数量的值。如果集合中没有足够的元素来满足样本,则返回以随机顺序排列的完整集合。
无论何时想要获取集合的一部分,请使用 take()
函数,它将创建一个新的集合,其中最多包含您在第一个参数中指定的值的数量,从第二个参数中传递的位置开始。
$topFive = $collection->sortBy('age')->take(5);
// Take 5 people from the collection starting from position 4
$nextTopFive = $collection->sortBy('age')->take(5, 4);
位置从零开始,因此第一个位置号是 0
。
虽然 take()
的第二个参数可以帮助您在从集合中获取元素之前跳过一些元素,但您也可以使用 skip()
用于相同目的,作为获取特定位置之后其余元素的一种方法。
$collection = new Collection([1, 2, 3, 4]);
$allExceptFirstTwo = $collection->skip(2)->toList(); // [3, 4]
take()
的最常见用途之一是获取集合中的第一个元素。用于实现相同目标的快捷方法是使用 first()
方法。
$collection = new Collection([5, 4, 3, 2]);
$collection->first(); // Returns 5
同样,您可以使用 last()
方法获取集合的最后一个元素。
$collection = new Collection([5, 4, 3, 2]);
$collection->last(); // Returns 2
您可以将多个集合组合成一个。这使您可以从各种来源收集数据,将其连接起来,并非常顺畅地对其应用其他集合函数。 append()
方法将返回一个新的集合,其中包含来自两个来源的值。
$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->append($phpTweets);
// Tweets containing `cakefest` from both sources
$myTimeline->filter(function ($tweet) {
return strpos($tweet, 'cakefest');
});
允许您将一个带可选键的项目附加到集合中。如果您指定一个已经在集合中存在的键,则不会覆盖该值。
$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->appendItem($newTweet, 99);
prepend()
方法将返回一个新的集合,该集合包含来自两个来源的值。
$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->prepend($phpTweets);
允许您将一个带有可选键的项目追加到集合中。如果您指定了一个集合中已经存在的键,则该值将不会被覆盖。
$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->prependItem($newTweet, 99);
警告
当从不同的来源追加时,您可能会发现来自两个集合的一些键是相同的。例如,当追加两个简单的数组时。当使用 toArray()
将集合转换为数组时,这可能是一个问题。如果您不希望来自一个集合的值基于它们的键覆盖前一个集合中的其他值,请确保您调用 toList()
以丢弃键并保留所有值。
有时,您可能有两个独立的数据集,您希望将其中一个数据集的元素插入到另一个数据集的每个元素中。当您从不支持数据合并或联接的数据库中获取数据时,这是一个非常常见的用例。
集合提供了 insert()
方法,允许您将一个集合中的每个元素插入到另一个集合的每个元素中的属性中。
$users = [
['username' => 'mark'],
['username' => 'juan'],
['username' => 'jose']
];
$languages = [
['PHP', 'Python', 'Ruby'],
['Bash', 'PHP', 'Javascript'],
['Javascript', 'Prolog']
];
$merged = (new Collection($users))->insert('skills', $languages);
$result = $merged->toArray();
// $result contains
[
['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
['username' => 'jose', 'skills' => ['Javascript', 'Prolog']]
];
insert()
方法的第一个参数是一个用点分隔的属性路径,用于跟踪以便可以将元素插入到该位置。第二个参数是任何可以转换为集合对象的东西。
请注意,元素是按照它们被找到的位置插入的,因此,第二个集合的第一个元素被合并到第一个集合的第一个元素中。
如果第二个集合中的元素不足以插入到第一个集合中,则目标属性将不存在。
$languages = [
['PHP', 'Python', 'Ruby'],
['Bash', 'PHP', 'Javascript']
];
$merged = (new Collection($users))->insert('skills', $languages);
$result = $merged->toArray();
// $result contains
[
['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
['username' => 'jose']
];
insert()
方法可以操作数组元素或实现 ArrayAccess
接口的对象。
当要完成的工作量很小且集中时,使用闭包进行集合方法非常棒,但它很快就会变得很混乱。当需要调用许多不同的方法或闭包方法的长度不仅仅几行时,这一点变得更加明显。
还有一些情况下,集合方法中使用的逻辑可以在应用程序的多个部分中重用。建议您考虑将复杂的集合逻辑提取到单独的类中。例如,假设一个像这样的冗长的闭包
$collection
->map(function ($row, $key) {
if (!empty($row['items'])) {
$row['total'] = collection($row['items'])->sumOf('price');
}
if (!empty($row['total'])) {
$row['tax_amount'] = $row['total'] * 0.25;
}
// More code here...
return $modifiedRow;
});
可以通过创建另一个类来重构它
class TotalOrderCalculator
{
public function __invoke($row, $key)
{
if (!empty($row['items'])) {
$row['total'] = collection($row['items'])->sumOf('price');
}
if (!empty($row['total'])) {
$row['tax_amount'] = $row['total'] * 0.25;
}
// More code here...
return $modifiedRow;
}
}
// Use the logic in your map() call
$collection->map(new TotalOrderCalculator)
有时,一系列集合方法调用可以在应用程序的其他部分中重复使用,但前提是它们以特定的顺序调用。在这些情况下,您可以使用 through()
以及实现 __invoke
的类来分配您的便捷数据处理调用。
$collection
->map(new ShippingCostCalculator)
->map(new TotalOrderCalculator)
->map(new GiftCardPriceReducer)
->buffered()
...
上面的方法调用可以提取到一个新的类中,这样每次都不需要重复
class FinalCheckOutRowProcessor
{
public function __invoke($collection)
{
return $collection
->map(new ShippingCostCalculator)
->map(new TotalOrderCalculator)
->map(new GiftCardPriceReducer)
->buffered()
...
}
}
// Now you can use the through() method to call all methods at once
$collection->through(new FinalCheckOutRowProcessor);
集合通常以惰性的方式执行使用其函数创建的大多数操作。这意味着,即使您可以调用一个函数,也不意味着它会立即执行。对于此类中的大量函数来说,情况都是如此。在您没有使用集合中的所有值的场景中,惰性求值可以节省资源。当迭代过早停止或当过早达到异常/失败情况时,您可能不会使用所有值。
此外,惰性求值有助于加快一些操作。考虑以下示例
$collection = new Collection($oneMillionItems);
$collection = $collection->map(function ($item) {
return $item * 2;
});
$itemsToShow = $collection->take(30);
如果集合不是惰性的,即使我们只想要显示其中的 30 个元素,我们也会执行一百万次操作。相反,我们的映射操作仅应用于我们使用的 30 个元素。当我们对较小的集合执行多个操作时,我们也可以从这种惰性求值中获益。例如:调用 map()
两次,然后调用 filter()
。
惰性求值也有其缺点。如果您过早地优化了集合,您可能会多次执行相同的操作。考虑这个例子
$ages = $collection->extract('age');
$youngerThan30 = $ages->filter(function ($item) {
return $item < 30;
});
$olderThan30 = $ages->filter(function ($item) {
return $item > 30;
});
如果我们迭代 youngerThan30
和 olderThan30
,集合不幸地会执行两次 extract()
操作。这是因为集合是不可变的,并且对这两个过滤器都会执行惰性提取操作。
幸运的是,我们可以用一个函数克服这个问题。如果您计划多次重复使用某些操作的结果,可以使用 buffered()
函数将结果编译到另一个集合中
$ages = $collection->extract('age')->buffered();
$youngerThan30 = ...
$olderThan30 = ...
现在,当两个集合被迭代时,它们只会调用一次提取操作。
buffered()
方法也适用于将不可倒带的迭代器转换为可以迭代多次的集合。
// In PHP 5.5+
public function results()
{
...
foreach ($transientElements as $e) {
yield $e;
}
}
$rewindable = (new Collection(results()))->buffered();
有时您需要获取另一个集合中元素的克隆。当您需要同时从不同地方迭代同一组元素时,这很有用。为了从另一个集合中克隆一个集合,请使用 compile()
方法。
$ages = $collection->extract('age')->compile();
foreach ($ages as $age) {
foreach ($collection as $element) {
echo h($element->name) . ' - ' . $age;
}
}