[Laravel] 16 - DB: Eloquent

时间:2023-02-04 22:49:48

前言


一、大纲

写后端API,与数据库打交道无疑是很重要的角色。

PHP数据库操作:从MySQL原生API到PDO

PHP数据库操作:使用ORM

Ref: [PHP] 07 - Json, XML and MySQL

二、细节

SQL 教程

三、初识 Eloquent ORM

Eloquent ORM是Laravel框架使用的ORM。Laravel 的 Eloquent ORM 提供了更优雅的ActiveRecord 实现来和数据库的互动。每个数据库表对应一个模型文件。

Goto: Eloquent: 入门【2.完整复习版本】

From: Eloquent ORM笔记【1.基础篇】

新增
# 新建了一条 id, name
$user = new User;
$user->name = 'John';
$user->save();
$insertedId = $user->id;//从对象取得 id 属性值 使用模型的 Create 方法
class User extends Model {
protected $guarded = ['id', 'account_id'];  //黑名单,不会被更新
} // 建立一个用户
$user = User::create(['name' => 'John']); // 以属性找用户 - 若没有则新增并取得新的实例...
$user = User::firstOrCreate(['name' => 'John']); // 以属性找用户 - 若没有则建立新的实例...
$user = User::firstOrNew(['name' => 'John']); 辨析
firstOrCreate:判断之后直接入库
firstOrNew :判断之后还要做其他业务流程,之后再入库
$student=Student::firstOrNew(['vip_name'=>'mmm']); 
$student->save();  # 就是需要再单独save一下
删除
$this->where($where)->delete(); 或者
$user = User::find(1);
$user->delete(); 更新
return $this->where($where)->update($data); 或者
$user = User::find(1);
$user->update($data); 查找
//取出所有记录 - 可以遍历
$this->all()->toArray(); //取出一条数据
$one = $this->find('2');
return array(
$one->id,
$one->title,
$one->content,
); //查找id=2的第一条数据
$this->where('id', 2)->first()->toArray(); //查找id>0的所有数据
$this->where('id', '>', '0')->get()->toArray();
  • For find(n), you retrieve a row based on the primary key which is 'n'.
  • For first(), you retrieve the first row among all rows that fit the where clauses.
  • For get()  , you retrieve all the rows that fit the where clauses. (Please note that loops are required to access all the rows or you will get some errors).
//降序排列
$this->where('id', '>', '0')->orderBy('id', 'desc')->get()->toArray(); //降序排列,计数
$this->where('id', '>', '0')->orderBy('id', 'desc')->count(); //从offset起始算起的limit条数据
$this->where('id', '>', '0')->orderBy($order[0], $order[1])->skip($offset)->take($limit);
//等同于
$this->where('id', '>', '0')->orderBy($order[0], $order[1])->offset($offset)->limit($limit); 条件类
//条件类
where('id', '>', '0')
where('id', '>=', '0')
where('id', '<', '0')
where('id', '<=', '0')
where('id', 'like', 'name%') whereIn($key, $array)
whereNotIn($key, $array)
whereBetween($key, $array)
whereNotBetween($key, $array) orWhereIn($key, $array)
orWhereNotIn($key, $array)
orWhereBetween($key, $array)
orWhereNotBetween($key, $array) 结果方法
//结果方法:Illuminate\Database\Query\Builder
first()取第一个
get()取所有
all()取所有(无条件) 数学统计
//聚合方法
count()
avg()
sum()
max()
min()

Pure SQL


RDBMS 指关系型数据库管理系统,全称 Relational Database Management System。

(1). 登录:mysql -u root -p 

(2). phpMyAdmin创建数据库,并导入.sql文件。

(3). 支持中文:set names utf8;

详见:[MySQL] 01- Basic sql

Eloquent: 入门


{tip} 由于 Eloquent 模型是查询构造器,因此你应当去阅读所有 查询构造器 中可用的方法。

一、模型

  • 生成模型、迁移文件
php artisan make:model User --migration
php artisan make:model User -m
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;  # 所有的 Eloquent 模型都继承自这个

