[关闭]
@Chiang 2019-12-14T10:08:31.000000Z 字数 12377 阅读 634

Eloquent ORM - 快速入门

Laravel


定义模型

开始之前,让我们先来创建一个 Eloquent 模型。模型通常放在 app 目录中,不过你可以将他们随意放在任何可通过 composer.json 自动加载的地方。所有的 Eloquent 模型都继承自 Illuminate\Database\Eloquent\Model 类

  1. // 创建模型实例的最简单方法是使用 make:model Artisan 命令
  2. php artisan make:model User
  3. // 当你生成一个模型时想要顺便生成一个 数据库迁移,可以使用 --migration 或 -m 选项
  4. php artisan make:model User --migration
  5. php artisan make:model User -m

Eloquent 模型约定

数据表名称

请注意,我们并没有告诉 Eloquent Flight 模型该使用哪一个数据表。除非数据表明确地指定了其它名称,否则将使用类的「蛇形名称」、复数形式名称来作为数据表的名称。因此在此例子中,Eloquent 将会假设 Flight 模型被存储记录在 flights 数据表中。你可以在模型上定义一个 table 属性,用来指定自定义的数据表名称

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. /**
  7. * 与模型关联的数据表
  8. *
  9. * @var string
  10. */
  11. protected $table = 'my_flights';
  12. }

主键

  • Eloquent 也会假设每个数据表都有一个叫做id的主键字段。你也可以定义一个 $primaryKey 属性来重写这个约定

  • 此外,Eloquent 假定主键是一个递增的整数值,这意味着在默认情况下主键将自动的被强制转换为 int。 如果你想使用非递增或者非数字的主键,你必须在你的模型 public $incrementing 属性设置为false。

时间戳

默认情况下,Eloquent 会认为在你的数据库表有 created_at 和 updated_at 字段。如果你不希望让 Eloquent 来自动维护这两个字段,可在模型内将 $timestamps 属性设置为 false

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. /**
  7. * 该模型是否被自动维护时间戳
  8. *
  9. * @var bool
  10. */
  11. public $timestamps = false;
  12. }

自定义时间格式

如果你需要自定义自己的时间戳格式,可在模型内设置 $dateFormat 属性。这个属性决定了日期应如何在数据库中存储,以及当模型被序列化成数组或 JSON 格式

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. /**
  7. * 模型的日期字段保存格式。
  8. *
  9. * @var string
  10. */
  11. protected $dateFormat = 'U';
  12. }

自定义时间戳字段名

如果你需要自定义用于存储时间戳的字段名,可在模型中设置 CREATED_AT 和 UPDATED_AT 常量

  1. <?php
  2. class Flight extends Model
  3. {
  4. const CREATED_AT = 'creation_date';
  5. const UPDATED_AT = 'last_update';
  6. }

数据库连接

默认情况下,所有的 Eloquent 模型会使用应用程序中默认的数据库连接设置。如果你想为模型指定不同的连接,可以使用 $connection 属性

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. /**
  7. * 此模型的连接名称。
  8. *
  9. * @var string
  10. */
  11. protected $connection = 'connection-name';
  12. }

取回多个模型

  1. <?php
  2. use App\Flight;
  3. $flights = App\Flight::all();
  4. foreach ($flights as $flight) {
  5. echo $flight->name;
  6. }
  1. $flights = App\Flight::where('active', 1)
  2. ->orderBy('name', 'desc')
  3. ->take(10)
  4. ->get();

集合(Collection)

类似 all 以及 get 之类的可以取回多个结果的 Eloquent 方法,将会返回一个 Illuminate\Database\Eloquent\Collection 实例。Collection 类提供 多种辅助函数 来处理你的 Eloquent 结果。

  1. $flights = $flights->reject(function ($flight) {
  2. return $flight->cancelled;
  3. });
  1. // 像数组一样遍历集合
  2. foreach ($flights as $flight) {
  3. echo $flight->name;
  4. }

