# 数据库模型与 Eloquent

## 复用或克隆 query

通常，我们需要从过滤后的查询中进行更多次查询。因此，大多数时候我们使用 query() 方法， 让我们编写一个查询来获取今天创建的可用和不可用的产品

```php
$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`。

```php
$active_products = $query->clone()->where('status', 1)->get(); // it will not modify the $query
$inactive_products = $query->clone()->where('status', 0)->get(); // so we will get inactive products from $query
```

## Eloquent where 日期方法

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

```php
$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。

```php
Post::find($post_id)->increment('view_count');
User::find($user_id)->increment('points', 50);
```

## 禁止 timestamp 列

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

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

## 软删除-多行恢复

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

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

## Model all-columns

当调用Eloquent's `Model::all()`时你可以指定返回哪些列。

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

## To Fail or not to Fail

除了 `findOrFail()` 之外，还有 Eloquent 方法 `firstOrFail()`，如果没有找到查询记录，它将返回 `404` 页面。

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

## 列名修改

在 `Eloquent Query Builder` 中，您可以像在普通 SQL 查询中一样指定`as`以返回任何列的别名。

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

## 过滤结果集合

在 `Eloquent` 查询到结果之后，您可以使用 Collections 中的 `map()` 函数来修改行数据。

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

## 修改默认的 Timestamp 字段

如果您使用的是非 `Laravel` 数据库并且时间戳列的名称不同怎么办？也许，你有 `create_time` 和 `update_time`。 幸运的是，您也可以在模型中指定它们：

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

## 按照 created\_at 快速排序

不用:

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

你可以更快的使用排序:

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

默认情况下 `latest()` 将按照 `created_at`排序。

有一个相反的方法 `oldest()`，按 created\_at 升序排序：

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

您也可以指定另一列进行排序。 例如，如果你想使用 `updated_at`，你可以这样做：

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

## 当创建记录时自动修改某些列的值

如果您想在创建记录时生成一些 DB 列值，请将其添加到模型的 boot() 方法中。 例如，如果您有一个字段 「position」，并且想要赋值下一个可用位置（如 Country::max('position') + 1)，请执行以下操作：

```php
class Country extends Model {
    protected static function boot()
    {
        parent::boot();

        Country::creating(function($model) {
            $model->position = Country::max('position') + 1;
        });
    }
}
```

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

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

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

## 不止一个范围

您可以在 Eloquent 中组合和链式调用查询范围，在一个 `query` 查询中使用多个范围。

Model文件内:

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

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

控制器内:

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

## 无需转换 Carbon

如果你正使用 `whereDate()` 查询今日的记录，可以直接使用 `Carbon` 的 `now()` 方法，它会自动转换为日期进行查询，而不需要指定 ->toDateString()。

```php
// 不用
$todayUsers = User::whereDate('created_at', now()->toDateString())->get();
// 不用做转换 只需要用 now()
$todayUsers = User::whereDate('created_at', now())->get();
```

## 根据首字母分组

你可以用任意自定义条件对 Eloquent 结果进行分组，下面的示例是由用户名的第一个单词进行分组:

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

## 永不更新某个字段

如果有一个数据库字段你想只设置一次并不想再次更新，您可以在Eloquent的模型上使用一个修改器设置该限制：

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

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

## find 查询多条数据

`find()` 方法可以接受多参数, 传入多个值时会返回所有找到记录的集合，而不是一个模型:

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

由 [@tahiriqbalnajam](https://twitter.com/tahiriqbalnajam/status/1436120403655671817) 提供

## find 多个模型并返回多列

`find` 方法可接受多参数 使得结果集返回指定列的模型集合，而不是模型的所有列:

```php
// Will return Eloquent Model with first_name and email only
$user = User::find(1, ['first_name', 'email']);
// Will return Eloquent Collection with first_name and email only
$users = User::find([1,2,3], ['first_name', 'email']);
```

由 [@tahiriqbalnajam](https://github.com/tahiriqbalnajam) 提供

## 按照键查找

您还可以使用 `whereKey()` 方法根据您指定的主键查找多条记录。(默认 `id` 但是你可以在 Eloquent 模型中覆盖掉)

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

## 使用 UUID 替换 auto-increment

您不想在模型中使用自动递增 ID？

迁移:

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

模型:

```php
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()`方法，对列进行一些计算。

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

## 隐藏某些列

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

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

## 确定 DB 报错

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

```php
try {
    // Some Eloquent/SQL statement
} catch (\Illuminate\Database\QueryException $e) {
    if ($e->getCode() === '23000') { // integrity constraint violation
        return back()->withError('Invalid data');
    }
}
```

## 软删除与查询构造器

注意 当你用到 `Eloquent` 时 软删除将会起作用，但是查询构造器不行。

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

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

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

## SQL 声明

如果你需要执行一个简单的 SQL 查询，但没有方案 —— 比如改变数据库模式中的某些东西，只需执行 DB::statement()。

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

## 数据库事务

如果您执行了两个数据库操作，第二个可能会出错，那么您应该回滚第一个，对吗？ 为此，我建议使用 DB Transactions，它在 Laravel 中非常简单：

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

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

## 更新或创建

如果你需要检查记录是否存在，然后更新它，或者创建一个新记录，你可以用一句话来完成 - 使用 Eloquent updateOrCreate() 方法：

```php
// Instead of this
$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
	]);
}
// Do it in ONE sentence
$flight = Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);
```

## 保存时移除缓存

如果您缓存了一个键存储了 `posts` 这个集合，想在新增或更新时移除缓存键，可以在您的模型上调用静态的 saved 函数：

```php
class Post extends Model
{
    // Forget cache key on storing or updating
    public static function boot()
    {
        parent::boot();
        static::saved(function () {
           Cache::forget('posts');
        });
    }
}
```

由 [@pratiksh404](https://github.com/pratiksh404) 提供

## 修改 Created\_at 和 Updated\_at 的格式

想要改变 `created_at` 的格式，您可以在模型中添加一个方法，如下所示:

```php
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`：

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

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

由 [@syofyanzuhad](https://github.com/syofyanzuhad) 提供

## 数组类型存储到 JSON 中

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

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

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

由 [@pratiksh404](https://github.com/pratiksh404) 提供

## 复制一个模型

如果你有两个非常相似的模型（比如送货地址和账单地址），而且你想要复制其中一个作为另一个，你可以使用 replicate() 方法并更改一部分属性。

[官方文档的示例](https://laravel.com/docs/8.x/eloquent#replicating-models)

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

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

$billing->save();
```