class Flight extends Model
{
// 用它从 flights 数据表中取回与保存信息
}
  • 默认常用四属性

[1] 表名

除非数据表明确地指定了其它名称,否则将使用类的「蛇形名称」、复数形式名称来作为数据表的名称。自定义对应表名:

protected $table = 'my_flights'; 

[2] 主键

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

默认是递增;否则,必须在你的模型 public $incrementing=false

[3] 时间戳

默认情况下,Eloquent 会认为在你的数据库表有 created_at 和 updated_at 字段。

如果你不希望让 Eloquent 来自动维护这两个字段,可在模型内将 $timestamps=false。

决定了日期应如何在数据库中存储,以及当模型被序列化成数组或 JSON 格式 protected $dateFormat = 'U';

[4] 连接数据库

默认情况下,所有的 Eloquent 模型会使用应用程序中默认的数据库连接设置。

如果你想为模型指定不同的连接,可以使用 protected $connection = 'connection-name';

[5] 通过模型获取结果

$flights = App\Flight::where('active', 1) ->orderBy('name', 'desc') ->take(10) ->get();
  • 模型创建与删除

详见:"三、初识 Eloquent ORM"

补充:软删除

模型上设置一个 deleted_at 属性并将其添加到数据库。如果模型有一个非空值 deleted_at,代表模型已经被软删除了。

[1] 设置字段

当模型被软删除时,它们并不会真的从数据库中被移除。而是会在模型上设置一个 deleted_at 属性并将其添加到数据库。

如果模型有一个非空值 deleted_at,代表模型已经被软删除了。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model
{
use SoftDeletes; /**
* 需要被转换成日期的属性。
* 添加 deleted_at 字段到你的 $dates 属性。
*
* @var array
*/
protected $dates = ['deleted_at'];
}

[2] 添加 deleted_at 字段到数据表

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

当查询有启用软删除的模型时,被软删除的模型将会自动从所有查询结果中排除。

[3] 查询 deleted_at 字段

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

if ($flight->trashed()) {
//
}

[4] 查询捎带上 '软删除‘ 集合

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

[5] 只查询 ‘软删除’ 集合

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

[6] 恢复 '软删除'

$flight->restore();

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

[7] 永久地删除模型

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

二、集合

Collection 类提供 多种辅助函数 来处理你的 Eloquent 结果。

  • 遍历 Chunk & Cursor

[Chunk]

It will "paginate" your query, this way you use less memory. (1) Uses less memory (2) It takes longer

public function getData() {
Contact::chunk(1000, function ($contacts) {
foreach ($contacts as $contact) {
//rest of your code...
}
});
}

[Cursor]

You will use PHP Generators to search your query items one by one. (1) It takes less time (2) Uses more memory

public function getData() {
foreach (Contact::cursor() as $contact) {
//rest of your code...
}
}
  • 返回指定行
// 取回符合查询限制的第一个模型 ...
$flight = App\Flight::where('active', 1)->first(); $flights = App\Flight::find([1, 2, 3]);
  • ‘未找到’ 处理
$model = App\Flight::findOrFail(1);
$model = App\Flight::where('legs', '>', 100)->firstOrFail();

// 用法示范
Route::get('/api/flights/{id}', function ($id) {
  return App\Flight::findOrFail($id);
});
  • 聚合函数

可以使用 countsummax,和其它 查询构造器 提供的 聚合函数

这些方法会返回适当的标量值,而不是一个完整的模型实例

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

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

三、控制器

  • 添加一条记录

自动完成了例如对时间戳的记录。

<?php

namespace App\Http\Controllers;