分块结果

  • 如果你需要处理数以千计的 Eloquent 查找结果,则可以使用 chunk 命令。chunk 方法将会获取一个 Eloquent 模型的「分块」,并将它们送到指定的 闭包 (Closure) 中进行处理。当你在处理大量结果时,使用 chunk 方法可节省内存
  • 传递到方法的第一个参数表示每次「分块」时你希望接收的数据数量。闭包则作为第二个参数传递,它将会在每次从数据取出分块时被调用。
  1. Flight::chunk(200, function ($flights) {
  2. foreach ($flights as $flight) {
  3. //
  4. }
  5. });

使用游标

cursor 允许你使用游标来遍历数据库数据,一次只执行单个查询。在处理大数据量请求时 cursor 方法可以大幅度减少内存的使用

  1. foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
  2. //
  3. }

取回单个模型/集合

当然,除了从指定的数据表取回所有记录,你也可以通过 find 和 first 方法来取回单条记录。但这些方法返回的是单个模型的实例,而不是返回模型的集合

  1. // 通过主键取回一个模型...
  2. $flight = App\Flight::find(1);
  3. // 取回符合查询限制的第一个模型 ...
  4. $flight = App\Flight::where('active', 1)->first();
  5. // 你也可以用主键的集合为参数调用find方法,它将返回符合条件的集合
  6. $flights = App\Flight::find([1, 2, 3]);

「未找到」异常 findOrFail firstOrFail

  1. $model = App\Flight::findOrFail(1);
  2. $model = App\Flight::where('legs', '>', 100)->firstOrFail();
  3. Route::get('/api/flights/{id}', function ($id) {
  4. return App\Flight::findOrFail($id);
  5. });

取回集合

当然,你也可以使用 count、sum、max,和其它 查询构造器 提供的 聚合函数。这些方法会返回适当的标量值,而不是一个完整的模型实例

  1. $count = App\Flight::where('active', 1)->count();
  2. $max = App\Flight::where('active', 1)->max('price');

添加和更新模型

基本添加

  • 要在数据库中创建一条新记录,只需创建一个新模型实例,并在模型上设置属性和调用 save 方法即可
  • 在这个例子中,我们把来自 HTTP 请求中的 name 参数简单地指定给 App\Flight 模型实例的 name 属性。当我们调用 save 方法,就会添加一条记录到数据库中。当 save 方法被调用时,created_at 以及 updated_at 时间戳将会被自动设置,因此我们不需要去手动设置它们。
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Flight;
  4. use Illuminate\Http\Request;
  5. use App\Http\Controllers\Controller;
  6. class FlightController extends Controller
  7. {
  8. /**
  9. * 创建一个新的航班实例。
  10. *
  11. * @param Request $request
  12. * @return Response
  13. */
  14. public function store(Request $request)
  15. {
  16. // 验证请求...
  17. $flight = new Flight;
  18. $flight->name = $request->name;
  19. $flight->save();
  20. }
  21. }

基本更新

save 方法也可以用于更新数据库中已经存在的模型。要更新模型,则须先取回模型,再设置任何你希望更新的属性,接着调用 save 方法。同样的,updated_at 时间戳将会被自动更新,所以我们不需要手动设置它的值

  1. $flight = App\Flight::find(1);
  2. $flight->name = 'New Flight Name';
  3. $flight->save();

批量更新

  • 也可以针对符合指定查询的任意数量模型进行更新。在这个例子中,所有 active 并且 destination 为 San Diego 的航班,都将会被标识为延迟
  • update 方法会期望收到一个含有字段与值对应的数组,而这些字段的内容将会被更新。
  • 当通过“Eloquent”批量更新时,saved和updated模型事件将不会被更新后的模型代替。这是因为批量更新时,模型从来没有被取回。
  1. App\Flight::where('active', 1)
  2. ->where('destination', 'San Diego')
  3. ->update(['delayed' => 1]);

