【譯】20個 Laravel Eloquent 小技巧

在其表面簡單易用的機制背後,還有不少半隱藏的功能或者少有人知的方法來實現一些頗有用的需求。 在本文中,我將向您展現一些技巧。php

1. 增量和減小

若是你平時是這麼作的:laravel

$article = Article::find($article_id);
$article->read_count++;
$article->save();
複製代碼

那麼你能夠試試這樣:git

$article = Article::find($article_id);
$article->increment('read_count');
複製代碼

或者這樣也是能夠的:github

Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1
複製代碼

2. XorY 方法

Eloquent有不少方法是兩個方法的組合,實現 「請作X,不然作Y」這樣的需求。sql

例 1 findOrFail():shell

能夠把這樣的代碼:數據庫

$user = User::find($id);
if (!$user) { abort (404); }
複製代碼

換成這樣:數組

$user = User::findOrFail($id);
複製代碼

例 2 firstOrCreate():bash

不須要寫這麼長:閉包

$user = User::where('email', $email)->first();
if (!$user) {
  User::create([
    'email' => $email
  ]);
}
複製代碼

這樣就夠了:

$user = User::firstOrCreate(['email' => $email]);
複製代碼

3. 模型的 boot() 方法

在Eloquent模型中有一個名爲boot()的神奇地方,您能夠在其中覆蓋默認行爲:

class User extends Model {
    public static function boot() {
        parent::boot();
        static::updating(function($model) {
            // 記錄一些日誌
            // 覆蓋或者重寫一些屬性 好比$model->something = transform($something);
        });
    }
}
複製代碼

可能最多見的例子之一是在建立模型對象時設置一些字段值。比方說你須要在建立對象時候生成UUID字段。

4. 帶條件以及排序的關聯關係模型

一般定義關係模型的方法是這樣的

public function users() {
    return $this->hasMany('App\User');    
}
複製代碼

但你是否知道在定義關係模型的時候就已經能夠增長 where 或者 orderBy 的條件了? 好比說你須要定義一個特定類型的用戶的關聯關係而且用郵箱信息來排序,那你能夠這麼作:

public function approvedUsers() {
    return $this->hasMany('App\User')->where('approved', 1)->orderBy('email');
}
複製代碼

5. 模型屬性: 時間戳, 附加屬性(appends) 等

Eloquent模型有一些「參數」,會以該類的屬性形式出現。 最經常使用的多是這些:

class User extends Model {
    protected $table = 'users';
    protected $fillable = ['email', 'password']; // 這些字段能夠在模型的 create 方法中直接建立
    protected $dates = ['created_at', 'deleted_at']; // 這些字段將會轉換成 Carbon類型的,能夠方便的使用 Carbon 提供的時間方法
    protected $appends = ['field1', 'field2']; // 序列化時候附加的額外屬性,經過模型中定義 getXXXAttribute 的方式來定義
}
複製代碼

可不只僅有這些,還有:

protected $primaryKey = 'uuid'; // 模型的主鍵名稱能夠不是默認的 id
public $incrementing = false; // 甚至能夠沒必要是自增的類型!
protected $perPage = 25; // 是的,你還定義模型集合分頁參數(默認是 15)
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at'; // 默認的時間戳字段也是能夠改變的
public $timestamps = false; // 或者徹底不用他
複製代碼

甚至還有更多,我僅僅列出了最有意思的一部分,更多請查看默認抽象Model類的代碼,並查看全部使用的trait 方法。

6. 查詢多個實體對象

find()方法想必你們都知道的吧?

$user = User::find(1);
複製代碼

我很驚訝不多有人知道它能夠接受多個ID做爲數組:

$users = User::find([1,2,3]);
複製代碼

7. WhereX

有一種很優雅的方式能夠把下面的代碼:

$users = User::where('approved', 1)->get();
複製代碼

改爲這樣:

$users = User::whereApproved(1)->get(); 
複製代碼

是的,你也能夠改爲任何字段的名稱,並將其做爲後綴附加到「where」,它將神奇的產生預想的效果(經過魔術方法實現調用)。

