Laravel 编码技巧 Eloquent 模型

2023-02-16 17:09 更新

DB 模型和 Eloquent

复用或克隆 query ()

通常,我们需要从过滤后的查询中进行多次查询。所以,大多数时候我们使用 query() 方法,

让我们编写一个查询来获取今天创建的已激活和未激活的产品。

$query = Product::query();

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

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

// 让我们获取已激活和未激活的产品

$active_products = $query->where('status', 1)->get(); // 这一行修改了 $query 对象变量

$inactive_products = $query->where('status', 0)->get(); // 所以这里我们不会找到任何未激活的产品

但是,在获得 $active_products 后,$query 会被修改。$inactive_products 不会从 $query 中获取任何未激活产品,并且每次都返回空集合。因为,它尝试从 $active_products 中查找未激活产品($query 仅返回激活产品)。

为了解决这个问题,我们可以通过复用这个 $query 对象来进行多次查询。

因此,我们需要在执行任何 $query 修改操作之前克隆这个 $query

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

$inactive_products = (clone $query)->where('status', 0)->get(); // 所以我们将从 $query 中获取未激活的产品

Eloquent where 日期方法

在 Eloquent 中,使用 whereDay()whereMonth()whereYear()whereDate() 和 whereTime() 函数检查日期。

$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();

增量和减量

如果要增加数据库某个表中的某个列,只需使用 increment() 函数。你不仅可以增加 1,还可以增加一些数字,比如 50。

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

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

没有 timestamp 列

如果你的数据库表不包含 timestamp 字段 created_at 和 updated_at,你可以使用 $timestamps = false 属性指定 Eloquent 模型不使用它们。

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

软删除:多行恢复

使用软删除时,您可以在一个句子中恢复多行。

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

模型 all:列

当调用 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 查询结果

在 Eloquent 查询之后,您可以使用 Collections 中的 map() 函数来修改行。

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

更改默认时间戳字段

如果您使用的是非 Laravel 数据库并且时间戳列的名称不同怎么办?也许,你有 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();

默认情况下,latest() 会按 created_at 降序排序。

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

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

此外,您可以指定另一列进行排序。 例如,如果你想使用 updated_at,你可以这样做:

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

创建记录时的自动列值

如果您想在创建记录时生成一些 DB 列值,请将其添加到模型的 boot() 方法中。

例如,如果您有一个字段 「position」,并且想要将下一个可用位置分配给新记录(例如 Country::max('position') + 1),请执行以下操作:

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

数据库原始查询计算运行得更快

使用类似 whereRaw() 方法的 SQL 原始查询,直接在查询中进行一些特定于数据库的计算,而不是在 Laravel 中,通常结果会更快。 例如,如果您想获得注册后 30 天以上仍处于活跃状态的用户,请使用以下代码:

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

不止一个作用域

您可以在 Eloquent 中组合和链式查询作用域,在查询中使用多个作用域。

模型:

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();

无需转换 Carbon

如果你使用 whereDate() 查询今日的记录,可以直接使用 Carbon 的 now() 方法,会自动转换为日期进行查询,无需指定 ->toDateString()

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

由第一个单词分组

你可以对 Eloquent 结果进行条件分组,下面的示例是由用户名称的第一个单词进行分组:

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

永不更新某个字段

如果有一个数据库字段你想只更新一次,可以使用 Eloquent 的修改器来实现:

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

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

find () 查询多条数据

find() 不止可以查询一条数据,当传入多个 ID 的值会返回这些结果的集合:

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

感谢 @tahiriqbalnajam 提供

find () 限制字段

find() 可在查询多条的数据的情况下,指定只返回哪些字段:

// 会返回只包含 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() 方法查找多条记录,该方法负责处理哪个字段正是您的主键(id 是默认值,但您可以在 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 (),并对附加的列进行一些计算。

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

隐藏一些列

在进行 Eloquent 查询时,如果您想在返回中隐藏特定字段,最快捷的方法之一是在集合结果上添加 ->makeHidden()

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

确切的数据库错误

如果您想捕获 Eloquent Query 异常,请使用特定的 QueryException 代替默认的 Exception 类,您将能够获得错误的确切 SQL 代码。

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

使用查询构造器查询软删除

不要忘记,当您使用 Eloquent 时会排除已软删除的条目,但如果您使用查询构造器,则不会起作用。

// 排除软删除条目
$users = User::all();

// 不排除软删除条目
$users = User::withTrashed()->get();

// 不排除软删除条目
$users = DB::table('users')->get();

SQL 声明

如果你需要执行一个简单的 SQL 查询,但没有得到任何结果 —— 比如改变数据库模式中的某些东西,只需执行 DB::statement()

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

使用数据库事务

如果您执行了两个数据库操作,第二个可能会出错,那么您应该回滚第一个,对吗?

为此,我建议使用 DB Transactions,它在 Laravel 中非常简单:

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

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

更新或创建

如果你需要检查记录是否存在,然后更新它,或者创建一个新记录,你可以用一句话来完成 - 使用 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 这样的缓存键,想在新增或更新时移除缓存键,可以在您的模型上调用静态的 saved 函数:

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 。

数组类型存储到 JSON 中

