Laravel 編碼技巧 Eloquent 模型

2023-02-16 17:09 更新

DB 模型和 Eloquent

復(fù)用或克隆 query ()

通常,我們需要從過濾后的查詢中進(jìn)行多次查詢。所以,大多數(shù)時候我們使用 query() 方法,

讓我們編寫一個查詢來獲取今天創(chuàng)建的已激活和未激活的產(chǎn)品。

$query = Product::query();

$today = request()->q_date ?? today();

if($today){
    $query->where('created_at', $today);
}

// 讓我們獲取已激活和未激活的產(chǎn)品

$active_products = $query->where('status', 1)->get(); // 這一行修改了 $query 對象變量

$inactive_products = $query->where('status', 0)->get(); // 所以這里我們不會找到任何未激活的產(chǎn)品

但是,在獲得 $active_products 后,$query 會被修改。$inactive_products 不會從 $query 中獲取任何未激活產(chǎn)品,并且每次都返回空集合。因為,它嘗試從 $active_products 中查找未激活產(chǎn)品($query 僅返回激活產(chǎn)品)。

為了解決這個問題,我們可以通過復(fù)用這個 $query 對象來進(jìn)行多次查詢。

因此,我們需要在執(zhí)行任何 $query 修改操作之前克隆這個 $query。

$active_products = (clone $query)->where('status', 1)->get(); // 它不會修改 $query

$inactive_products = (clone $query)->where('status', 0)->get(); // 所以我們將從 $query 中獲取未激活的產(chǎn)品

Eloquent where 日期方法

在 Eloquent 中,使用 whereDay()、whereMonth()whereYear()、whereDate() 和 whereTime() 函數(shù)檢查日期。

$products = Product::whereDate('created_at', '2018-01-31')->get();

$products = Product::whereMonth('created_at', '12')->get();

$products = Product::whereDay('created_at', '31')->get();

$products = Product::whereYear('created_at', date('Y'))->get();

$products = Product::whereTime('created_at', '=', '14:13:58')->get();

增量和減量

如果要增加數(shù)據(jù)庫某個表中的某個列,只需使用 increment() 函數(shù)。你不僅可以增加 1,還可以增加一些數(shù)字,比如 50。

Post::find($post_id)->increment('view_count');

User::find($user_id)->increment('points', 50);

沒有 timestamp 列

如果你的數(shù)據(jù)庫表不包含 timestamp 字段 created_at 和 updated_at,你可以使用 $timestamps = false 屬性指定 Eloquent 模型不使用它們。

class Company extends Model
{
    public $timestamps = false;
}

軟刪除:多行恢復(fù)

使用軟刪除時,您可以在一個句子中恢復(fù)多行。

Post::onlyTrashed()->where('author_id', 1)->restore();

模型 all:列

當(dāng)調(diào)用 Eloquent 的 Model::all() 時,你可以指定要返回的列。

$users = User::all(['id', 'name', 'email']);

失敗或不失敗

除了 findOrFail() 之外,還有 Eloquent 方法 firstOrFail(),如果沒有找到查詢記錄,它將返回 404 頁。

$user = User::where('email', 'povilas@laraveldaily.com')->firstOrFail();

列名更改

在 Eloquent Query Builder 中,您可以指定「as」以返回具有不同名稱的任何列,就像在普通 SQL 查詢中一樣。

$users = DB::table('users')->select('name', 'email as user_email')->get();

Map 查詢結(jié)果

在 Eloquent 查詢之后,您可以使用 Collections 中的 map() 函數(shù)來修改行。

$users = User::where('role_id', 1)->get()->map(function (User $user) {
    $user->some_column = some_function($user);
    return $user;
});

更改默認(rèn)時間戳字段

如果您使用的是非 Laravel 數(shù)據(jù)庫并且時間戳列的名稱不同怎么辦?也許,你有 create_time 和 update_time。 幸運的是,您也可以在模型中指定它們:

