如何使用 Laravel Collections 類編寫神級代碼

本文首發於 如何使用 Laravel Collections 類編寫神級代碼,轉載請註明出處。

Laravel 提供了一些超讚的組件,在我看來,它是目前全部 Web 框架中提供組件支持最好的一個。它不只提供了開箱即用的視圖(views)、身份認證(authentication)、會話(sessions)、緩存(caching)、Eloquent、隊列(queues)、數據校驗(data validation)等組件。甚至還提供了開發工具(Valet 和 Homestead)。php

可是,這個框架功能中最強大的一個特性經常被萌新們視而不見 - Collection(集合) 類。在這篇文章,咱們將探尋如何使用集合提高編碼效率、代碼的易讀行,及編寫出更精簡的編碼。laravel

預覽

最初接觸到使用集合的場景來自於研發人員使用 Eloquent 執行數據庫查詢,並從返回數據中使用 foreach 語句遍歷獲取模型集合。git

不過,初學者可能並無注意到,集合提供了超過 90 個以上的方法來操做底層數據。更妙的是幾乎全部的方法都支持鏈式操做,可以讓你的代碼讀起來就像一篇散文同樣。這樣使得你的代碼更易閱讀,不管是你仍是其餘使用者都是如此。github

尚未進入正題?好吧,讓咱們回顧一個簡單的代碼片斷,來看看咱們如何使用集合編寫粗、快、猛的代碼吧。數據庫

代碼示例

讓咱們構建一個真實的世界。假設咱們查詢某些 API 接口並獲取到以下以數組保存的結果集:express

<?php
// API 請求返回的結果
$data = [
    ['first_name' => 'John', 'last_name' => 'Doe', 'age' => 'twenties'],
    ['first_name' => 'Fred', 'last_name' => 'Ali', 'age' => 'thirties'],
    ['first_name' => 'Alex', 'last_name' => 'Cho', 'age' => 'thirties'],
];

咱們看到數組包含名字(first name)、姓氏(last name) 和年齡(age)範圍。如今,咱們假設從記錄中獲取一名 年齡(age)30 歲(thirties) 的用戶,而後依據 姓氏(last name) 進行 排序(sort)。最後,咱們還但願返回的結果爲 一個字符串(single string),這樣每一個用戶獨佔 一行(new line)。最後,咱們還但願返回的結果爲編程

這個需求看起來不難實現,如今讓咱們看看使用 PHP 如何實現這一功能:數組

// 依據姓氏排序
usort($data, function ($item1, $item2) {
    return $item1['last_name'] <=> $item2['last_name'];
});

// 依據年齡範圍分組
$new_data = [];

foreach ($data as $key => $item) {
    $new_data[$item['age']][$key] = $item;
}

ksort($new_data, SORT_NUMERIC);

// 從年齡爲 30 歲組裏獲取用戶全名
$result = array_map(function($item) {
    return $item['first_name'].' '.$item['last_name'];
}, $new_data['thirties']);

// 將數組轉換爲字符串並以行分隔符分隔
$final = implode("\n", $result);

// 譯註:原文是 $final = implode($results, "\n"); implode函數接收兩種順序的參數,爲了保持與文檔一致因此我這邊作了調整。

咱們的實現代碼超過 20 行,而且很不優雅。移除掉註釋及換行相關代碼,這段代碼會變得難以閱讀。再者,咱們還須要藉助臨時變量以及 PHP 中內置的不友好的 sort 方法。緩存

如今,讓咱們看下藉助 Collection 類實現起來是多麼簡單吧:session

collection($data)->where('age', 'thirties')
                 ->sortBy('last_name')
                 ->map(function($item){
                    return $item['first_name'].' '.$item['last_name'];
                 })
                 ->implode("\n");

哇哦!咱們的代碼從 20 行變成了 6 行。如今的代碼不只順暢很多,而且在方法實現時無需藉助註釋告訴咱們它們在處理什麼問題。

不過,還存在一個問題阻止咱們的代碼不如完美階段... 就是用於比較 first name 和 last name 的 map 方法。坦白說,這真的不是什麼大問題,可是它爲咱們探索 macro(宏) 概念提供了動力。

擴展集合(Extending Collections)

Collection 類,同其它 Laravel 組件同樣,支持宏(macroable),就是說你能夠給它添加方法隨後使用。

