iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 15
1
Modern Web

Laravel 6.0 初體驗!怎麼用最新的 laravel 架網站!系列 第 15

[Day 15] 終於講到 6.0 的改變了!來看 Laravel LazyCollection

有了資料庫,資料之間也有了關聯,還透過 seeder 產生了大筆的資料。相信有的讀者可能會好奇,不知道什麼都得透過 PHP 物件來存取資料庫內容的話,會不會有空間或者時間效率上的問題?

這個問題,正好可以用 Laravel 6.0 的一個新特性:LazyCollection 來回答!

什麼是 LazyCollection

什麼是 Collection

如果我們用 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 的問題

Collection 有什麼問題,為什麼需要多建立一個 `LazyCollection 類別呢?

藉著昨天的例子,我們來試想一下,假設我們今天希望取得所有的文章,應該怎麼寫。

相信各位讀者的寫法,差不多會是這樣

Route::get('/test', function(){
    return Post::all();
});

這樣實際運作看看,效果也還不差。不過相信可以預期,當未來資料量越來越大時,這些資料就會全部裝在某個變數裡面。這樣想必很吃記憶體。

為了解決這個問題,Laravel 引進了新的東西,也就是前面所說的 LazyCollection

php yield 和 generator

要解釋什麼是 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

說了這麼多,我們該怎麼使用 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。並且為了說明這個的特點,我們解釋了什麼是 yieldGenerator

今天所講的東西有一點難度,希望大家吸收起來沒有太多的困難,我們明天見!


上一篇
[Day 14] 在開發時加入資料!聊 database seeder
下一篇
[Day 16] 網站要有會員了!用 Laravel 實作用戶登入
系列文
Laravel 6.0 初體驗!怎麼用最新的 laravel 架網站!30

尚未有邦友留言

立即登入留言