Laravel Collections
循环语句
在对数组 items 进行操作时,我们避免不了使用循环语句去处理我们的逻辑。
如,我们想拿到所有用户的邮箱地址,也许我们这么写:
function getUserEmails($users) {
// 1. 创建空数组用于保存结果
$emails = [];
// 2. 初始化变量 $i,用于遍历所有用户
for ($i = 0; $i < count($users); $i++) {
$emails[] = $$users[$i]->email;
}
return $emails;
}
又如,我们要对数组每个元素 *3 计算:
function cheng3($data) {
for ($i = 0; $i < count($data); $i++) {
$data[$i] *= 3;
}
}
又如,我们要把贵的商品挑出来:
function expensive($products) {
$expensiveProducts = [];
foreach ($products as $product) {
if ($product->price > 100) {
$expensiveProducts[] = $product;
}
} return $expensiveProducts;
}
对数组的操作,这类例子太多了,终究都是通过循环来对数组的每个元素进行操作。
而我们重构的思路就是:把循环的地方封装起来,这样最大的避免我们在写业务逻辑时,自己去写循环语句
第一个例子,主要的业务逻辑在于这条语句,获取每个用户的邮箱:$emails[] = $$users[$i]->email;
将其他代码封装成如下 map 函数:
function map($items, $func) {
$results = [];
foreach ($items as $item) {
$results[] = $func($item);
} return $results;
}
这样使用该 map 函数进行重构就简单:
function getUserEmails($users) {
return $this->map($users, function ($user) {
return $user->email;
});
}
相比较刚开始的写法,明显简单多了,而且也避免了不必要的变量。一样的,对第二个例子进行重构,将循环语句封装成 each 函数:
function each($items, $func) {
foreach ($items as $item) {
$func($item);
}
}
这个 each 和 map 函数最大的区别在于,each 函数是对每个元素的处理逻辑,且没有返回新的数组。
使用 each 函数就比较简单:
function cube($data) {
$this->each($data, function ($item) {
return $item * 3;
});
}
同样的对第三个例子进行重构,重构的对象在于价格的筛选判断上
if ($product->price > 100) {
$expensiveProducts[] = $product;
}
我们参考 map 函数进行重构:
function filter($items, $func) {
$result = [];
foreach ($items as $item) {
if ($func($item)) {
$result[] = $item;
}
} return $result;
}
当满足于 $func($item)条件的 item 都放入 $result 数组中。
使用就很简单:
return $this->filter($products, function ($product) {
return $product->price > 100;
});
这里的 filter 函数和 map 函数的区别在于,map 函数是获取原有数组对应的属性集或者计算产生的新数组;而 filter 更多的是通过筛选符合条件的 item,构成的数组。
构造 Collection 类
我们把这些 map、each、filter 方法整合在一起构成一个 Collection 类
class Collection {
protected $items;
public function __construct($items) {
$this->items = $items;
}
function map($items, $func) {
$results = [];
foreach ($items as $item) {
$results[] = $func($item);
}
return $results;
}
function each($items, $func) {
foreach ($items as $item) {
$func($item);
}
}
function filter($items, $func) {
$result = [];
foreach ($items as $item) {
if ($func($item)) {
$result[] = $item;
}
}
return $result;
}
public function toArray() {
return $this->items;
}
}
当然到目前为止,自己封装的 Collection 雏形就已经有了,但还是达不到可以通用的水平。所以我们需要看看别人是怎么写的—— Laravel 使用的
Laravel Collections
Illuminate\Support\Collection
主要实现了以下几个接口:
- ArrayAccess
- Countable
- IteratorAggregate
- JsonSerializable and Laravel’s own Arrayable and Jsonable
ArrayAccess
这个接口更多的职责是让 Collection 类看起来像是个 array,主要是对 items 进行增删查和判断 item 是否存在。
如果你的类实现了 ArrayAccess 接口,那么这个类的对象就可以使用 $foo ['xxx'] 这种结构了。
-
$foo ['xxx'] 对应调用 offsetGet 方法。
-
$foo ['xxx'] = 'yyy' 对应调用 offsetSet 方法。
-
isset ($foo ['xxx']) 对应调用 offsetExists 方法。
-
unset ($foo ['xxx']) 对应调用 offsetUnset 方法。
再次强调,方法的实现代码,你想怎么写就怎么写
interface ArrayAccess {
public function offsetExists($offset);
public function offsetGet($offset);
public function offsetSet($offset, $value);
public function offsetUnset($offset);
}
Countable
count() 这个方法使用率很高,而且在 PHP 中,arrays 没有具体实现该接口,我们基本没看到类似这样的 array->count()
。
interface Countable {
/**
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* @since 5.1.0
*/
public function count();
}
具体实现:
/**
* Count the number of items in the collection.
*
* @return int
*/
public function count()
{
return count($this->items);
}
IteratorAggregate
「聚合式迭代器」接口
/**
* Interface to create an external Iterator.
* @link http://php.net/manual/en/class.iteratoraggregate.php
*/
interface IteratorAggregate extends Traversable {
/**
* Retrieve an external iterator
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator();
}
实现也简单,只是实例化 ArrayIterator:
/**
* Get an iterator for the items.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->items);
}
Arrayable
interface Arrayable{
/**
* Get the instance as an array.
*
* @return array
*/
public function toArray();
}
具体实现,数组输出:
array_map — 为数组的每个元素应用回调函数
/**
* Get the collection of items as a plain array.
*
* @return array
*/
public function toArray()
{
return array_map(function ($value) {
return $value instanceof Arrayable ?
$value->toArray() : $value;
}, $this->items);
}
Jsonable + JsonSerializable
interface Jsonable{
/**
* Convert the object to its JSON representation.
*
* @param int $options
* @return string
*/
public function toJson($options = 0);
}
具体实现,转成 JSON 格式,这方法比较常规使用:
/**
* Convert the object into something JSON serializable.
*
* @return array
*/
public function jsonSerialize()
{
return array_map(function ($value) {
if ($value instanceof JsonSerializable) {
return $value->jsonSerialize();
} elseif ($value instanceof Jsonable) {
return json_decode($value->toJson(), true);
} elseif ($value instanceof Arrayable) {
return $value->toArray();
}
return $value;
}, $this->items);
} /**
* Get the collection of items as JSON.
*
* @param int $options
* @return string
*/
public function toJson($options = 0)
{
return json_encode($this->jsonSerialize(), $options);
}
当然,如果这些常规方法还满足不了你,你也可以对 Collection 类使用 Collection::macro 方法进行扩展:
use Illuminate\Support\Str;
Collection::macro('toUpper', function () {
return $this->map(function ($value) {
return Str::upper($value);
});
});
$collection = collect(['first', 'second']);
$upper = $collection->toUpper();
// ['FIRST', 'SECOND']
本作品采用《CC 协议》,转载必须注明作者和本文链接