class Role extends Model
{
    const CREATED_AT = 'create_time';
    const UPDATED_AT = 'update_time';
}

按 created_at 快速排序

不要使用:

User::orderBy('created_at', 'desc')->get();

你可以做的更快:

User::latest()->get();

默認(rèn)情況下,latest() 會按 created_at 降序排序。

有一個相反的方法 oldest(),它按 created_at 升序排序:

User::oldest()->get();

此外,您可以指定另一列進(jìn)行排序。 例如,如果你想使用 updated_at,你可以這樣做:

$lastUpdatedUser = User::latest('updated_at')->first();

創(chuàng)建記錄時的自動列值

如果您想在創(chuàng)建記錄時生成一些 DB 列值,請將其添加到模型的 boot() 方法中。

例如,如果您有一個字段 「position」,并且想要將下一個可用位置分配給新記錄(例如 Country::max('position') + 1),請執(zhí)行以下操作:

class Country extends Model {
    protected static function boot()
    {
        parent::boot();
        Country::creating(function($model) {
            $model->position = Country::max('position') + 1;
        });
    }
}

數(shù)據(jù)庫原始查詢計算運行得更快

使用類似 whereRaw() 方法的 SQL 原始查詢,直接在查詢中進(jìn)行一些特定于數(shù)據(jù)庫的計算,而不是在 Laravel 中,通常結(jié)果會更快。 例如,如果您想獲得注冊后 30 天以上仍處于活躍狀態(tài)的用戶,請使用以下代碼:

User::where('active', 1)
->whereRaw('TIMESTAMPDIFF(DAY, created_at, updated_at) > ?', 30)
->get();

不止一個作用域

您可以在 Eloquent 中組合和鏈?zhǔn)讲樵冏饔糜颍诓樵冎惺褂枚鄠€作用域。

模型:

public function scopeActive($query) {
    return $query->where('active', 1);
}

public function scopeRegisteredWithinDays($query, $days) {
    return $query->where('created_at', '>=', now()->subDays($days));
}

控制器中使用:

$users = User::registeredWithinDays(30)->active()->get();

無需轉(zhuǎn)換 Carbon

如果你使用 whereDate() 查詢今日的記錄,可以直接使用 Carbon 的 now() 方法,會自動轉(zhuǎn)換為日期進(jìn)行查詢,無需指定 ->toDateString()

// 今日注冊的用戶
$todayUsers = User::whereDate('created_at', now()->toDateString())->get();
// 無需 toDateString() ,直接 now() 即可
$todayUsers = User::whereDate('created_at', now())->get();

由第一個單詞分組

你可以對 Eloquent 結(jié)果進(jìn)行條件分組,下面的示例是由用戶名稱的第一個單詞進(jìn)行分組:

$users = User::all()->groupBy(function($item) {
    return $item->name[0];
});

永不更新某個字段

如果有一個數(shù)據(jù)庫字段你想只更新一次,可以使用 Eloquent 的修改器來實現(xiàn):

class User extends Model
{
    public function setEmailAttribute($value)
    {
        if ($this->email) {
            return;
        }

        $this->attributes['email'] = $value;
    }
}

find () 查詢多條數(shù)據(jù)

find() 不止可以查詢一條數(shù)據(jù),當(dāng)傳入多個 ID 的值會返回這些結(jié)果的集合:

// 返回 Eloquent Model
$user = User::find(1);
// 返回 Eloquent Collection
$users = User::find([1,2,3]);

感謝 @tahiriqbalnajam 提供

find () 限制字段

find() 可在查詢多條的數(shù)據(jù)的情況下,指定只返回哪些字段:

// 會返回只包含 first_name 和 email 的 Eloquent 模型
$user = User::find(1, ['first_name', 'email']);
// 會返回只包含 first_name 和 email 兩個字段的 Eloquent 集合
$users = User::find([1,2,3], ['first_name', 'email']);