## 降低内存占用

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

```php
$orders = Order::all();
```

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

```php
$orders = Order::toBase()->get();
//$orders 将是一个由`StdClass`组成的 `Illuminate\Support\Collection`
```

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

## 忽略 $fillable / $guarded 并强制执行

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

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

如果 name 不在`team`模型的 `$fillable` 中，怎么办？或者如果根本就没有 `$fillable/$guarded`， 怎么办？

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

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

## 3 层父子级结构

If you have a 3-level structure of parent-children, like categories in an e-shop, and you want to show the number of products on the third level, you can use `with('yyy.yyy')` and then add `withCount()` as a condition

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

```php
class Category extends Model
{
    public function subcategories()
    {
        return $this->hasMany(Category::class);
    }
    
    public function products()
    {
    return $this->hasMany(Product::class);
    }
}
```

```php
<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 的集合来搜索更多的记录，方法如下：

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

这么做:

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

如果是整数使用 "whereIntegerInRaw" 比 "whereIn "快。

```php
Product::whereIn('id', range(1, 50))->get();
// You can do this
Product::whereIntegerInRaw('id', range(1, 50))->get();
```

由 [@sachinkiranti](https://raisachin.com.np) 提供

## 失败时执行任何操作

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

`->firstOr(function() { ... })`

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

## 检查记录是否存在否则显示 404

不要使用 find() ，然后再检查记录是否存在，使用 `findOrFail()`

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

更简单的方法:

```php
$product = Product::findOrFail($id); // shows 404 if not found
$product->update($productDataArray);
```

## 条件语句为否时中止

可以使用 `abort_if()` 作为判断条件和抛出错误页面的快捷方式。

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

更简单的方法:

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

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

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

```php
// App\Models\User.php

