將「優雅」進行到底——laravel的最佳實踐與建議

Laravel已是衆所周知的「優雅」、「簡潔」、「實用」、「敏捷」、「特性豐富」等的代名詞,尤爲是對PHP開發者來講。固然了,也正是由於它特性過於「豐富」,致使不少特性並無列在文檔裏,或者曾經列出來過,可是出於某些緣由,後來又移除了。不少時候,咱們給新手學習建議的時候,都不會建議他直接去看文檔,由於文檔體系的展開,每每並非新手能hold住的。咱們每每會推薦他們從一些過來人的、真正優雅規範的教程開始上手,上手之後,再在實際當中自行查閱文檔。php

本文裏,將列出一些截至laravel 5.7的最佳實踐與使用建議,不論新手與否,相信均可以幫你將laravel用得更好,將laravel的優雅進行到底,同時更充分地利用起laravel的特性來。前端

更好的閱讀體驗,請直接移步原文出處:www.pilishen.com/posts/pushi…

(一)將一些經常使用的數據庫查詢,放到local scopes中

$orders = Order::where('status', 'delivered')->where('paid', true)->get();
複製代碼

咱們常常會寫些相似的邏輯,可是當條件查詢啥的多了之後,也會變得繁瑣,並且期間的不少條件限定與查詢,就不能複用,每次都得重複寫,這個時候local scopes就登場了。local scopes你能夠理解成本地的查詢域,或者更直白一些,就是一個經常使用的查詢條件片斷。咱們能夠在model裏這樣:laravel

class Order extends Model
{
   ...
   public function scopeDelivered($query) {
      return $query->where('status', 'delivered');
   }
   public function scopePaid($query) {
      return $query->where('paid', true);
   }
}
複製代碼

能夠看到這些查詢域,都是以scope開頭的,後面接上你想要的名字,而後在調用的時候,只須要調用scope後面的實際名字便可:數據庫

$orders = Order::delivered()->paid()->get();
複製代碼

因此這個查詢域,就是laravel query builder的一個個片斷,或者說數據庫條件查詢的片斷,而後它們能夠拼接起來,讓咱們終端的代碼更加簡潔直觀。bash

固然,咱們能夠更進一步,給這個查詢域傳上參數,造成動態的條件查詢,好比下面這樣:app

class Order extends Model
{
   ...
   public function scopeStatus($query, string $status) {
      return $query->where('status', $status);
   }
}
$orders = Order::status('delivered')->paid()->get();
複製代碼

(二)儘量地使用自定義Form Request

在不少不那麼規範的教程裏,你常常會看到以下的代碼:框架

public function store(Request $request)
{
    $validatedData = $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

    // The blog post is valid...
}
複製代碼

不規範的緣由,就是他們把全部能想到的東西,都丟在controller裏就完事了,但這樣觸犯的忌諱就太多了,違背的原則也就太多了,更重要的,隨着項目的進行,很容易讓代碼變成大花臉,變得愈來愈無法維護和擴展,更不用說別人甚至本身回頭閱讀了。那麼上面的這個例子,就是讓controller去管了它本不該該負責的數據驗證邏輯,而這塊,laravel有更優雅的方式來解放controller,也便是咱們的自定義form request:less

php artisan make:request StoreBlogPost
複製代碼

執行了之後,在app/Http/Requests/文件夾下,就會多出下面這麼個單獨的類:dom

class StoreBlogPostRequest extends FormRequest
{
   public function authorize()
   {
      return $this->user()->can('create.posts');
   }
   public function rules()
   {
       return [
         'title' => 'required|unique:posts|max:255',
         'body' => 'required',
       ];
   }
}
複製代碼

這樣了之後,在controller裏,就不須要傳Request了,而是傳上咱們剛纔定義的這個StoreBlogPostRequest異步

use App\Http\Requests\StoreBlogPostRequest;

public function store(StoreBlogPostRequest $request)
{
    // 到這兒的時候,post的相關數據就已經驗證好了
}
複製代碼

固然在這個自定義的request類裏,咱們能夠作的文章不止這一點,好比咱們還能夠具體定義每個字段驗證失敗時的錯誤信息,這個能夠經過定義messages()方法來實現,咱們也能夠將request類裏的錯誤信息,在blade當中去相應地返回。這些呢,在咱們的《Laravel5.7優雅實戰入門:第二版》課程裏都已經詳細說了,這裏就實在懶得囉嗦了。

(三)將一些自帶的魔術查詢方法用起來

  1. 按照created_at一欄,進行降序排列(‘desc’)
User::latest()->get();
複製代碼
  1. 按照任意字段,降序排列查詢
User::latest('last_login_at')->get();
複製代碼
  1. 以隨機的順序獲取數據
User::inRandomOrder()->get();
複製代碼
  1. 只有當特定條件爲true時,才執行相應查詢
// 假設用戶在news頁面,想經過下面的url進行最新消息的排序
// mydomain.com/news?sort=new

User::when($request->query('sort'), function ($query, $sort) {
   if ($sort == 'new') {
      return $query->latest();
   }
   
   return $query;
})->get();
複製代碼

與這個when()方法相對應的,還有一個unless(), 它是when()的反面,也即只有爲false的時候,才執行某個查詢

(四)使用Model關係,來避免大段的查詢語句,甚至糟糕的查詢

時至今日,你是否還在寫一些大段的、難寫難讀又難複用的SQL查詢語句呢?好比爲了獲取更多的信息,使用了大量的join。即便你利用laravel的Query Builder,這些語句也很是難寫,每次寫一個是否得花半天呢?並且若是你不是那麼懂,原本一個很快的查詢,可能讓你寫糟糕了,反而很是很是慢。