感谢 @pratiksh404 提供

如果你的输入字段有一个数组需要存储为 JSON 格式,你可以在模型中使用 $casts 属性。 这里的 images 是 JSON 属性。

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

这样你可以以 JSON 格式存储它,但当你从 DB 中读取时,它会以数组方式使用。

制作模型的副本

如果你有两个非常相似的模型(比如送货地址和账单地址),而且你想要复制其中一个作为另一个,你可以使用 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();

减少内存

有时我们需要将大量的数据加载到内存中,比如:

$orders = Order::all();

但如果我们有非常庞大的数据库,这可能会很慢,因为 Laravel 会准备好模型类的对象。在这种情况下,Laravel 有一个很方便的函数 toBase()

$orders = Order::toBase()->get();
//$orders 将包含 `Illuminate\Support\Collection` 与对象 `StdClass`

通过调用这个方法,它将从数据库中获取数据,但它不会准备模型类。同时,向 get() 方法传递一个字段数组通常是个好主意,这样可以防止从数据库中获取所有字段。

忽略 $fillable/$guarded 并强制查询

如果你创建了一个 Laravel 模板作为其他开发者的「启动器」, 并且你不能控制他们以后会在模型的 $fillable/$guarded 中填写什么,你可以使用 forceFill()

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

如果 name 不在团队模型的 $fillable 中,怎么办?或者如果根本就没有 $fillable/$guarded, 怎么办?

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

这将忽略该查询的 $fillable 并强制执行。

3 层父子级结构

如果你有一个 3 层的父子结构,比如电子商店中的分类,你想显示第三层的产品数量,你可以使用 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)

失败时执行任何操作

当查询一条记录时,如果没有找到,你可能想执行一些操作。除了用 ->firstOrFail() 会抛出 404 之外,你可以在失败时执行任何操作,只需要使用 ->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)

在删除模型之前执行任何额外的操作

感谢 @back2Lobby 提供

我们可以使用 Model::delete() 执行额外的操作来覆盖原本的删除方法。

// App\Models\User.php

public function delete(){

    //执行你想要的额外操作

    //然后进行正常的删除
    Model::delete();
}

当你需要在保存数据到数据库时自动填充一个字段

当你需要在保存数据到数据库时自动填充一个字段 (例如: slug),使用模型观察者来代替重复编写代码。

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() ) {
}

// 我不关心它有多少数据只要它是0
// Laravel 的 exists() 方法会很清晰:
if ( ! $model->where('status', 'pending')->exists() ) {
}

// 但我发现上面这条语句中的!很容易被忽略。
// 那么 doesntExist() 方法会让这个例子更加清晰
if ( $model->where('status', 'pending')->doesntExist() ) {
}

感谢 @ShawnHooper 提供

想要在一些模型的 boot () 方法中自动调用一个特性

如果你有一个特性,你想把它添加到几个模型中,自动调用它们的 boot() 方法,你可以把特质的方法作为 boot (特性名称)来调用。

class Transaction extends  Model
{
    use MultiTenantModelTrait;
}
class Task extends  Model
{
    use MultiTenantModelTrait;
}
trait MultiTenantModelTrait
{
    // 这个方法名是 boot[特性名称]。
    // 它将作为事务/任务的 boot() 被自动调用。
    public static function bootMultiTenantModelTrait()
    {
        static::creating(function ($model) {
            if (!$isAdmin) {
                $isAdmin->created_by_id = auth()->id();
            }
        })
    }
}

Laravel 的 find () 方法,比只传一个 ID 更多的选择

// 在 find($id) 方法中第二个参数可以是返回字段
Studdents::find(1, ['name', 'father_name']);
// 这样我们可以查询 ID 为 '1' 并返回 name , father_name 字段

// 我们可以用数组的方式传递更多的 ID
Studdents::find([1,2,3], ['name', 'father_name']);
// 输出: ID 为 1,2,3 并返回他们的 name , father_name 字段

在 Laravel 中,有两种常见的方法来确定一个表是否为空表

在 Laravel 中,有两种常见的方法来确定一个表是否为空表。直接在模型上使用 exists() 或者 count()

一个返回严格的布尔值,另一个返回一个整数,你都可以在条件语句中使用。

public function index()
{
    if (\App\Models\User::exists()) {
        // 如果表有任何保存的数据,则返回布尔值 true 或 false。
    }

    if (\App\Models\User::count()) {
        // 返回表中数据的数量。
    }
}

感谢 @aschmelyun 提供

如何防止 property of non-object 错误

// 设定默认模型
// 假设你有一篇 Post (帖子) 属于一个 Author (作者),代码如下:
$post->author->name;

// 当然你可以像这样阻止错误:
$post->author->name ?? ''
// 或者
@$post->auhtor->name

// 但你可以在Eloquent关系层面上做到这一点。
// 如果没有作者关联帖子,这种关系将返回一个空的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 数据改变后获取原始数据

Eloquent 模型数据改变后,你可以使用 getOriginal () 方法来获取原始数据。

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

感谢 @devThaer 提交

一种更简单创建数据库的方法

Laravel 还可以使用 .sql 文件来更简单的创建数据库。

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

感谢 @w3Nicolas 提交


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号