批量赋值

  • 你也可以使用 create 方法通过一行代码来保存一个新模型。被插入数据库的模型实例将会返回给你。不过,在这样做之前,你需要先在你的模型上定义一个 fillable 或 guarded 属性,因为所有的 Eloquent 模型都针对批量赋值(Mass-Assignment)做了保护。
  • 当用户通过 HTTP 请求传入了非预期的参数,并借助这些参数更改了数据库中你并不打算要更改的字段,这时就会出现批量赋值(Mass-Assignment)漏洞。例如,恶意用户可能会通过 HTTP 请求发送 is_admin 参数,然后对应到你模型的 create 方法,此操作能让该用户把自己升级为一个管理者。
  • 所以,在开始之前,你应该定义好哪些模型属性是可以被批量赋值的。你可以在模型上使用 $fillable 属性来实现。例如,让我们让 Flight 模型的 name 属性可以被批量赋值
  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. /**
  7. * 可以被批量赋值的属性。
  8. *
  9. * @var array
  10. */
  11. protected $fillable = ['name'];
  12. }

一旦我们已经设置好可以被批量赋值的属性,便能通过 create 方法来添加一条新记录到数据库。create 方法将返回已经被保存的模型实例

  1. $flight = App\Flight::create(['name' => 'Flight 10']);

如果你已经有一个 model 实例,你可以使用一个数组传递给 fill 方法

  1. $flight->fill(['name' => 'Flight 22']);

防护属性(Guarding Attributes)

$fillable 作为一个可以被批量赋值的属性「白名单」。另外你也可以选择使用 $guarded$guarded 属性应该包含一个你不想要被批量赋值的属性数组。所有不在数组里面的其它属性都可以被批量赋值。因此,$guarded 的功能更类似一个「黑名单」。使用的时候应该只选择 $fillable$guarded 中的其中一个。 下面这个例子中,除了 price 所有的属性都可以被批量赋值

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. /**
  7. * 不可被批量赋值的属性。
  8. *
  9. * @var array
  10. */
  11. protected $guarded = ['price'];
  12. }

如果你想让所有的属性都可以被批量赋值,你应该定义 $guarded为空数组。

  1. /**
  2. * 不可被批量赋值的属性。
  3. *
  4. * @var array
  5. */
  6. protected $guarded = [];

其他创建的方法

firstOrCreate/ firstOrNew

  • 还有两种其它方法,你可以用来通过属性批量赋值创建你的模型:firstOrCreate 和 firstOrNew。firstOrCreate 方法将会使用指定的字段/值对,来尝试寻找数据库中的记录。如果在数据库中找不到模型,则会使用指定的属性来添加一条记录。
  • firstOrNew 方法类似 firstOrCreate 方法,它会尝试使用指定的属性在数据库中寻找符合的纪录。如果模型未被找到,将会返回一个新的模型实例。请注意 firstOrnew 返回的模型还尚未保存到数据库。你需要通过手动调用 save 方法来保存它
  1. // 通过name属性检索航班,当结果不存在时创建它...
  2. $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
  3. // 通过name属性检索航班,当结果不存在的时候用name属性和delayed属性去创建它
  4. $flight = App\Flight::firstOrCreate(
  5. ['name' => 'Flight 10'], ['delayed' => 1]
  6. );
  7. // 通过name属性检索航班,当结果不存在时实例化一个新实例...
  8. $flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
  9. // 通过name属性检索航班,当结果不存在的时候用name属性和delayed属性去实例化一个新实例
  10. $flight = App\Flight::firstOrNew(
  11. ['name' => 'Flight 10'], ['delayed' => 1]
  12. );

updateOrCreate