此外,Eloquent中還有一些與日期/時間相關的預約義方法:

User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));
複製代碼

8. 使用關係模型字段排序

一個更復雜的「技巧」。 若是你有帖子,但要經過最新帖子對它們進行排序? 頂部有最新更新主題的論壇中很是常見的要求,對吧?

首先,定義關於該主題的最新帖子的關係:

public function latestPost() {
    return $this->hasOne(\App\Post::class)->latest();
}
複製代碼

接下來能夠在咱們的控制器中用這個神奇的方法來實現:

$users = Topic::with('latestPost')->get()->sortByDesc('latestPost.created_at');
複製代碼

9. Eloquent::when() – 不用再寫 if -else 啦

大部分時候咱們用 if-else 來實現按條件查詢,相似這樣的代碼:

if (request('filter_by') == 'likes') {
    $query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
    $query->orderBy('created_at', request('ordering_rule', 'desc'));
}
複製代碼

可是一個更好的方法是——使用 when()方法

$query = Author::query();
$query->when(request('filter_by') == 'likes', function ($q) {
    return $q->where('likes', '>', request('likes_amount', 0));
});
$query->when(request('filter_by') == 'date', function ($q) {
    return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});
複製代碼

它看起來可能不會更短或更優雅,但最強大的是能夠傳遞參數:

$query = User::query();
$query->when(request('role', false), function ($q, $role) { 
    return $q->where('role_id', $role);
});
$authors = $query->get();
複製代碼

10. BelongsTo 關聯的默認模型對象

假設有個 Post(帖子) 對象屬於 Author (做者)對象,在 Blade 模板中有下面的代碼

{{ $post->author->name }}
複製代碼

可是若是做者被刪除,或者因爲某種緣由沒有設置呢? 那麼就會致使報錯,多是「property of non-object(非對象屬性)」。

固然你能夠用下面的代碼來必變這種錯誤:

{{ $post->author->name ?? '' }}
複製代碼

不過你能夠再模型定義時候就解決這個問題:

public function author()
{
    return $this->belongsTo('App\Author')->withDefault();
}
複製代碼

在這個例子中,在這個帖子下沒有關聯做者的時候,author()關聯關係將返回一個空的App\Author 模型。

更進一步,咱們能夠設置一些默認屬性個這個模型。

public function author() {
    return $this->belongsTo('App\Author')->withDefault([
        'name' => 'Guest Author'
    ]);
}
複製代碼

11. 自定義屬性排序

假設你有下面的一段代碼:

(設定了一個在返回對象時候的附加屬性 ‘full_name’參見 tips5 模型屬性: 時間戳, 附加屬性(appends) 等)