use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; class FlightController extends Controller
{
/**
* 创建一个新的航班实例。
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
  • 更新 & 批量更新
$flight = App\Flight::find(1);

$flight->name = 'New Flight Name';
$flight->save();

两个where并列条件,然后更新字段:delayed

App\Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);

{note} 当通过“Eloquent”批量更新时,savedupdated模型事件将不会被更新后的模型代替。这是因为批量更新时,模型从来没有被取回。

  • 批量赋值

注意:批量赋值是有风险的,所有的 Eloquent 模型都针对批量赋值(Mass-Assignment)做了保护。

方案:$fillable 作为一个可以被批量赋值的属性「白名单」;$guarded 是「黑名单」,设置为空代表大门敞开。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
/**
* 可以被批量赋值的属性。
* 使 Flight 模型的 name 属性可以被批量赋值
*
* @var array
*/
protected $fillable = ['name'];
}

赋值:

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

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

四、查询作用域

  • 编写 全局作用域

自定义全局作用域很简单,首先定义一个实现 Illuminate\Database\Eloquent\Scope 接口的类,该接口要求你实现一个方法:apply

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder; class AgeScope implements Scope
{
/**
* 应用作用域
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->where('age', '>', 200); # select * from `users` where `age` > 200
}
}

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

  • 应用全局作用域
<?php

namespace App;

use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model; class User extends Model
{
/**
* 数据模型的启动方法
*
* @return void
*/
protected static function boot() # 需要重写给定模型的 boot 方法
{
parent::boot();
static::addGlobalScope(new AgeScope); # 并使用 addGlobalScope 方法
}
}

添加作用域后,如果使用 User::all() 查询则会生成如下SQL语句:select * from `users` where `age` > 200

  • 匿名的全局作用域

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder; class User extends Model
{
/**
* 数据模型的启动方法
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function(Builder $builder) {
$builder->where('age', '>', 200);
});
}
}
  • 移除 全局作用域
User::withoutGlobalScope('age')->get();

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

// 移除多个
User::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get();
  • 本地作用域

本地作用域允许我们定义通用的约束集合以便在应用中复用。

例如,你可能经常需要获取最受欢迎的用户,要定义这样的一个作用域,只需在对应 Eloquent 模型方法前加上一个 scope 前缀,作用域总是返回查询构建器:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
/**
* 限制查询只包括受欢迎的用户。
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
} /**
* 限制查询只包括活跃的用户。
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}

Then, 利用查询范围

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

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

  

  • 动态范围

定义一个可接受参数的范围。

这时只需给你的范围加上额外的参数即可。范围参数应该被定义在 $query 参数之后:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
/**
* 限制查询只包括指定类型的用户。
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}

在范围调用时传递参数:

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

五、事件

  • 模型的事件映射

事件让你每当有特定的模型类在数据库保存或更新时,执行代码。

Eloquent 模型会触发许多事件,让你在模型的生命周期的多个时间点进行监控:creatingcreatedupdatingupdatedsavingsaveddeletingdeletedrestoringrestored。

举例子:

当一个新模型被初次保存将会触发 creating 以及 created 事件。

如果一个模型已经存在于数据库且调用了 save 方法,将会触发 updating 和 updated 事件。

这两种情况下都会触发 saving 和 saved事件。

<?php

namespace App;

use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable
{
use Notifiable; /**
* 模型的事件映射。
*
* @var array
*/
protected $events = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}
  • 观察者

如果你在一个给定的模型中监听许多事件,您可以使用观察者将所有监听器变成一个类。

观察者类里的方法名应该反映Eloquent想监听的事件。 每种方法接收 model 作为其唯一的参数。

Laravel不包括观察者默认目录,所以你可以创建任何你喜欢你的目录来存放:

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
/**
* 监听用户创建的事件。
*
* @param User $user
* @return void
*/
public function created(User $user)
{
//
} /**
* 监听用户删除事件。
*
* @param User $user
* @return void
*/
public function deleting(User $user)
{
//
}
}

AppServiceProvider注册观察者

<?php

namespace App\Providers;

use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider
{
/**
* 运行所有应用.
*
* @return void
*/
public function boot()
{
User::observe(UserObserver::class);
} /**
* 注册服务提供.
*
* @return void
*/
public function register()
{
//
}
}

Ref: Laravel 数据库之:数据库请求构建器【日后继续补充】