其次,你可能会碰到模型已经存在则更新,否则创建新模型的情形,Laravel 提供了一个 updateOrCreate 方法来一步完成该操作,类似 firstOrCreate 方法, updateOrCreate 方法会持久化模型,所以无需调用 save()

  1. // If there's a flight from Oakland to San Diego, set the price to $99.
  2. // If no matching model exists, create one.
  3. $flight = App\Flight::updateOrCreate(
  4. ['departure' => 'Oakland', 'destination' => 'San Diego'],
  5. ['price' => 99]
  6. );

删除模型

要删除模型,必须在模型实例上调用 delete 方法

  1. $flight = App\Flight::find(1);
  2. $flight->delete();

通过主键来删除现有的模型

在上面的例子中,我们在调用 delete 方法之前会先从数据库中取回模型。不过,如果你已知道了模型中的主键,则可以不用取回模型就能直接删除它。若要直接删除,请调用 destroy 方法

  1. App\Flight::destroy(1);
  2. App\Flight::destroy([1, 2, 3]);
  3. App\Flight::destroy(1, 2, 3);

通过查询来删除模型

  • 当然,你也可以运行在一组模型删除查询。在这个例子中,我们会删除被标记为不活跃的所有航班。 像批量更新那样,批量删除不会删除的任何被删除的模型的事件
  • 当使用 Eloquent 批量删除语句时,deleting 和 deleted 模型事件不会在被删除模型实例上触发。因为删除语句执行时,不会检索回模型实例。
  1. $deletedRows = App\Flight::where('active', 0)->delete();

软删除

除了从数据库中移除实际记录,Eloquent 也可以「软删除」模型。当模型被软删除时,它们并不会真的从数据库中被移除。而是会在模型上设置一个 deleted_at 属性并将其添加到数据库。如果模型有一个非空值 deleted_at,代表模型已经被软删除了。要在模型上启动软删除,则必须在模型上使用 Illuminate\Database\Eloquent\SoftDeletes trait 并添加 deleted_at 字段到你的 $dates 属性上

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Database\Eloquent\SoftDeletes;
  5. class Flight extends Model
  6. {
  7. use SoftDeletes;
  8. /**
  9. * 需要被转换成日期的属性。
  10. *
  11. * @var array
  12. */
  13. protected $dates = ['deleted_at'];
  14. }

当然,你也应该添加 deleted_at 字段到数据表中。Laravel 结构生成器 包含了一个用来创建此字段的辅助函数

  1. Schema::table('flights', function ($table) {
  2. $table->softDeletes();
  3. });

现在,当你在模型上调用 delete 方法时,deleted_at 字段将会被设置成目前的日期和时间。而且,当查询有启用软删除的模型时,被软删除的模型将会自动从所有查询结果中排除。

要确认指定的模型实例是否已经被软删除,可以使用 trashed 方法

  1. if ($flight->trashed()) {
  2. //
  3. }

查询被软删除的模型

包含被软删除的模型

如上所述,被软删除的模型将会自动从所有的查询结果中排除。不过,你可以通过在查询中调用 withTrashed 方法来强制查询已被软删除的模型

  1. $flights = App\Flight::withTrashed()
  2. ->where('account_id', 1)
  3. ->get();

withTrashed 方法也可以被用在 关联 查询

  1. $flight->history()->withTrashed()->get();

只取出软删除数据

onlyTrashed 会只取出软删除数据

  1. $flights = App\Flight::onlyTrashed()
  2. ->where('airline_id', 1)
  3. ->get();

恢复被软删除的数据

有时候你可能希望「取消删除」一个已被软删除的模型。要恢复一个已被软删除的模型到有效状态,则可在模型实例上使用 restore 方法

  1. $flight->restore();

你也可以在查询上使用 restore 方法来快速地恢复多个模型

  1. App\Flight::withTrashed()
  2. ->where('airline_id', 1)
  3. ->restore();

与 withTrashed 方法类似,restore 方法也可以被用在 关联 查询上

  1. $flight->history()->restore();

永久的删除模型