public function delete(){

	//extra steps here whatever you want
	
	//now perform the normal deletion
	Model::delete();
}
```

由[@back2Lobby](https://github.com/back2Lobby) 提供

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

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

```php
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](https://twitter.com/sky_0xs/status/1432390722280427521) 提供

## 获取查询语句的额外信息

你可以使用 `explain()` 方法来获取查询语句的额外信息

```php
Book::where('name', 'Ruskin Bond')->explain()->dd();
```

```php
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](https://twitter.com/amit_merchant/status/1432277631320223744) 提供

## 在 Laravel 中使用 doesntExist() 方法

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

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

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

由 [@ShawnHooper](https://twitter.com/ShawnHooper/status/1435686220542234626) 提供

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

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

```php
class Transaction extends  Model
{
    use MultiTenantModelTrait;
}
```

```php
class Task extends  Model
{
    use MultiTenantModelTrait;
}
```

```php
trait MultiTenantModelTrait
{
    // This method's name is boot[TraitName]
    // It will be auto-called as boot() of Transaction/Task
    public static function bootMultiTenantModelTrait()
    {
        static::creating(function ($model) {
            if (!$isAdmin) {
                $isAdmin->created_by_id = auth()->id();
            }
        })
    }
}
```

## Laravel 的 find 方法，比只传一个 ID 更多的选择

```php
// 在 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()` 不等于一个返回严格的布尔值，另一个返回一个整数，你都可以在条件语句中使用。

```php
public function index()
{
    if (\App\Models\User::exists()) {
        // returns boolean true or false if the table has any saved rows
    }
    
    if (\App\Models\User::count()) {
        // returns the count of rows in the table
    }
}
```

由 [@aschmelyun](https://twitter.com/aschmelyun/status/1440641525998764041) 提供

## 如何避免 property of non-object 错误

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

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

// 但你可以在Eloquent关系层面上做到这一点。
// 如果没有作者关联帖子，这种关系将返回一个空的App/Author模型。
public function author() {
    return $this->belongsTo('App\Author')->withDefault();
}
// 或者
public function author() {
    return $this->belongsTo('App\Author')->withDefault([
        'name' => 'Guest Author'
    ]);
}
```

由 [@coderahuljat](https://twitter.com/coderahuljat/status/1440556610837876741) 提供

## Eloquent 数据改变后获取原始数据

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

```php
$user = App\User::first();
$user->name; // John
$user->name = "Peter"; // Peter
$user->getOriginal('name'); // John
$user->getOriginal(); // Original $user record
```

由 [@devThaer](https://twitter.com/devThaer/status/1442133797223403521) 提供

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

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

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

由 [@w3Nicolas](https://twitter.com/w3Nicolas/status/1447902369388249091) 提供

## Query构造器的crossJoinSub方法

使用CROSS JOIN交叉连接

```php
use Illuminate\Support\Facades\DB;
$totalQuery = DB::table('orders')->selectRaw('SUM(price) as total');
DB::table('orders')
    ->select('*')
    ->crossJoinSub($totalQuery, 'overall')
    ->selectRaw('(price / overall.total) * 100 AS percent_of_total')
    ->get();
```

由 [@PascalBaljet](https://twitter.com/pascalbaljet) 提供

## belongsToMany 的中间表命名

为了决定 关系表的中间表, Eloquent 将按字母顺序连接两个相关的型号名称。

这意味着可以这样添加 “Post” 和 “Tag” 之间的连接：

```php
class Post extends Model
{
    public $table = 'posts';
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }
}
```

但是，您可以自由重写此约定，并且需要在第二个参数中指定联接表。

```php
class Post extends Model
{
    public $table = 'posts';
    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'posts_tags');
    }
}
```

如果希望明确说明主键，还可以将其作为第三个和第四个参数提供。

```php
class Post extends Model
{
    public $table = 'posts';
    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id');
    }
}
```

由 [@iammikek](https://twitter.com/iammikek) 提供

## 根据 Pivot 字段排序

`BelongsToMany::orderByPivot()` 允许你直接对`BelongsToMany` 关系查询的结果集进行排序。

```php
class Tag extends Model
{
    public $table = 'tags';
}
class Post extends Model
{
    public $table = 'posts';
    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'posts_tags', 'post_id', 'tag_id')
            ->using(PostTagPivot::class)
            ->withTimestamps()
            ->withPivot('flag');
    }
}
class PostTagPivot extends Pivot
{
    protected $table = 'posts_tags';
}
// Somewhere in the Controller
public function getPostTags($id)
{
    return Post::findOrFail($id)->tags()->orderByPivot('flag', 'desc')->get();
}
```

由 [@PascalBaljet](https://twitter.com/pascalbaljet) 提供

## 从数据库中查询一条记录

`sole()` 方法将会只返回一条匹配标准的记录。如果没找到，将会抛出`NoRecordsFoundException` 异常。如果发现了多条记录，抛出`MultipleRecordsFoundException` 异常

```php
DB::table('products')->where('ref', '#123')->sole();
```

由 [@PascalBaljet](https://twitter.com/pascalbaljet) 提供

## 记录自动分块

与 `each()` 相同，但是更简单使用。`chunks` 自动将记录分成多块。

```php
return User::orderBy('name')->chunkMap(fn ($user) => [
    'id' => $user->id,
    'name' => $user->name,
]), 25);
```

由[@PascalBaljet](https://twitter.com/pascalbaljet) 提供

## 定时清理过期记录中的模型

定期清理过时记录的模型。有了这个特性，Laravel 将自动完成这项工作，只需调整内核类中 `model:prune` 命令的频率

```php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
class Flight extends Model
{
    use Prunable;
    /**
     * Get the prunable model query.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function prunable()
    {
        return static::where('created_at', '<=', now()->subMonth());
    }
}
```

此外，在修剪方法中，可以设置删除模型之前必须执行的操作：

```php
protected function pruning()
{
    // Removing additional resources,
    // associated with the model. For example, files.
    Storage::disk('s3')->delete($this->filename);
}
```

由 [@PascalBaljet](https://twitter.com/pascalbaljet) 提供

## 不变的日期和对它们的强制转换

Laravel 8.53 介绍了 `immutable_date` 和`immutable_datetime` 将日期转换为 `Immutable`.

转换成 `CarbonImmutable`，而不是常规的 `Carbon` 实例。

```php
class User extends Model
{
    public $casts = [
        'date_field'     => 'immutable_date',
        'datetime_field' => 'immutable_datetime',
    ];
}
```

由 [@PascalBaljet](https://twitter.com/pascalbaljet) 提供

## findorfail方法也接收ids数组

findorfail 方法也接收 ids 数组。若无 ids 被找到 则失败。

若你想拿到一个模型的集合 并不想检测返回数量为你想得到的数量时很好用。

```php
User::create(['id' => 1]);
User::create(['id' => 2);
User::create(['id' => 3]);
// Retrives the user...
$user = User::findOrFail(1);
// Throws a 404 because the user doesn't exist...
User::findOrFail(99);
// Retrives all 3 users...
$users = User::findOrFail([1, 2, 3]);
// Throws because it is unable to find *all* of the users
User::findOrFail([1, 2, 3, 99]);
```

由 [@timacdonald87](https://twitter.com/timacdonald87/status/1457499557684604930) 提供

## 从你的数据库中自动移除模型 prunableTrait

Laravel 8.50新特性:

你可以使用 `prunable trait` 从你的数据库中自动移除模型。举例:你可以在几天后永久移除软删除的模型。

```php
class File extends Model
{
    use SoftDeletes;
    
    // Add Prunable trait
    use Prunable;
    
    public function prunable()
    {
        // Files matching this query will be pruned
        return static::query()->where('deleted_at', '<=', now()->subDays(14));
    }
    
    protected function pruning()
    {
        // Remove the file from s3 before deleting the model
        Storage::disk('s3')->delete($this->filename);
    }
}
// Add PruneCommand to your shedule (app/Console/Kernel.php)
$schedule->command(PruneCommand::class)->daily();
```

由 [@Philo01](https://twitter.com/Philo01/status/1457626443782008834) 提供

## 日期转换

当标记改变时 原来用布尔值来控制模型的可见性，现在可以使用`something_at` 替换。比如 一个产品变成可见:

```php
// Migration
Schema::table('products', function (Blueprint $table) {
    $table->datetime('live_at')->nullable();
});
// In your model
public function live()
{
    return !is_null($this->live_at);
}
// Also in your model
protected $dates = [
    'live_at'
];
```

由 [@alexjgarrett](https://twitter.com/alexjgarrett/status/1459174062132019212) 提供

## 多模型更新插入

`upsert()` 方法将插入/更新多个记录。

* 第一个参数数组:要更新/插入的值
* 第二个:查询表达式中使用的唯一标识列
* 第三个:若记录存在 你想要更新的列

```php
Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150],
], ['departure', 'destination'], ['price']);
```

由 [@mmartin\_joo](https://twitter.com/mmartin_joo/status/1461591319516647426) 提供

## 过滤结果集之后获取查询构造器

你可以使用 `toQuery()` 在过滤结果集之后获取查询构造器。

该方法在内部使用集合的第一个模型 并使用集合模型上的 “whereKey” 比较器。(此处翻译拗口 存疑。但是使用方法很明确。)

```php
// Retrieve all logged_in users
$loggedInUsers = User::where('logged_in', true)->get();
// Filter them using a Collection method or php filtering
$nthUsers = $loggedInUsers->nth(3);
// You can't do this on the collection
$nthUsers->update(/* ... */);
// But you can retrieve the Builder using ->toQuery()
if ($nthUsers->isNotEmpty()) {
    $nthUsers->toQuery()->update(/* ... */);
}
```

由 [@RBilloir](https://twitter.com/RBilloir/status/1462529494917566465) 提供

## 选择聚合计算相关模型

选择聚合计算相关模型。

需要指出的是在一组相关模型上使用 `count` 方法要慢一点。

```php
// In your controller
$user = User::withCount('articles');
// Or, to add a constraint to the aggregate
$user = User::withCount([
    'articles' => fn ($query) => $query->live();
]);
// In your view
$user->articles_count
// Instead of
$user->articles->count();
```

由 [@alexjgarrett](https://twitter.com/alexjgarrett/status/1462753602385108995) 提供

## 自定义强制转换

你可以自定义强制转换来让 `Laravel` 自动格式化你的模型数据。

下面是一个在检索或更改用户名时将其大写的示例。

```php
class CapitalizeWordsCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes)
    {
        return ucwords($value);
    }
    
    public function set($model, string $key, $value, array $attributes)
    {
        return ucwords($value);
    }
}
class User extends Model
{
    protected $casts = [
        'name'  => CapitalizeWordsCast::class,
        'email' => 'string',
    ]; 
}
```

由 [@mattkingshott](https://twitter.com/mattkingshott/status/1462828232206659586) 提供

## 保存中不要触发事件

若你不想触发模型事件 使用 `saveQuietly()` 方法

```php
public function quietly()
{
    $user = User::findOrFail(1);
    $user->name = 'Martin Joo';
    
    // Will not trigger any model event
    $user->saveQuietly();
}
```

由 [@mmartin\_joo](https://twitter.com/mmartin_joo/status/1465289689154265091) 提供

## 基于相关模型的平均值或总数排序

你是否曾需要基于关系模型的平均值或总数来排序？

这很简单

```php
public function bestBooks()
{
    Book::query()
        ->withAvg('ratings as average_rating', 'rating')
        ->orderByDesc('average_rating');
}
```

由 [@mmartin\_joo](https://twitter.com/mmartin_joo/status/1466769691385335815) 提供

## 返回事务结果

若你有一个 `DB` 事务 并且你想返回它的结果 至少有两种方法:

```php
// 1. You can pass the parameter by reference
$invoice = NULL;
DB::transaction(function () use (&$invoice) {
    $invoice = Invoice::create(...);
    $invoice->items()->attach(...);
})
// 2. Or shorter: just return trasaction result
$invoice = DB::transaction(function () {
    $invoice = Invoice::create(...);
    $invoice->items()->attach(...);
    
    return $invoice;
});
```

## 从 query 中移除多个公共 scope

当使用 `Global Scopes` 时 你不仅可以使用多个 `scope` 而且可以在不需要的时候通过提供的 `withoutGlobalScopes` 方法移除他们

[Link to docs](https://laravel.com/docs/8.x/eloquent#global-scopes)

## JSON 列属性排序

你可以使用 JSON 列属性排序

```php
// JSON column example:
// bikes.settings = {"is_retired": false}
$bikes = Bike::where('athlete_id', $this->athleteId)
        ->orderBy('name')
        ->orderByDesc('settings->is_retired')
        ->get();