可是若是你懂得laravel裏的Model關係,懂得ORM這種更現代的查詢方式,那麼每每幾乎沒有什麼難的查詢,寫的時候根本無需動腦,並且不少邏輯均可以複用,而且避免了因不懂背後原理而致使性能反而降低的問題。

這一方面,咱們pilishen.com已經說得足夠多了,好比能夠看看咱們以前的一篇相關帖子《laravel框架中的Model操做數據庫 , 相比DB類有什麼明顯的優越性嗎?》

固然這些,在咱們的《Laravel5.7優雅實戰入門:第二版》課程裏也都詳盡演示了。

(五)使用隊列job來處理耗時操做

原來咱們課程里老說,「高性能離不開異步,異步離不開隊列」。這方面,laravel的隊列job,在處理耗時的、能夠放在後臺運行的任務上,是一個強大的、必需要掌握的工具。

想發送郵件?隊列job。 想發送廣播消息?隊列job。 想進行較多的圖片操做?隊列job。 。。。

相似的耗時操做,隊列可讓你的前臺用戶無需等待,背後處理便可。這期間,你甚至能夠設置隊列的頻道或名字,設置優先級,設置超時時間,設置重試次數,等等等等。

(六)善用屬性獲取器(accessor)與修改器(mutator)

假設你只有first_namelast_name兩個字段,而後你想着每次都能輕鬆地取得user的全名name,那麼能夠:

class User extends Model
{
   ...
   public function getNameAttribute(): string
   {
       return $this->first_name.' '.$this->last_name;
   }
}
複製代碼

這樣當你獲取$user->name時,就能自動拼接起全名來。默認的這個$user->name屬性,並不會自動附加到你的user實例上,也即這個時候你dd($user),並不會顯示出name屬性來,那麼怎麼樣讓這個name屬性自動附加到$user實例上呢?能夠以下:

class User extends Model
{
   protected $appends = [
      'name',
   ];
   ...
   public function getNameAttribute(): string
   {
       return $this->first_name.' '.$this->last_name;
   }
}
複製代碼

這個時候,每次咱們dd($user),或者在blade視圖以及js前端調用中,就天然會帶上這個拼接出來的屬性了,固然了,這個屬性,並無實際存在數據庫中。

(七)不要將model相關的靜態屬性或內容,放到config當中

假設有個BettingOdds.php

class BettingOdds extends Model
{
   ...
}
複製代碼

可能有的人會將一些靜態內容放到好比說config/bettingOdds.php中:

return [
   'sports' => [
      'soccer' => 'sport:1',
      'tennis' => 'sport:2',
      'basketball' => 'sport:3',
      ...
   ],
];
複製代碼

而後呢,獲取的時候這樣config(’bettingOdds.sports.soccer’);,可是這樣並很差,存在一些隱患,不如直接放到model當中:

class BettingOdds extends Model
{
   protected static $sports = [
      'soccer' => 'sport:1',
      'tennis' => 'sport:2',
      'basketball' => 'sport:3',
      ...
   ];
}
複製代碼

而後這樣來獲取BettingOdds::$sports['soccer'];

並且這樣,在進一步的數據調用中,更方便,好比:

class BettingOdds extends Model
{
   protected static $sports = [
      'soccer' => 'sport:1',
      'tennis' => 'sport:2',
      'basketball' => 'sport:3',
      ...
   ];
   public function scopeSport($query, string $sport)
   {
      if (! isset(self::$sports[$sport])) {
         return $query;
      }
      
      return $query->where('sport_id', self::$sports[$sport]);
   }
}
複製代碼

那麼這個時候,咱們就能夠像前面說到的,BettingOdds::sport('soccer')->get();

(八)多使用集合collection,而不是原始的array來操做數據

$fruits = ['apple', 'pear', 'banana', 'strawberry'];
foreach ($fruits as $fruit) {
   echo 'I have '. $fruit;
}
複製代碼

之前,咱們都習慣直接用array來操做相關數據,可是在laravel裏,更優雅的方式是多使用collection及相關方法

$fruits = collect($fruits);
$fruits = $fruits->reject(function ($fruit) {
   return $fruit === 'apple';
})->toArray();
['pear', 'banana', 'strawberry']
複製代碼

這樣咱們就能夠方便地利用上一些filter、map、transform、reject等等方法,既簡單,又優雅

咱們知道laravel的Query Builders,當在它後面最終調用->get()方法時,返回的是一個集合Collection實例。可是這裏,不要將單純的Collection和Query builder混淆起來:

  1. 在Query Builder中,在你最終調用->get()first()這些方法前,咱們實際上並無獲取任何數據,好比當咱們用orderBy()where()這些查詢方法時
  2. 當咱們調用了->get()first()方法時,數據才真的去獲取,相應的內存才真的佔用,而後返回一個集合實例

因此,若是能在Query Builder的環節,就進行數據的過濾或條件限定,那就要在這一環節作。而不要說先獲取了數據,返回了Collection實例之後,再用Collection相關的方法去過濾或操做,這樣的話耗費的內存資源就容易多得多。固然了,這期間也要限定好查詢,利用好數據庫索引,關於數據庫索引,若是你還不夠精通,記得看咱們的國際IT專場:《每一個程序猿必須且必定要懂的「數據庫索引」》

參考文章: //medium.com/@alexrenoki/pushing-laravel-further-best-tips-good-practices-for-laravel-5-7-ac97305b8cac(在該文基礎上有所刪減和更改)

來個人網站作個客吧,你會發現更多精彩內容的:www.pilishen.com/posts/pushi…

相關文章
相關標籤/搜索