提示: 若是你但願新方法隨處可用,你應該將它們添加到服務提供中。我喜歡建立一個 MacroServiceProvider 實先這個功能,對於你來講隨你喜歡就好。

讓咱們添加一個方法它會鏈接由數組提供的任意數量的字段並返回字符串結果:

Collection::macro('toConcatenatedString', function ($fields = [], $separator = ' ') {
    return $this->map(function($item) use ($fields, $separator) {
        return implode($separator, array_map(function ($el) use ($item) {
                return $item[$el];
            }, $fields)
        );
    })->implode("\n");
});

添加完這個方法後,咱們的代碼基本上就完美了:

collect($data)->where('age', 'thirties')
              ->sortBy('last_name')
              ->toConcatenatedString(['first_name', 'last_name']);

咱們的代碼從混亂的 20 多行精簡到了 3 行,代碼乾淨整潔功能清晰任何人均可以立馬理解。

又一個示例

如今讓咱們看下第二個示例,假設咱們一個用戶列表,咱們須要基於角色(role)過濾出來,而後進一步若是他們的註冊時間爲 5 年或以上且 last name 以字母 A-M 開始的僅獲取第一個用戶。

數據相似以下:

<?php
// API 請求返回的結果
$users = [
    ['name' => 'John Doe', 'role' => 'vip', 'years' => 7],
    ['name' => 'Fred Ali', 'role' => 'vip', 'years' => 3],
    ['name' => 'Alex Cho', 'role' => 'user', 'years' => 9],
];

若是咱們使用的是 PHP 實現,咱們的代碼看下來以下:

$subset = [];
foreach ($users as $user) {
    if ($user['role'] === 'vip' && $user['years'] >= 5) {
        if (preg_match('/\s[A-Z]/', $user['name'])) {
            $subset[] = $user;
        }
    }
}
return reset($subset)
注意: 你能夠將第二個 if 語句移至第一個裏面,可是我我的喜歡在單個 if 語句中使用不超過兩個條件語句,由於我認爲超過 2 個條件語句回事代碼難以閱讀。

這段代碼不至於太糟糕,可是咱們依然須要使用臨時變量,咱們還須要使用 reset 函數將指針重置到第一個用戶。咱們的代碼還有四層縮進,這使得代碼解析變得更有挑戰性。

相反,咱們來看看集合是如何處理這個問題的:

collect($users)->where('role', 'vip')
              ->map(function($user) {
                  return preg_match('/\s[A-Z]/', $user['name']);
              })
              ->firstWhere('years', '>=', '5');

咱們將代碼簡化到了以前的通常左右,每一步過濾處理清晰明瞭,而且咱們不須要引入臨時變量。

遺憾的是目前集合還不支持正則匹配,因此咱們使用 map 方法,不過咱們能夠爲這個功能建立一個宏:

Collection::macro('whereRegex', function($expression, $field) {
    return $this->map(function ($item) use ($expression, $field) {
        return preg_match($expression, $item[$field]);
    })
});

得益於宏方法,咱們的代碼如今看起來以下:

collect($users) -> where('role', 'vip')
                -> whereRegex('/\s[A-Z]/', 'name')
                -> firstWhere('years', '>=', 5);
注意: 爲了簡單起見,咱們的紅僅僅適用於數組集合。若是你計劃讓它們能夠在 Eloquent 集合上使用,你須要在此場景下作相應的代碼處理才行。

不一樣的視角

咱們能夠繼續列出無數的示例,但仍然沒法涵蓋全部可用的集合方法,而且這歷來都不是本文的真正目的。

須要注意的是,經過使用 Collection 類,您不只能夠得到一個方法庫來簡化編程工做,還能夠選擇一種從根本上改善代碼的方法。

你會不由自主的將你的代碼結構從代碼塊重構簡化成一行,同時減小代碼的縮進,臨時變量的使用和技巧性方法,另外你還可使用鏈式編程方法,這讓你的代碼更加便於閱讀和解析,此外最重要的是減小了編碼工做!

查看官方文檔獲取更多這個迷人的類庫的使用細節:https://laravel.com/docs/coll...

提示: 你還能夠獲取這個 Collection 類獨立安裝包,在使用非 laravel 項目是會很是有幫助。感謝 Tighten Co 團隊作出的努力 https://github.com/tightenco/...

感謝閱讀,快樂編碼!

若是你有興趣,能夠 follow 我 @mattkingshott

原文

How Laravel Collections lead to Zen Code

相關文章
相關標籤/搜索