```

由 [@brbcoding](https://twitter.com/brbcoding/status/1473353537983856643) 提供

## 从第一个结果中获取单列的值

你可以使用 `value` 方法从第一个结果中获取单列的值。

```php
// Instead of
Integration::where('name', 'foo')->first()->active;
// You can use
Integration::where('name', 'foo')->value('active');
// or this to throw an exception if no records found
Integration::where('name', 'foo')->valueOrFail('active')';
```

由 [@justsanjit](https://twitter.com/justsanjit/status/1475572530215796744) 提供

## 检测模型属性是否被修改

想知道您对模型所做的更改是否改变了键的值吗？没问题，只需`originalIsEquivalent` 方法即可。

```php
$user = User::first(); // ['name' => "John']
$user->name = 'John';
$user->originalIsEquivalent('name'); // true
$user->name = 'David'; // Set directly
$user->fill(['name' => 'David']); // Or set via fill
$user->originalIsEquivalent('name'); // false
```

由 [@mattkingshott](https://twitter.com/mattkingshott/status/1475843987181379599) 提供

## 定义访问器与修改器的新方法

Laravel 8.77:定义访问器与修改器的新方法

```php
// Before, two-method approach
public function setTitleAttribute($value)
{
    $this->attributes['title'] = strtolower($value);
}
public function getTitleAttribute($value)
{
    return strtoupper($value);
}
 