感謝 @tahiriqbalnajam 提供

按鍵查找

您還可以使用 whereKey() 方法查找多條記錄,該方法負(fù)責(zé)處理哪個字段正是您的主鍵(id 是默認(rèn)值,但您可以在 Eloquent 模型中覆蓋它):

$users = User::whereKey([1,2,3])->get();

使用 UUID 而不是自動遞增

您不想在模型中使用自動遞增 ID?

遷移:

Schema::create('users', function (Blueprint $table) {
    // $table->increments('id');
    $table->uuid('id')->unique();
});

模型:

class User extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';

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

        User::creating(function ($model) {
            $model->setId();
        });
    }

    public function setId()
    {
        $this->attributes['id'] = Str::uuid();
    }
}

Laravel 中的子選擇

從 Laravel 6 開始,您可以在 Eloquent 語句中使用 addSelect (),并對附加的列進(jìn)行一些計算。

return Destination::addSelect(['last_flight' => Flight::select('name')
    ->whereColumn('destination_id', 'destinations.id')
    ->orderBy('arrived_at', 'desc')
    ->limit(1)
])->get();

隱藏一些列

在進(jìn)行 Eloquent 查詢時,如果您想在返回中隱藏特定字段,最快捷的方法之一是在集合結(jié)果上添加 ->makeHidden()

$users = User::all()->makeHidden(['email_verified_at', 'deleted_at']);

確切的數(shù)據(jù)庫錯誤

如果您想捕獲 Eloquent Query 異常,請使用特定的 QueryException 代替默認(rèn)的 Exception 類,您將能夠獲得錯誤的確切 SQL 代碼。

try {
    // 一些 Eloquent/SQL 聲明
} catch (\Illuminate\Database\QueryException $e) {
    if ($e->getCode() === '23000') { // integrity constraint violation
        return back()->withError('Invalid data');
    }
}

使用查詢構(gòu)造器查詢軟刪除

不要忘記,當(dāng)您使用 Eloquent 時會排除已軟刪除的條目,但如果您使用查詢構(gòu)造器,則不會起作用。

// 排除軟刪除條目
$users = User::all();

// 不排除軟刪除條目
$users = User::withTrashed()->get();

// 不排除軟刪除條目
$users = DB::table('users')->get();

SQL 聲明

如果你需要執(zhí)行一個簡單的 SQL 查詢,但沒有得到任何結(jié)果 —— 比如改變數(shù)據(jù)庫模式中的某些東西,只需執(zhí)行 DB::statement()。

DB::statement('DROP TABLE users');
DB::statement('ALTER TABLE projects AUTO_INCREMENT=123');

使用數(shù)據(jù)庫事務(wù)

如果您執(zhí)行了兩個數(shù)據(jù)庫操作,第二個可能會出錯,那么您應(yīng)該回滾第一個,對嗎?

為此,我建議使用 DB Transactions,它在 Laravel 中非常簡單:

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
});

更新或創(chuàng)建

如果你需要檢查記錄是否存在,然后更新它,或者創(chuàng)建一個新記錄,你可以用一句話來完成 - 使用 Eloquent updateOrCreate() 方法:

// 不要這樣做
$flight = Flight::where('departure', 'Oakland')
    ->where('destination', 'San Diego')
    ->first();
if ($flight) {
    $flight->update(['price' => 99, 'discounted' => 1]);
} else {
    $flight = Flight::create([
        'departure' => 'Oakland',
        'destination' => 'San Diego',
        'price' => 99,
        'discounted' => 1
    ]);
}
// 一句話完成
$flight = Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

保存時移除緩存

感謝 @pratiksh404 提供

如果您有提供集合 posts 這樣的緩存鍵,想在新增或更新時移除緩存鍵,可以在您的模型上調(diào)用靜態(tài)的 saved 函數(shù):