有时候你可能需要真正地从数据库移除模型。要永久地从数据库移除一个已被软删除的模型,则可使用 forceDelete 方法

  1. // 强制删除单个模型实例...
  2. $flight->forceDelete();
  3. // 强制删除所有相关模型...
  4. $flight->history()->forceDelete();

查询作用域

全局作用域

全局作用域允许我们为给定模型的所有查询添加条件约束。Laravel 自带的 软删除功能 就使用了全局作用域来从数据库中拉出所有没有被删除的模型。编写自定义的全局作用域可以提供一种方便的、简单的方式,来确保给定模型的每个查询都有特定的条件约束。

编写全局作用域

自定义全局作用域很简单,首先定义一个实现 Illuminate\Database\Eloquent\Scope 接口的类,该接口要求你实现一个方法:apply。需要的话可以在 apply 方法中添加 where 条件到查询

  1. <?php
  2. namespace App\Scopes;
  3. use Illuminate\Database\Eloquent\Scope;
  4. use Illuminate\Database\Eloquent\Model;
  5. use Illuminate\Database\Eloquent\Builder;
  6. class AgeScope implements Scope
  7. {
  8. /**
  9. * 应用作用域
  10. *
  11. * @param \Illuminate\Database\Eloquent\Builder $builder
  12. * @param \Illuminate\Database\Eloquent\Model $model
  13. * @return void
  14. */
  15. public function apply(Builder $builder, Model $model)
  16. {
  17. return $builder->where('age', '>', 200);
  18. }
  19. }

Laravel 没有规定你需要把这些类放置于哪个文件夹,你可以自由在 app 文件夹下创建 Scopes 文件夹来存放。

应用全局作用域

要将全局作用域分配给模型,需要重写给定模型的 boot 方法并使用 addGlobalScope 方法

  1. <?php
  2. namespace App;
  3. use App\Scopes\AgeScope;
  4. use Illuminate\Database\Eloquent\Model;
  5. class User extends Model
  6. {
  7. /**
  8. * 数据模型的启动方法
  9. *
  10. * @return void
  11. */
  12. protected static function boot()
  13. {
  14. parent::boot();
  15. static::addGlobalScope(new AgeScope);
  16. }
  17. }

添加作用域后,如果使用 User::all() 查询则会生成如下SQL语句

  1. select * from `users` where `age` > 200

匿名的全局作用域

Eloquent 还允许我们使用闭包定义全局作用域,这在实现简单作用域的时候特别有用,这样的话,我们就没必要定义一个单独的类了

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Database\Eloquent\Builder;
  5. class User extends Model
  6. {
  7. /**
  8. * 数据模型的启动方法
  9. *
  10. * @return void
  11. */
  12. protected static function boot()
  13. {
  14. parent::boot();
  15. static::addGlobalScope('age', function(Builder $builder) {
  16. $builder->where('age', '>', 200);
  17. });
  18. }
  19. }

我们还可以通过以下方式,利用 age 标识符来移除全局作用

  1. User::withoutGlobalScope('age')->get();

移除全局作用域

如果想要在给定查询中移除指定全局作用域,可以使用 withoutGlobalScope

  1. User::withoutGlobalScope(AgeScope::class)->get();

如果你想要移除某几个或全部全局作用域,可以使用 withoutGlobalScopes 方法

  1. User::withoutGlobalScopes()->get();
  2. User::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get();

本地作用域

本地作用域允许我们定义通用的约束集合以便在应用中复用。例如,你可能经常需要获取最受欢迎的用户,要定义这样的一个作用域,只需简单在对应 Eloquent 模型方法前加上一个 scope 前缀,作用域总是返回查询构建器

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class User extends Model
  5. {
  6. /**
  7. * 限制查询只包括受欢迎的用户。
  8. *
  9. * @return \Illuminate\Database\Eloquent\Builder
  10. */
  11. public function scopePopular($query)
  12. {
  13. return $query->where('votes', '>', 100);
  14. }
  15. /**
  16. * 限制查询只包括活跃的用户。
  17. *
  18. * @return \Illuminate\Database\Eloquent\Builder
  19. */
  20. public function scopeActive($query)
  21. {
  22. return $query->where('active', 1);
  23. }
  24. }