// New approach
protected function title(): Attribute
{
    return new Attribute(
        get: fn ($value) => strtoupper($value),
        set: fn ($value) => strtolower($value),
}
```

由 [@Teacoders](https://twitter.com/Teacoders/status/1473697808456851466) 提供

## 另外一种定义访问器与修改器的方法

在一些模型中想用同样的修改器 访问器 可以自定义转换。

只需要创建一个类 实现 `CastsAttributes` 实现两个方法

* get 标识模型应当从数据库如何拿到
* set 标识数据应当如何存储到数据库

```php
<?php
namespace App\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class TimestampsCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes)
    {
        return Carbon::parse($value)->diffForHumans();
    }
    public function set($model, string $key, $value, array $attributes)
    {
        return Carbon::parse($value)->format('Y-m-d h:i:s');
    }
}
```

然后你可以在模型中实现这个转换

```php
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Casts\TimestampsCast;
use Carbon\Carbon;
class User extends Authenticatable
{
    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'updated_at' => TimestampsCast::class,
        'created_at' => TimestampsCast::class,
    ];
}
```

由 [@AhmedRezk](https://github.com/AhmedRezk59) 提供

## 在搜索第一条记录时，你可以执行一些操作

当搜索第一条记录时，你想执行一些操作，当你没有找到它时。 `firstOrFail()` 抛出一个 404 异常。

你可以用 `firstOr(function() {})` 代替。 Laravel帮你解决了这个问题

```php
$book = Book::whereCount('authors')
            ->orderBy('authors_count', 'DESC')
            ->having('modules_count', '>', 10)
            ->firstOr(function() {
                // THe Sky is the Limit ...
                
                // You can perform any action here
            });