class Post extends Model
{
    // 存儲或更新時移除緩存
    public static function boot()
    {
        parent::boot();
        static::saved(function () {
           Cache::forget('posts');
        });
    }
}

改變 Created_at 和 Updated_at 的時間格式

感謝 @syofyanzuhad 提供

想要改變 created_at 的格式,您可以在模型中添加一個方法,如下所示:

public function getCreatedAtFormattedAttribute()
{
   return $this->created_at->format('H:i d, M Y');
}

你可以在需要改變時間格式時使用 $entry->created_at_formatted ,它會返回 created_at 的屬性如同 04:19 23, Aug 2020。

你也可以用同樣的方法更改 updated_at

public function getUpdatedAtFormattedAttribute()
{
   return $this->updated_at->format('H:i d, M Y');
}

在有需要的時候使用 $entry->updated_at_formatted。它會返回 updated_at 的屬性如同: 04:19 23, Aug 2020 。

數(shù)組類型存儲到 JSON 中

感謝 @pratiksh404 提供

如果你的輸入字段有一個數(shù)組需要存儲為 JSON 格式,你可以在模型中使用 $casts 屬性。 這里的 images 是 JSON 屬性。

protected $casts = [
    'images' => 'array',
];

這樣你可以以 JSON 格式存儲它,但當(dāng)你從 DB 中讀取時,它會以數(shù)組方式使用。

制作模型的副本

如果你有兩個非常相似的模型(比如送貨地址和賬單地址),而且你想要復(fù)制其中一個作為另一個,你可以使用 replicate() 方法并更改一部分屬性。

官方文檔 的示例:

$shipping = Address::create([
    'type' => 'shipping',
    'line_1' => '123 Example Street',
    'city' => 'Victorville',
    'state' => 'CA',
    'postcode' => '90001',
]);

$billing = $shipping->replicate()->fill([
    'type' => 'billing'
]);

$billing->save();

減少內(nèi)存

有時我們需要將大量的數(shù)據(jù)加載到內(nèi)存中,比如:

$orders = Order::all();

但如果我們有非常龐大的數(shù)據(jù)庫,這可能會很慢,因為 Laravel 會準(zhǔn)備好模型類的對象。在這種情況下,Laravel 有一個很方便的函數(shù) toBase()。

$orders = Order::toBase()->get();
//$orders 將包含 `Illuminate\Support\Collection` 與對象 `StdClass`

通過調(diào)用這個方法,它將從數(shù)據(jù)庫中獲取數(shù)據(jù),但它不會準(zhǔn)備模型類。同時,向 get() 方法傳遞一個字段數(shù)組通常是個好主意,這樣可以防止從數(shù)據(jù)庫中獲取所有字段。

忽略 $fillable/$guarded 并強制查詢

如果你創(chuàng)建了一個 Laravel 模板作為其他開發(fā)者的「啟動器」, 并且你不能控制他們以后會在模型的 $fillable/$guarded 中填寫什么,你可以使用 forceFill()

$team->update(['name' => $request->name])

如果 name 不在團(tuán)隊模型的 $fillable 中,怎么辦?或者如果根本就沒有 $fillable/$guarded, 怎么辦?

$team->forceFill(['name' => $request->name])

這將忽略該查詢的 $fillable 并強制執(zhí)行。

3 層父子級結(jié)構(gòu)

如果你有一個 3 層的父子結(jié)構(gòu),比如電子商店中的分類,你想顯示第三層的產(chǎn)品數(shù)量,你可以使用 with('yyy.yyy'),然后添加 withCount() 作為條件

class HomeController extend Controller
{
    public function index()
    {
        $categories = Category::query()
            ->whereNull('category_id')
            ->with(['subcategories.subcategories' => function($query) {
                $query->withCount('products');
            }])->get();
    }
}
class Category extends Model
{
    public function subcategories()
    {
        return $this->hasMany(Category::class);
    }