function getFullNameAttribute() {
  return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
複製代碼

若是你想要按照 full_name 來排序的話?下面的代碼是不行的:

$clients = Client::orderBy('full_name')->get(); //不行滴
複製代碼

固然解決方案也是很是簡單。 咱們須要在獲得結果之後再對他們進行排序。

$clients = Client::get()->sortBy('full_name'); //穩了
複製代碼

注意兩個方法名字是不同的——不是 orderBy 而是 sortBy

(一個是 SQL 語句,自定義屬性是數據庫沒有的字段固然不能直接用。可是查詢的返回都是一個 Collection 對象,Laravel 爲集合提供了不少方便的操做方法,sortBy 就是其中一個,固然還能夠用 filter 等集合操做)

12. 全局範圍(global scope)內的默認排序

若是你但願User :: all()始終按名稱字段排序,該怎麼辦? 你能夠分配全局的查詢做用域。 讓咱們回到上面已經提到的boot()方法。

protected static function boot() {
    parent::boot();

    // 默認按照name 字段升序
    static::addGlobalScope('order', function (Builder $builder) {
        $builder->orderBy('name', 'asc');
    });
}
複製代碼

這裏還有更多關於請求範圍做用域的介紹。

13. 原生查詢方法

有時咱們須要在Eloquent語句中添加原生查詢語句。 幸運的是,它提供了這樣的功能。

// 原生 where 語句
$orders = DB::table('orders')
    ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
    ->get();

// 原生 having 語句
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();

// 原生 orderBy 語句
User::where('created_at', '>', '2016-01-01')
  ->orderByRaw('(updated_at - created_at) desc')
  ->get();
複製代碼

(本質上Eloquent就是對 DB 查詢對象的一個封裝,因此能夠用在 DB 上的原始查詢方法,均可以用在繼承自 Eloquent 的 model 對象上。)

14. 複製: 獲得一行數據的一個副本

很簡單的一條,不須要太多解釋。這是生成數據庫條目副本的最佳手段。

$task = Tasks::find(1);
$newTask = $task->replicate();
$newTask->save();
複製代碼

15. 用於大表大集合的 Chunk()方法

不徹底與Eloquent相關,它更可能是Collection 集合類提供的方法,但仍然很強大 —— 處理更大的數據集,你能夠將它們分紅幾塊。

不要這麼作:

$users = User::all();
foreach ($users as $user) {
    // ...
複製代碼

而是這樣:

User::chunk(100, function ($users) {
    foreach ($users as $user) {
        // ...
    }
});
複製代碼

相似於數據分片,減小佔用提高性能

16. 在生成模型的時候再額外生成一些模板

咱們都知道這個的 Artisan 的命令:

php artisan make:model Company
複製代碼

但你是否知道它還有三個頗有用的參數標記用來生成與這個模型關聯的其餘文件?

php artisan make:model Company -mcr
複製代碼
  • -m 將會建立模型的遷移(migration)文件
  • -c 將會建立控制器(contriller)
  • -r 將表用這個控制器應該是一個資源控制器 (resourceful)

17. 在保存的時候重寫 update_at 字段

你知道 - > save()方法是能夠接受參數的嗎? 所以,咱們能夠告訴它「忽略」 updated_at默認填充當前時間戳的功能。 看這個例子:

$product = Product::find($id);
$product->updated_at = '2019-01-01 10:00:00';
$product->save(['timestamps' => false]);
複製代碼

這裏咱們動態的重寫的 update_at 字段,而不是預先在模型中定義。

Laravel 默認會給全部實體類配置時間戳,若是不須要通常是在模型中指定 $timestamps = false

18. update()方法的返回值是什麼?

你有沒有曾想過下面這段代碼返回的 result 是什麼?

$result = $products->whereNull('category_id')->update(['category_id' => 2]);
複製代碼

個人意思是,更新語句是在數據庫中正確執行的,但 $ result 變量會包含什麼?

答案是受影響的行。 所以,若是您須要檢查受影響的行數,則無需再調用任何其餘方法 - update()方法將爲你返回這個數字。

19. 正確翻譯 SQL 語句中的括號 到 Eloquent 的查詢

假設在你的 SQL 查詢中 包含了 and / or 這樣的關鍵字,以下:

... WHERE (gender = 'Male' and age >= 18) or (gender = 'Female' and age >= 65)
複製代碼

怎麼翻譯成 Eloquent的查詢呢? 這是錯誤的方法:

$q->where('gender', 'Male');
$q->orWhere('age', '>=', 18);
$q->where('gender', 'Female');
$q->orWhere('age', '>=', 65);
複製代碼

這個順序是有問題的。正確的方法稍微有些複雜,須要用到閉包函數做爲子查詢:

$q->where(function ($query) {
    $query->where('gender', 'Male')
        ->where('age', '>=', 18);
})->orWhere(function($query) {
    $query->where('gender', 'Female')
        ->where('age', '>=', 65); 
})
複製代碼

20 orWhere方法使用更多參數

最後一條,你能夠個 orWhere 方法傳遞一個數組。

常規用法是:

$q->where('a', 1);
$q->orWhere('b', 2);
$q->orWhere('c', 3);
複製代碼

你也能夠用下面的語句實現同樣的功能:

$q->where('a', 1);
$q->orWhere(['b' => 2, 'c' => 3]);
複製代碼
相關文章
相關標籤/搜索