有了資料庫,資料之間也有了關聯,還透過 seeder 產生了大筆的資料。相信有的讀者可能會好奇,不知道什麼都得透過 PHP 物件來存取資料庫內容的話,會不會有空間或者時間效率上的問題?
這個問題,正好可以用 Laravel 6.0 的一個新特性:LazyCollection
來回答!
如果我們用 get_class()
這個函式來看看 Laravel 取得的物件類別
Route::get('/test', function(){
return get_class(Post::all());
});
我們會看到 Illuminate\Database\Eloquent\Collection
這是個怎樣的物件呢?簡單的說,這是 Laravel 對 PHP 陣列所進行的包裝。多數情況下,你可以把 Collection
當作一般的陣列使用,像是
Route::get('/test', function(){
$posts = Post::all();
foreach($posts as $post){
echo $post->id . '<br />';
}
});
不過,Collection
比起原生的陣列多了很多的行為,使用起來方便很多。
如果透過 Laravel 取出的資料超過一筆,通常都會是以這個物件的形式進行回傳。這也就是為什麼我們透過 all()
函式取出來的資料是以這個類別回傳。
Collection 有什麼問題,為什麼需要多建立一個 `LazyCollection 類別呢?
藉著昨天的例子,我們來試想一下,假設我們今天希望取得所有的文章,應該怎麼寫。
相信各位讀者的寫法,差不多會是這樣
Route::get('/test', function(){
return Post::all();
});
這樣實際運作看看,效果也還不差。不過相信可以預期,當未來資料量越來越大時,這些資料就會全部裝在某個變數裡面。這樣想必很吃記憶體。
為了解決這個問題,Laravel 引進了新的東西,也就是前面所說的 LazyCollection
要解釋什麼是 LazyCollection
,就必須先講一下 PHP 的 yield
語法。
PHP 在處理大量回傳的時候,為了避免前面所說到的回傳值變成一個很佔空間的變數,除了一般的回傳方式以外,現在還可以用 yield
這個方式。
我們實際看一個例子
Route::get('/test', function(){
function getLargeData()
{
$returnData = [];
for ($index = 0; $index <= 10000; $index++) {
$returnData[] = $index;
}
return $returnData;
}
$largeDataRows = getLargeData();
foreach($largeDataRows as $row){
echo $row . "<br />";
}
});
這樣的話,$largeDataRows
就會是包含一萬個物件的陣列。
我們可以換個方式來撰寫我們的 getLargeData()
Route::get('/test', function(){
function getLargeData()
{
for ($index = 0; $index <= 10000; $index++) {
yield $index;
}
}
});
這樣的函式看起來很怪。到底會回傳什麼呢?
我們檢查看看這個回傳的型態
Route::get('/test', function () {
/**
* @return Generator
*/
function getLargeData()
{
for ($index = 0; $index <= 10000; $index++) {
yield $index;
}
}
$largeDataRows = getLargeData();
echo get_class($largeDataRows);
});
我們會看到 Generator
這個字串。這是一個 PHP 原生的物件。這個物件可以被當成陣列一樣使用,但是卻只有在程式重新跑到 yield
時,才回傳一筆資料,不需要立刻取得所有的資料。
這樣,就可以避免我們前面所說的,一次建立大量資料,導致耗費記憶體的問題。
Laravel 的 LazyCollection
就是改用 yield
的方式實作函式,達到避免記憶體消耗的目的
比方說,原本 Collection 的 filter() 這個函式的實作
public function filter(callable $callback = null)
{
if ($callback) {
return new static(Arr::where($this->items, $callback));
}
return new static(array_filter($this->items));
}
就改寫成
public function filter(callable $callback = null)
{
if (is_null($callback)) {
$callback = function ($value) {
return (bool) $value;
};
}
return new static(function () use ($callback) {
foreach ($this as $key => $value) {
if ($callback($value, $key)) {
yield $key => $value;
}
}
});
}
來避免一次對所有元素進行操作。
說了這麼多,我們該怎麼使用 LazyCollection
呢?
回到我們前面的範例
Route::get('/test', function(){
return get_class(Post::all());
});
我們把它改成
Route::get('/test', function(){
return get_class(Post::cursor());
});
改成這樣之後,我們會得到 Illuminate\Support\LazyCollection
這樣就得到 LazyCollection
物件囉!非常簡單吧!
之後的操作基本上與前面沒有差異。我們可以
Route::get('/test', function(){
return Post::cursor();
});
或者
Route::get('/test', function(){
return Post::cursor()->filter(function ($post) {
return $post->id > 500;
});
});
小小總結一下,今天我們特別強調了 Laravel 6.0 的一個賣點:LazyCollection
。並且為了說明這個的特點,我們解釋了什麼是 yield
和 Generator
。
今天所講的東西有一點難度,希望大家吸收起來沒有太多的困難,我們明天見!