Laravel
是PHP Web开发框架,说到Laravel
就肯定得提到Eloquent ORM
1 。
Eloquent
模型类对应着数据库中的一张表,反向控制容器(IoC container)提供了生成新对象、随时实例化对象、访问单例(singleton)对象的便捷方式。反向控制(IoC)类似于Java中的控制反转思想。
我的理解就是相当于Java中的对象,将数据表作为一个对象,进行调用里面的属性和相关方法。
ORM 2两种最常见的实现方式是 ActiveRecord 和 DataMapper,ActiveRecord 尤其流行,在很多框架中都能看到它的身影。两者的区别主要在于 ActiveRecord 中模型与数据表一一对应,而 DataMapper 中模型与数据表是完全分离的。
Laravel 中的 Eloquent ORM 使用的也是 ActiveRecord 实现方式,每一个 Eloquent 模型类对应着数据库中的一张表,我们通过调用模型类中相应方法实现对数据库的增删改查。
这个模型类我们就称它为 model。
创建后的文件会放在app目录下的Models目录下
php artisan make:model ModelName
如果你想要在生成模型时生成数据库迁移,可以使用–migration 或-m 选项:
php artisan make:model User -m
php artisan make:model User --migration
Eloquent模型的对应表名 默认规则是模型类名的复数作为与其对应的表名,除非在模型类中明确指定了其它名称,在model中定义。
//设置model关联对应名称表名,如不设置,默认为ModelName的复数形式
protected $table = 'table_name';
Eloquent 默认每张表的主键名为id ,你可以在模型类中定义一个$primaryKey
属性来覆盖该约定,
通过$keyType
设置主键类型,$incrementing
设置是否自增
//设置主键默认字段
protected $primaryKey = 'primaryKeyName';
// 设置主键的字段类型
protected $keyType = 'String';
// 设置主键是否自增
public $incrementing = false;
默认情况下于数据表中,Eloquent 会自动管理 created_at
和 updated_at
字段 。如果数据表没有这两个字段,保存数据时 Model::create($arrayOfValues
); 会看到 SQL error,原因就是Laravel 在自动填充 created_at updated_at 的时候,无法找到这两个字段。所以我们不想要这Laravel自动管理,就需要在模型类中将$timestamps
属性设置为false。
// 时间字段是否自动管理
public $timestamps = false;
如果你需要自定义时间戳格式,设置模型中的$dateFormat 属性,该属性决定日期存储到数据库中格式。
//设置格式,默认为 'Y-m-d H:i:s'格式
protected $dateFormat = 'U';
如果数据库中字段与Laravel预设名称不同,处理方法:
// 统一名称
const CREATED_AT = 'create_time';
const UPDATED_AT = 'modify_time';
// 获取模型中全部数据
$models= Models::all();
// 获取模型中的一列
$models->name;
在介绍模型之前,顺带提一下,DB数据查询方式:
$data= DB::select('select * from table_name');
//查询构造器
$data= DB::table('table_name')->get();
//以上两种查询结果是对象,直接使用return不能输出需要进行处理
//方法一
return response()->json($data);
//方法二
return [$data];
使用 Eloquent ORM 模型来操作数据库,相较于传统方法,模型查询方式可以很方便的为我们的查询语句添加全局条件,以及查询结果的数据处理,这两个功能我们在后面模块里再做介绍,这部分先介绍一些常用的查询方法:
//laravel 模型查询总结
Model::find(id); //查找主键为id)
Model::find([key1,key2]); //使用双主键进行查找
Model::findOrFail(id); //查找主键为id的数据,找不到抛出异常
Model::whereNotNull('name')->get();//查找name字段不为空
Model::where('id',$id)−>first(); //获取id为id的第一条数据
Model::where('id',$id)->get(); //获取id为id的数据
Model::where('id',$id)−> exists( ); //查询id为id的数据是否存在
Model::where('id',$id)−>pluck('name',' id' )−> first() //查询id为$id的第一个结果的id和name ,[id=>name]
Model::select('id','name')->first();//只查询第一个结果的id和name字段
Model::selectRaw('id' , 'name' as '姓名')->first();//查询id,name 作为姓名字段展示,selectRaw()里面可以使用原生sql语句
Model::whereIn('id',[1,2,3])->get(); //whereIn查询数组范围内的内容
Model::whereRaw("id in (1,2,3)")->get();//whereRaw查询字符串类型用逗号隔开,类似于原生写法
Model::whereBetween('id',[1,3]);//whereBetween查询两个数中间范围内的内容
Model::whereId($id) −>get();//动态属性查找id为id的数据
Model::paginate(10);//分页
//关联查询
Model::with('relation')->get();//将关联数据一起查出来
Model::with('relation:relation.id,relation.name')->get();//只查找关联数据的id,name字段
Model::whereHas('relation',function($query){ ... })->get();//1对多关联,查找关联数据符合条件的数据
Model::whereNotExists(function($query){
$query->from('relation_table')->where('');
})->get();//查找不符合条件的数据
Model::withCount(['marks as marks_avg'=>function($query){
$query->select(DB::raw("avg(grade) as makrs_avg"))
}])->orderBy('marks_avg','desc')->paginate(10);//根据关联数据的平均分排序,将avg()改为max,sum,min,可以实现其他排序
在没有学习作用域查询之前我们想要验证某个字段是否符合要求,比方说经常用到的是否有效,通过判断delete_time
是否为空,那么我们就需要在每条查询中增加
//首先拼接相应字段非空之后再拼接其他条件查询
$model = Model::whereNotNull('delete_time')->get();
当在一定范围内或特定条件下的所有查询都需要增加同样的查询条件时,我们就可以在全局或者局部作用域下进行查询,对上面的传统方法进行优化
要实现「全局作用域」,首先需要编写一个实现 Illuminate\Database\Eloquent\Scope 接口的全局作用域类,这里我们将其命名为 DeleteTimeScope ,并将其放到 app/Scopes 目录下:
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class DeleteTimeScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
return $builder->whereIsNull('delete_time');
}
}
在这个全局作用域类中,我们就进行了delete_time的非空判断,然后我们就需要在我们需要应用的模型类上声明,就可以实现在 对应的模型类进行查询的时候,可以应用相应的过滤条件。
//方法一:已经按照上述方法进行全局作用域类声明
protected static function boot()
{
parent::boot();
static::addGlobalScope(new DeleteTimeScope ());
}
我们也可以只在对应的模型中进行局部声明,通过匿名函数方式实现全局作用域,这种方式下,顺便提一下,也可以还可以通过if 判断进行全局作用域的二次筛选。
//方法二:只在对应的模块里进行声明
protected static function boot()
{
parent::boot();
//比方说我们只在请求头中包含对应字段不为空的时候才进行
if (!empty(request()->header('Authorization'))){
static::addGlobalScope('delete_time', function (Builder $builder) {
$builder->where('delete_time', '=', null);
});
}
//所有情况下都进行全局作用域查询
static::addGlobalScope('delete_time', function (Builder $builder) {
$builder->where('delete_time', '=', null);
});
}
在某些特定场景下,我们可能需要移全局作用域,比方说需要显示,这个时候我们可以借助模型类的 withoutGlobalScope
方法来实现,该方法支持多种传参格式,移除多种全局作用域及其组合:
Model::withoutGlobalScope(DeleteTimeScope ::class)->get(); # 指定类
Model::withoutGlobalScope('delete_time')->get(); # 匿名函数
Model::withoutGlobalScopes()->get(); # 移除所有全局作用域
Model::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get(); # 移除多个类/匿名函数
在我的理解中,局部作用域查询,其实就是将一些经常用得到的,出现频率高的封装成我们约定的方法,类似于where()
,whereIn()
等预设方法
比方说排序函数
public function scopeOrderID(Builder $query)
{
return $query->orderBy('id', 'desc');
}
在进行调用时,只需调用 scope 之后的过滤器名称即可,Eloquent 底层会通过魔术方法自动调用对应完整方法,使用方法与模型查询方式类似
$model= Model::orderID()->get();
Eloquent 模型类还支持动态作用域,所谓动态作用域指的是在查询过程中动态设置预置过滤器的查询条件。
动态作用域和局部作用域类似,过滤器方法名同样以 scope 开头,只不过可以通过额外参数指定查询条件,我的理解就是可以携带参数的模型查询方法,增加了灵活性
//比方说传递排序字段类型
public function scopeOrderBy(Builder $query, $orderBy)
{
return $query->orderBy($orderBy, 'desc');
}
在很多情况下,我们需要对查询结果进行处理,比方说我们通常在数据库中存储的都是图片的ID,我们需要根据ID去图片服务器中取我们的图片,所以通常我们需要进行将ID转换为相应的图片路径,所以我们可以在这些通用的情况下,进行结果处理
我们按照Eloquent 的规则进行处理方法声明 ,*** 就是我们对应的字段名称,然后在进行model调用时,返回结果就会进行我们所规定的方法进行处理啦
//约定的命名方式,***为字段名,首字母大写
public function get***Attribute($value)
//对查询结果的logo字段进行路径处理
public function getLogoAttribute($value)
{
return MerchantAttachment::path($value);
}
本次总结就先写到这里,初次接触到 Laravel 给我的感觉就是莫名的熟悉感,感觉很多地方和java的设计思路类似,也有很多方便的功能,看后续的使用中,上述的总结中,有没有错误的地方,或者可以优化的地方,也期待大佬指教。
Eloquent ORMEloquent ORM是一个优美、简洁的ActiveRecord类,实现数据库操作,数据表都有一个与之对应的“模型”,用于和数据表交互。MVC架构中,Controller不是直接去数据库中取数据,而是model去访问数据库。 ↩︎
Object-Relational Mapping(对象关系映射) ↩︎