    public function products()
    {
    return $this->hasMany(Product::class);
    }
}
<ul>
    @foreach($categories as $category)
        <li>
            {{ $category->name }}
            @if ($category->subcategories)
                <ul>
                @foreach($category->subcategories as $subcategory)
                    <li>
                        {{ $subcategory->name }}
                        @if ($subcategory->subcategories)
                            <ul>
                                @foreach ($subcategory->subcategories as $subcategory)
                                    <li>{{ $subcategory->name }} ({{ $subcategory->product_count }})</li>
                                @endforeach
                            </ul>
                        @endif
                    </li>
                @endforeach
                </ul>
            @endif
        </li>
    @endforeach           
</ul>

使用 find() 來搜索更多的記錄

你不僅可以用 find() 來搜索單條記錄,還可以用 IDs 的集合來搜索更多的記錄,方法如下:

return Product::whereIn('id', $this->productIDs)->get();

你可以這樣做:

return Product::find($this->productIDs)

失敗時執(zhí)行任何操作

當(dāng)查詢一條記錄時,如果沒有找到,你可能想執(zhí)行一些操作。除了用 ->firstOrFail() 會拋出 404 之外,你可以在失敗時執(zhí)行任何操作,只需要使用 ->firstOr(function() { ... })

$model = Flight::where('legs', '>', 3)->firstOr(function () {
    // ...
})

檢查記錄是否存在或者顯示 404

不要使用 find() ,然后再檢查記錄是否存在,使用 findOrFail() 。

$product = Product::find($id);
if (!$product) {
    abort(404);
}
$product->update($productDataArray);

更簡單的方法:

$product = Product::findOrFail($id); // 查詢不到時顯示 404
$product->update($productDataArray);

條件語句為否時中止

可以使用 abort_if() 作為判斷條件和拋出錯誤頁面的快捷方式。

$product = Product::findOrFail($id);
if($product->user_id != auth()->user()->id){
    abort(403);
}

更簡單的方法:

/* abort_if(CONDITION, ERROR_CODE) */
$product = Product::findOrFail($id);
abort_if ($product->user_id != auth()->user()->id, 403)

在刪除模型之前執(zhí)行任何額外的操作

感謝 @back2Lobby 提供

我們可以使用 Model::delete() 執(zhí)行額外的操作來覆蓋原本的刪除方法。

// App\Models\User.php

public function delete(){

    //執(zhí)行你想要的額外操作

    //然后進(jìn)行正常的刪除
    Model::delete();
}

當(dāng)你需要在保存數(shù)據(jù)到數(shù)據(jù)庫時自動填充一個字段

當(dāng)你需要在保存數(shù)據(jù)到數(shù)據(jù)庫時自動填充一個字段 (例如: slug),使用模型觀察者來代替重復(fù)編寫代碼。

use Illuminate\Support\Str;

class Article extends Model
{
    ...
    protected static function boot()
    {
        parent:boot();

        static::saving(function ($model) {
            $model->slug = Str::slug($model->title);
        });
    }
}

感謝 @sky_0xs 提供

獲取查詢語句的額外信息

你可以使用 explain() 方法來獲取查詢語句的額外信息。

Book::where('name', 'Ruskin Bond')->explain()->dd();
Illuminate\Support\Collection {#5344
    all: [
        {#15407
            +"id": 1,
            +"select_type": "SIMPLE",
            +"table": "books",
            +"partitions": null,
            +"type": "ALL",
            +"possible_keys": null,
            +"key": null,
            +"key_len": null,
            +"ref": null,
            +"rows": 9,
            +"filtered": 11.11111164093,
            +"Extra": "Using where",
        },
    ],
}

感謝 @amit_merchant 提供

在 Laravel 中使用 doesntExist () 方法

// 一個例子
if ( 0 === $model->where('status', 'pending')->count() ) {
}

