Laravel-Collection

思考并回答以下问题:

  • 集合是“macroable”的,怎么理解?
  • new static是什么意思?
  • get_class()函数是什么意思?

简介

Illuminate\Support\Collection类为处理数组数据提供了流式、方便的封装。例如,查看下面的代码,我们使用辅助函数collect创建一个新的集合实例,为每一个元素运行strtoupper函数,然后移除所有空元素:

1
2
3
4
5
$collection = collect(['taylor', 'abigail', null])->map(function ($name) {
return strtoupper($name);
})->reject(function ($name) {
return empty($name);
});

正如你所看到的,Collection类允许你使用方法链对底层数组执行匹配和移除操作,通常,每个Collection方法都会返回一个新的Collection实例。

创建集合

正如上面所提到的,辅助函数collect为给定数组返回一个新的Illuminate\Support\Collection实例,所以,创建集合很简单:

1
$collection = collect([1, 2, 3]);

注:默认情况下,Eloquent查询的结果总是返回Collection实例。

扩展集合

集合是“macroable”的,这意味着我们可以在运行时动态添加方法到Collection类,例如,下面的代码添加了toUpper方法到Collection类:

1
2
3
4
5
6
7
8
9
10
11
12
13
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']

通常,我们需要在服务提供者中声明集合宏。

懒集合

简介

为了继续完善功能已经很强大的Collection类,LazyCollection类使用了PHP的生成器,从而可以通过极低的内存处理极大的数据集。

例如,假设你的应用需要通过Laravel提供的集合方法来解析并处理几个GB大小的日志文件,这个时候就可以使用懒集合(LazyCollection),它不会一次性将整个文件读入内存,而是每次只读取文件的一小部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use App\LogEntry;
use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');

while (($line = fgets($handle)) !== false) {
yield $line;
}
})->chunk(4)->map(function ($lines) {
return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
// Process the log entry...
});

或者,假设你需要迭代10000个Eloquent模型实例,使用传统的Laravel集合,所有10000个Eloquent模型实例必须一次性加载到内存中:

1
2
3
$users = App\User::all()->filter(function ($user) {
return $user->id > 500;
});

而现在,查询构建器的cursor方法会返回一个LazyCollection实例,这样一来,我们仍然只需对数据库做一次查询,但是一次只会加载一个Eloquent模型实例到内存。在这个例子中,filter回调只有在迭代到每个独立用户时才会执行,从而大幅降低对内存的占用:

1
2
3
4
5
6
7
$users = App\User::cursor()->filter(function ($user) {
return $user->id > 500;
});

foreach ($users as $user) {
echo $user->id;
}

创建懒集合

要创建一个懒集合实例,需要传递一个PHP生成器函数到集合的make方法:

1
2
3
4
5
6
7
8
9
use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');

while (($line = fgets($handle)) !== false) {
yield $line;
}
});

new static

无论是new static()还是new self()都是new一个对象,这两个方法new出来的对象有什么区别呢?说白了就是new出来的到底是同一个类的实例还是不同类的实例。

为了探究上面的问题,我们先上一段简单的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Father
{
public function getNewFather()
{
return new self();
}

public function getNewCaller()
{
return new static();
}
}

$f = new Father();

var_dump(get_class($f->getNewFather())); // Father
var_dump(get_class($f->getNewCaller())); // Father

这里无论是getNewFather还是getNewCaller都是返回的Father这个实例,到这里貌似new self()还是new static()是没有区别的,我们接着走。

1
2
3
4
5
6
7
8
9
class Sun1 extends Father
{

}

$sun1 = new Sun1();

var_dump($sun1->getNewFather()); // object(Father)#4 (0) { }
var_dump($sun1->getNewCaller()); // object(Sun1)#4 (0) { }

这里我们发现了getNewFather返回的是Father的实例,而getNewCaller返回的是调用者的实例。

他们的区别只有在继承中才能体现出来、如果没有任何继承、那么二者没有任何区别。

然后new self()返回的实例是不会变的,无论谁去调用,都返回的一个类的实例,而new static则是由调用者决定的。

LazyCollection

Illuminate\Support\LazyCollection.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
namespace Illuminate\Support;

use ArrayIterator;
use Closure;
use Illuminate\Support\Traits\EnumeratesValues;
use Illuminate\Support\Traits\Macroable;
use IteratorAggregate;
use stdClass;

class LazyCollection implements Enumerable
{
use EnumeratesValues, Macroable;

/**
* The source from which to generate items.
*
* @var callable|static
*/
public $source;

/**
* Create a new lazy collection instance.
*
* @param mixed $source
* @return void
*/
public function __construct($source = null)
{
if ($source instanceof Closure || $source instanceof self) {
$this->source = $source;
} elseif (is_null($source)) {
$this->source = static::empty();
} else {
$this->source = $this->getArrayableItems($source);
}
}

/**
* Create a new instance with no items.
*
* @return static
*/
public static function empty()
{
return new static([]);
}

/**
* Create a new instance by invoking the callback a given amount of times.
*
* @param int $number
* @param callable|null $callback
* @return static
*/
public static function times($number, callable $callback = null)
{
if ($number < 1) {
return new static;
}

$instance = new static(function () use ($number) {
for ($current = 1; $current <= $number; $current++) {
yield $current;
}
});

return is_null($callback) ? $instance : $instance->map($callback);
}

/**
* Create an enumerable with the given range.
*
* @param int $from
* @param int $to
* @return static
*/
public static function range($from, $to)
{
return new static(function () use ($from, $to) {
for (; $from <= $to; $from++) {
yield $from;
}
});
}

/**
* Get all items in the enumerable.
*
* @return array
*/
public function all()
{
if (is_array($this->source)) {
return $this->source;
}

return iterator_to_array($this->getIterator());
}

/**
* Eager load all items into a new lazy collection backed by an array.
*
* @return static
*/
public function eager()
{
return new static($this->all());
}
}
0%