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主要实现了以下几个接口:

  1. ArrayAccess
  2. Countable
  3. IteratorAggregate
  4. 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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!