```

由 [@bhaidar](https://twitter.com/bhaidar/status/1487757487566639113/) 提供

## 直接将 created\_at 日期转换为人性化格式

你知道吗，你可以使用 diffForHumans() 函数直接将 created\_at 日期转换成人性化格式，如 1 分钟前，1 个月前。Laravel eloquent 默认在 created\_at 字段上启用 Carbon 实例。

```php
$post = Post::whereId($id)->first();
$result = $post->created_at->diffForHumans();

/* OUTPUT */
// 1 Minutes ago, 2 Week ago etc..as per created time
```

由 [@vishal\_\_2931](https://twitter.com/vishal__2931/status/1488369014980038662) 提供

## 通过获取器排序

我们不是按数据库级别排序，而是按返回的集合的获取器排序。

```php
class User extends Model
{
    // ...
    protected $appends = ['full_name'];
    
    public function getFullNameAttribute()
    {
        return $this->attribute['first_name'] . ' ' . $this->attributes['last_name'];
    }
    // ..
}
```

```php
class UserController extends Controller
{
    // ..
    public function index()
    {
        $users = User::all();
        
        // order by full_name desc
        $users->sortByDesc('full_name');
        
        // or
        
        // order by full_name asc
        $users->sortBy('full_name');
        
        // ..
    }
    // ..
}
```

`sortByDesc` 和 `sortBy` 方法在集合中。

由 [@bhaidar](https://twitter.com/bhaidar/status/1490671693618053123) 提供

## 创建或发现特定模型的检查

如果你想检查特定模型是否被创建或找到，使用 `wasRecentlyCreated` 模型属性。

```php
$user = User::create([
    'name' => 'Oussama',
]);