利用查询范围

一旦定义了范围,则可以在查询模型时调用范围方法。在进行方法调用时不需要加上 scope 前缀。你甚至可以链式调用不同的范围

  1. $users = App\User::popular()->active()->orderBy('created_at')->get();

动态范围

有时候,你可能希望定义一个可接受参数的范围。这时只需给你的范围加上额外的参数即可。范围参数应该被定义在 $query 参数之后

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class User extends Model
  5. {
  6. /**
  7. * 限制查询只包括指定类型的用户。
  8. *
  9. * @return \Illuminate\Database\Eloquent\Builder
  10. */
  11. public function scopeOfType($query, $type)
  12. {
  13. return $query->where('type', $type);
  14. }
  15. }

现在,你可以在范围调用时传递参数

  1. $users = App\User::ofType('admin')->get();

事件

  • Eloquent 模型会触发许多事件,让你在模型的生命周期的多个时间点进行监控: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.
  • 事件让你每当有特定的模型类在数据库保存或更新时,执行代码。
  • 当一个新模型被初次保存将会触发 creating 以及 created 事件。如果一个模型已经存在于数据库且调用了 save 方法,将会触发 updating 和 updated 事件。在这两种情况下都会触发 saving 和 saved 事件。
  • 开始前,在你的 Eloquent 模型上定义一个 $dispatchesEvents 属性,将 Eloquent 模型的生命周期的各个点映射到你的 事件类 中。
  1. <?php
  2. namespace App;
  3. use App\Events\UserSaved;
  4. use App\Events\UserDeleted;
  5. use Illuminate\Notifications\Notifiable;
  6. use Illuminate\Foundation\Auth\User as Authenticatable;
  7. class User extends Authenticatable
  8. {
  9. use Notifiable;
  10. /**
  11. * 模型的事件映射。
  12. *
  13. * @var array
  14. */
  15. protected $dispatchesEvents = [
  16. 'saved' => UserSaved::class,
  17. 'deleted' => UserDeleted::class,
  18. ];
  19. }

观察者

如果你在一个给定的模型中监听许多事件,您可以使用观察者将所有监听器变成一个类。观察者类里的方法名应该反映Eloquent想监听的事件。 每种方法接收 model 作为其唯一的参数。 Laravel不包括观察者默认目录,所以你可以创建任何你喜欢你的目录来存放

  1. <?php
  2. namespace App\Observers;
  3. use App\User;
  4. class UserObserver
  5. {
  6. /**
  7. * 监听用户创建的事件。
  8. *
  9. * @param User $user
  10. * @return void
  11. */
  12. public function created(User $user)
  13. {
  14. //
  15. }
  16. /**
  17. * 监听用户删除事件。
  18. *
  19. * @param User $user
  20. * @return void
  21. */
  22. public function deleting(User $user)
  23. {
  24. //
  25. }
  26. }

要注册一个观察者,需要用模型中的observe方法去观察。你可以在你的服务提供商之一的boot方法中注册观察者。在这个例子中,我们将在AppServiceProvider注册观察者

  1. <?php
  2. namespace App\Providers;
  3. use App\User;
  4. use App\Observers\UserObserver;
  5. use Illuminate\Support\ServiceProvider;
  6. class AppServiceProvider extends ServiceProvider
  7. {
  8. /**
  9. * 运行所有应用.
  10. *
  11. * @return void
  12. */
  13. public function boot()
  14. {
  15. User::observe(UserObserver::class);
  16. }
  17. /**
  18. * 注册服务提供.
  19. *
  20. * @return void
  21. */
  22. public function register()
  23. {
  24. //
  25. }
  26. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注