// 我不關(guān)心它有多少數(shù)據(jù)只要它是0
// Laravel 的 exists() 方法會很清晰:
if ( ! $model->where('status', 'pending')->exists() ) {
}

// 但我發(fā)現(xiàn)上面這條語句中的!很容易被忽略。
// 那么 doesntExist() 方法會讓這個例子更加清晰
if ( $model->where('status', 'pending')->doesntExist() ) {
}

感謝 @ShawnHooper 提供

想要在一些模型的 boot () 方法中自動調(diào)用一個特性

如果你有一個特性,你想把它添加到幾個模型中,自動調(diào)用它們的 boot() 方法,你可以把特質(zhì)的方法作為 boot (特性名稱)來調(diào)用。

class Transaction extends  Model
{
    use MultiTenantModelTrait;
}
class Task extends  Model
{
    use MultiTenantModelTrait;
}
trait MultiTenantModelTrait
{
    // 這個方法名是 boot[特性名稱]。
    // 它將作為事務(wù)/任務(wù)的 boot() 被自動調(diào)用。
    public static function bootMultiTenantModelTrait()
    {
        static::creating(function ($model) {
            if (!$isAdmin) {
                $isAdmin->created_by_id = auth()->id();
            }
        })
    }
}

Laravel 的 find () 方法,比只傳一個 ID 更多的選擇

// 在 find($id) 方法中第二個參數(shù)可以是返回字段
Studdents::find(1, ['name', 'father_name']);
// 這樣我們可以查詢 ID 為 '1' 并返回 name , father_name 字段

// 我們可以用數(shù)組的方式傳遞更多的 ID
Studdents::find([1,2,3], ['name', 'father_name']);
// 輸出: ID 為 1,2,3 并返回他們的 name , father_name 字段

在 Laravel 中,有兩種常見的方法來確定一個表是否為空表

在 Laravel 中,有兩種常見的方法來確定一個表是否為空表。直接在模型上使用 exists() 或者 count()!

一個返回嚴(yán)格的布爾值,另一個返回一個整數(shù),你都可以在條件語句中使用。

public function index()
{
    if (\App\Models\User::exists()) {
        // 如果表有任何保存的數(shù)據(jù),則返回布爾值 true 或 false。
    }

    if (\App\Models\User::count()) {
        // 返回表中數(shù)據(jù)的數(shù)量。
    }
}

感謝 @aschmelyun 提供

如何防止 property of non-object 錯誤

// 設(shè)定默認(rèn)模型
// 假設(shè)你有一篇 Post (帖子) 屬于一個 Author (作者),代碼如下:
$post->author->name;

// 當(dāng)然你可以像這樣阻止錯誤:
$post->author->name ?? ''
// 或者
@$post->auhtor->name

// 但你可以在Eloquent關(guān)系層面上做到這一點。
// 如果沒有作者關(guān)聯(lián)帖子,這種關(guān)系將返回一個空的App/Author模型。
public function author() {
    return $this->belongsTo('App\Author')->withDefaults();
}
// 或者
public function author() {
    return $this->belongsTo('App\Author')->withDefault([
        'name' => 'Guest Author'
    ]);
}

感謝 @coderahuljat 提交

Eloquent 數(shù)據(jù)改變后獲取原始數(shù)據(jù)

Eloquent 模型數(shù)據(jù)改變后,你可以使用 getOriginal () 方法來獲取原始數(shù)據(jù)。

$user = App\User::first();
$user->name; // John
$user->name = "Peter"; // Peter
$user->getOriginal('name'); // John
$user->getOriginal(); // 原始的 $user 記錄

感謝 @devThaer 提交

一種更簡單創(chuàng)建數(shù)據(jù)庫的方法

Laravel 還可以使用 .sql 文件來更簡單的創(chuàng)建數(shù)據(jù)庫。

DB::unprepared(
    file_get_contents(__DIR__ . './dump.sql')
);

感謝 @w3Nicolas 提交


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號