// return boolean
return $user->wasRecentlyCreated;

// true for recently created
// false for found (already on you db)
```

由 [@sky\_0xs](https://twitter.com/sky_0xs/status/1491141790015320064) 提供

## 带有数据库驱动的 Laravel Scout

使用 laravel v9，你可以使用 Laravel Scout（搜索）与数据库驱动程序。

```php
$companies = Company::search(request()->get('search'))->paginate(15);
```

由 [@magarrent](https://twitter.com/magarrent/status/1493221422675767302) 提供

## 使用查询生成器的 value 方法

当你只需要检索一个列时，利用查询生成器上的 `value` 方法来执行一个更有效的查询。

```php
// Before (fetches all columns on the row)
Statistic::where('user_id', 4)->first()->post_count;

// After (fetches only `post_count`)
Statistic::where('user_id', 4)->value('post_count');
```

由 [@mattkingshott](https://twitter.com/mattkingshott/status/1493583444244410375) 提供

## 将数组传给 where 方法

你可以传递一个数组给 where 方法。

```php
// Instead of this
JobPost::where('company', 'laravel')
        ->where('job_type', 'full time')
        ->get();

// You can pass an array
JobPost::where(['company' => 'laravel',
                'job_type' => 'full time'])
        ->get();
```

由 [@cosmeescobedo](https://twitter.com/cosmeescobedo/status/1495626752282234881) 提供

## 从模型集合中返回主键

你知道 eloquent 中的 `modelsKeys()` 集合方法吗？它从模型集合中返回主键。

```php
$users = User::active()->limit(3)->get();

$users->modelsKeys(); // [1, 2, 3]
```

由 [@iamharis010](https://twitter.com/iamharis010/status/1495816807910891520) 提供

## 永久关闭懒加载

如果你想在你的应用程序中阻止懒加载，你只需要在你的 "AppServiceProvider" 的 "boot() " 方法中添加以下一行

```php
Model::preventLazyLoading();
```

但是，如果你想只在你的本地开发中启用这个功能，你可以改变上述代码。

```php
Model::preventLazyLoading(!app()->isProduction());
```

由 [@CatS0up](https://github.com/CatS0up) 提供

## 使你所有的模型的字段批量分配

出于安全原因，这不是一个推荐的方法，但它是可能的。

当你想这样做时，你不需要为每个模型设置一个空的 `$guarded` 数组，像这样：

```php
protected $guarded = [];
```

只要在你的 "AppServiceProvider" 中的 "boot() " 方法中添加以下一行：

```php
Model::unguard();
```

现在，你的所有模型都是可以自动分配的。

由 [@CatS0up](https://github.com/CatS0up) 提供

## 在查询语句中隐藏列

如果你使用 Laravel v8.78 和 MySQL 8.0.23 及以上版本, 你可以将选择的列定义为 "invisible"。被定义为 "invisible" 的列将被隐藏在 "select \*" 语句中。

然而，要做到这一点，我们必须在迁移中使用一个 `invisible()` 方法。

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('table', function (Blueprint $table) {
    $table->string('secret')->nullable()->invisible();
});
```

这将使选择的列从 "select \*" 语句中隐藏

由 [@CatS0up](https://github.com/CatS0up) 提供
