继承:ILLuminate\Database\Eloquent\Model
model与表名的关系
表名去掉s 就是关于这个表的model类
如users表 的Model类叫 User
创建model
使用命令提示符创建 Model
php artisan make:model ModelName
Model的使用
use model类
new model对象
model=new App\ModelName
使用静态方法调用
App\ModelName::方法名:
简单使用model 添加
$msg = new Msg;
$msg->title='mytitle'
$msg->save();//保存操作
指定连接的数据库
protected $connection ='连接名'
model 添加多列数据
先在Model类定义保护属性fillable设置允许添加的字段
protected $fillable = ['name','money','mobile','uid','pubtime'];
数据库添加
Model::create(['表字段名'=>'值','字段名'=>'值'])
简单查询
详情访问laravel查询构造器
$model->where()->first()//单行查询
->find(1)//根据主键查询
->get()//多行查询
->get(['title'])//选择列查询
->all()//取出所有数据
->all(['title])//选择列查询
->where('id','>',1)->select('content')->get();
辅助查询案例,跳过n行取m行
where->skip(n)->take(m)->get();
简单更新
更新前先查询
$flight = App\Flight::find(1);
$flight->name = 'new name';
$flight->save();
批量更新
App\Flight::where('active', 1)
->where('name', 'pik')
->update(['字段' => '值','字段'=>'值']);
//active = 1并且;name = pik 的字段更新
批量更新2
$pro = Pro::find($id);//查询当前项目
$pro->title = request('title');
$pro->rate = request('rate');
$pro->hrange = request('hrange');
$pro->staus = request('status');
$pro->save();
model删除
$flight = App\Flight::find(1);
$flight->delete();//删除主键id = 1的记录
App\Flight::destroy(1);//删除主键为1的记录
App\Flight::destroy(1, 2, 3);//删除主键为1,2,3的记录
条件删除
$deletedRows = App\Flight::where('active', 0)->delete();//删除actiove = 0的记录
model 约定
model 默认规则表名去掉s就是其model类名
如果不想遵循这种规则需在model类声明属性
protected $table = '表名'//指定操作表
model 默认主键为 id
如果修改默认主键在其类名设置属性
protected $primaryKey = '主键名'//指定主键
laravel默认添加数据库时,会 增加两个字段
create_at ,update_at
如不需要这两个字段,除在迁移文件删除之外
还需在model类设置属性
public $timestamps = false
查询全局作用域
app\Scope
全局范围能为给定模型的所有查询添加约束。Laravel 自带的 软删除功能 就利用全局作用域从数据库中提取「未删除」的模型。编写自定义的全局作用域可以提供一个方便、简单的方法来确保给定模型的每个查询都受到一定的约束。
编写全局作用域
1.定义一个类
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class AgeScope implements Scope
{
/**
* 将范围应用于给定的 Eloquent 查询生成器
*
* @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);
//查询限制年龄大于200
}
}
应用全局作用域
要将全局作用域分配给模型,需要重写给定模型的 boot 方法并使用 addGlobalScope 方法:
namespace App;
use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AgeScope);
}
}
匿名的全局作用域
Eloquent 还能使用闭包定义全局作用域,如此一来,便就没必要定义一个单独的类了:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function(Builder $builder) {
$builder->where('age', '>', 200);
});
}
}
注:如果你的全局作用域需要添加列到查询的 select 子句,需要使用 addSelect 方法来替代 select,这样就可以避免已存在的
select 查询子句造成影响。
删除全局作用域
删除一个全局作用域
User::withoutGlobalScope(AgeScope::class)->get();
删除多个全局作用域
如果你想要删除几个甚至全部的全局作用域,可以使用 withoutGlobalScopes 方法:
// 删除所有的全局作用域
User::withoutGlobalScopes()->get();
// 删除一些全局作用域
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get()
本地作用域
本地作用域定义通用的约束集合方便复用
定义方法 在约束方法前加scope
如
public function scopeFlag($query){
return $query->where('flag',1);
//状态为上架的书
}
调用本地作用域
直接调用scope方法即可,调用时不需加scope前缀
可同时调用多个scope
public function add(){
$res=Good::Flag()->get();
dump($res);
}
动态作用域
Laravel中Eloquent还支持动态作用域,动态作用域指在查询过程中动态设置预置过滤器的查询条件,动态作用域与本地作用域类似,都是以scope作为前缀,调用方法也相同,不同的是动态作用域可以通过额外参数指定查询条件,如下查找商品价格>200的记录
在定义动态作用域中
public function scopePrice($query,$price){
return $query->where('price','>',$price)
}
在查询时直接调用
$goods = Good::Price(200)->get();
全局作用域可理解为限制约束,本地作用域/动态作用域则可理解为一些定义好的常用约束集合,使用时直接调用即可。
Model关联
User
模型关联一个Phone
模型,为了定义此关联,我们需在User
模型定义一个Phone
方法,在方法内部调用hasOne
方法并返回结果。
namespace App;
use Illuminate\Database\Eloquent\Model;
class Muser extends Model
{
protected $table = 'musers';
protected $primaryKey = 'uid';
public $timesTamps = false;
//连接phone模型
public function phone(){
return $this->hasOne('App\Phone','uid','uid');
}
}
定义好关联后可获取相关记录
public function show(){
$res=Muser::find(1)->phone;
//在phones表中查找外键uid(uid=1)与musers表的主键uid相匹配的记录
dd($res);
}
一对一反向关联
在上面我们从muser
模型访问到phone
模型,现在从phone
模型访问muser
模型
在此之前我们需在phone
模型中定义muser
方法 并使用belongsTo
方法定义反向关联
如:
belongsTo(class,foreignkey,primarykey)
class:反向关联的类名
foreignkey:当前模型的外键
primarykey:父表的主键
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
protected $primaryKey = 'pid';
public $timestamps = false;
//定义反向关联
//获取拥有此电话的用户 belongsTo()
public function muser(){
return $this->belongsTo('App\Muser','uid','uid');
}
}
定义好反向关联后访问记录
public function show(){
$res = Phone::find(1)->muser;
//获取用于此电话的用户
dd($res);
}
默认模型
belongsTo
关联允许定义默认模型 ,使用withDefault()
,适用于返回结果为空的情况
如:
1.widthDefault无参数时,返回父模型实例
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
protected $primaryKey = 'pid';
public $timestamps = false;
//定义反向关联
//获取拥有此电话的用户 belongsTo()
public function muser(){
return $this->belongsTo('App\Muser','uid','uid')->withDefault();
}
}
2.指定默认值 以数组形式
public function muser(){
return $this->belongsTo('App\Muser','uid','uid')->withDefault(['name'=>'测试']);
}
返回结果
#attributes: array:1 [▼
"name" => "测试"
]
#original: []
一对多
如文章与评论的关系,一篇文章会有多个评论
在文章模型使用hasMany()
hasMany()
与hasOne()
语法一致,这里不予介绍
//获取文章的评论
public function comments(){
return $this->hasMany('App\Comment','aid','aid');
}
获取某篇文章的记录
public function show(){
$res = Article::find(1)->comments;
dd($res);
}
//返回结果
Collection {#198 ▼
#items: array:2 [▼
0 => Comment {#203 ▶}
1 => Comment {#207 ▶}
]
}
一对多反向
通过评论获取关联文章 使用belongsTo()
方法定义反向关联
在子模型Comment
中
//获取某评论关联的文章
public function article(){
return $this->belongsTo('App\Article','aid','aid');
}
获取关联记录
public function show(){
$res = Comment::find(1)->article;
dd($res);
//返回结果
#attributes: array:2 [▼
"aid" => 1
"title" => "山东福瑞达"
]
}
渴求式加载
当以属性方式访问
Eloquent
关联关系的时候,关联关系数据是[懒惰式加载]
因为都是用到的时候才执行查询,这就意味着要多次对数据库进行查询才能返回需要的结果,如果是单条记录获取关联关系,就需要两次查询;如果是多条记录获取关联关系,比如文章列表页获取作者信息,因为每篇文章的作者通过动态属性获取都有一次查询,所以对N条记录来说,需要N+1次查询才能返回需要的结果,对于数据库查询优化角度来说,显然不合理,有没有方法能一次就返回所有的关联查询的结果呢?
使用渴求式加载,即根据预先需求查询出所有数据
为了验证[渴求式加载]
的好处,举下列例子
这里用到了Laravel Debugbar 调试
安装方法可参照我的另一个博客 - - - Laravel Debugbar安装
创建文章表(articles)与作者表(auths)
表结构如下:
文章表(articles)
作者表(auths)
获取作者信息
public function show(){
$res = Article::all();//获取所有文章
//遍历获取所有作者
foreach($res as $v){
dump($v->auths[0]->uname);
}
dump($res);
}
由此可见在循环中程序执行了7次循环
该循环先执行1次查询获取表中的所有的文章,然后另一个查询获取每一篇文章的作者,因此如果有6个作者,则会执行7次查询,1次是获取文章,剩下的6次获取文章作者。
这样的查询次数显然不符合数据库查询优化
使用渴求式加载解决
Eloquent
中提供了with
方法,只需将建立Model
关系的方法传入with
即可
修改上述代码优化查询
public function show(){
$res = Article::with('auths')->get();//获取所有文章
//遍历获取所有作者
foreach($res as $v){
dump($v->auths[0]->uname);
}
dump($res);
}
显示执行结果
由此可见,只执行了两次查询,但查询结果与上面一致。
渴求式加载多个关联关系
有时候你需要在单个操作中渴求式加载多个不同的关联关系,要实现这一功能,只需添加参数到with方法即可 ,以逗号分割
如,查询文章的作者即所在栏目
嵌套的渴求式加载
要使用嵌套的渴求式加载的关联关系,使用“.”连接即可,如我们需要获取所有书的作者以及作者的个人联系方式。
在这里我们需要在键一张表 联系表(contacts)这个表与作者表(auths)建立连接
contacts
表结构如下
执行代码如下
public function show(){
//
$res = Article::with('auths.contacts')->get();
foreach($res as $v){
echo $v->title;//标题
echo "
";
echo $v->auths[0]->uname;
echo "
";
echo($v->auths[0]->contacts->email);
echo "
";
}
}
三表之间关系
article->auths
auths->contacts
打印结果
由此可见,文章标题,作者,作者联系方式都被查询出来。
渴求式加载指定字段
有时候,使用渴求式加载时不需要查询全部字段,这个时候可使用“指定字段”
格式with(‘模型方法:id,字段1,字段2’)
需要注意的是渴求式加载指定字段必须列出父表id
如下例子
public function show(){
$res = Article::with('auths:uid,aid')->get();
foreach($res as $v){
echo $v->auths;
}
}
public function show(){
$res = Article::with('auths:uname')->get();
foreach($res as $v){
echo $v->auths;
}
}
显示结果’
没有指定父表id字段,无法显示结果。
带条件约束的渴求式加载
有些时候我们需要为渴求是加载添加约束条件
以数组形式,如下例
public function show(){
$res = Article::with(['auths'=>function($query){
return $query->where('uname','like','%g%');
}])->get();
foreach($res as $v){
echo $v->auths;
}
}
//只显示uname,包含g的记录
显示结果
懒惰渴求式加载
有时候你需要在父模型已经被获取后渴求式加载一个关联关系。这在你需要动态决定是否加载关联模型时可能很有用:
如下例,使用load
方法
public function show(){
$res = Article::all();
$flag = true;
if($flag){
$author=$res->load('auths');
foreach ($author as $key => $value) {
echo $value->auths;
}
}
}
添加查询条件
public function show(){
$res = Article::all();
$flag = true;
if($flag){
$author=$res->load('auths'=>function($query){
$query->where('uname','like','%g%');
//查找名称包含g的记录
});
foreach ($author as $key => $value) {
echo $value->auths;
}
}
}
多对多
最典型的案例 - - - 学生与选修课 ,一名学生可选修多门选修课,一门选修课又可被多名学生选修,这里需要用到三张表 stu(学生),mclass(课程),stu_class(中间表),中间表包含自身id
,sid
,cid
字段
实现多对多关联
在stu模型定义一个方法,在方法内部调用belongsToMany()
方法并返回结果
belongsToMany(‘class’,‘middleTable’,‘foreignPivotKey’,‘relatedPivotKey’,‘parentKey’,‘relatedKey’)
class
:关联的类名
middleTable
:连接两张表的中间表
如不指定,默认拼接规则为stu_mclass
(这里没有复数形式)
foreignPivotKey
:当前模型在中间表的字段(当前模型类的外键)
如不指定,默认拼接规则为 表名_id,这里为stu_id
relatedPivotKey
:另一模型在中间表的字段(当前模型类的外键)
如不指定,默认拼接规则与foreignPivotKey一样 这里为 mclass_id
parentKey
:表示对应当前模型的哪个字段,即foreignPivotKey映射到当前模型所属表的哪个字段,如不指定默认foreignPivotKey
relatedKey
:表示对应关联模型的哪个字段 ,即relatedPivotKey映射到当前模型所属表的哪个字段,如不指定模型relatedPivotKey
如
public function mclass(){
return $this->belongsToMany('App\Mclass','stu__classes','sid','cid');
}
获取关联值
//获取1号学生选修的选修课
public function show(){
$res = Stu::find(1)->mclass;
dd($res);
}
反向关联
与上述方法基本一致,上述方法我们通过学生id获取其选修的课程,现在通过课程id查询选修的学生
在Mclass
模型中定义一个stus
方法,这个方法还是调用belongsToMany()
;并返回值
如:
//获取选修此课程的学生
public function stus(){
return $this->belongsToMany('App\Stu','stu__classes','cid','sid');
}
获取关联的值
public function show(){
$res = Mclass::find(1)->stus;
dd($res);
}
//部分结果
#attributes: array:2 [▼
"sid" => 5
"sname" => "唐大涵"
]
#original: array:4 [▼
"sid" => 5
"sname" => "唐大涵"
"pivot_cid" => 1
"pivot_sid" => 5
]
获得中间表字段
多对多关联需要有一个中间表的支持,Eloquent提供了一些方法和这张表进行交互,如Stus关联了Mclass对象,在获取这些关联对象后,可以通过模型的pivot
属性访问中间表数据
public function show(){
$stu = Stu::find(1)->mclass;
dump($stu[0]->pivot);
}
需要注意的是,我们取得的每个模型对象,都会被自动赋予
pivot
属性,它代表中间表的一个模型对象,能像其它的 Eloquent 模型一样使用。
默认情况,pivot对象只包含两个关联模型的键。如果中间表里面还有额外字段,则必须在定义关联时明确指出(通过withPivot
方法):
为了显示中间表其他数据,我们需要修改Stu模型的mclass方法
public function mclass(){
return $this->belongsToMany('App\Mclass','stu__classes','sid','cid');
}
//修改后
public function mclass(){
return $this->belongsToMany('App\Mclass','stu__classes','sid','cid')
->withPivot('flag');
//withPivot方法向Eloquent说明中间表还有flag字段
}
如果您想让中间表自动维护 created_at 和 updated_at 时间戳,那么在定义关联时加上 withTimestamps
方法即可。
public function stus(){
return $this->belongsToMany('App\Stu','stu__classes','cid','sid')
->withPivot('flag')
->withTimestamps();
}
通过中间表过滤关联数据
在定义关联时,您可以使用wherePivot
(限定条件)和wherePivotIn
(限定区间)方法过滤belongsToMany返回的结果。
public function mclass(){
return $this->belongsToMany('App\Mclass','stu__classes','sid','cid')
->wherePivot('cid',1);
}
自定义中间表模型
Laravel中允许你自定义中间表模型,需要注意的是中间表模型与普通模型不一样
普通模型继承自Illuminate\Database\Eloquent\Model
,而中间表模型继承自Illuminate\Database\Eloquent\Pivot
创建中间表模型
namespace App;
use Illuminate\Database\Eloquent\Pivot;
class Stu_Class extends Pivot
{
//
}
定义好中间表模型后,需在模型关联时通过using方法指定自定义中间表模型
public function mclass(){
return $this->belongsToMany('App\Mclass','stu__classes','sid','cid')
->using('App\Stu_Class');
}
远程一对多
官方文档没看太懂,看了这个大佬的文章,豁然开朗
原文链接
下面结合大佬的例子,阐述一下我的想法
远程一对多,顾名思义“远程”的一对多,既然称之为远程一对多,那这个一对多关系肯定不是直接关联,而是“远程”关联,问题是如何远程关联? 这得借助于中间表,通过前面的学习我们可能会有这样的疑惑,不是多对多才借助中间表吗?。。。。不急下面通过一个例子你就理解了 ,这里得补充一句,虽然借助了中间表,但本质上还是一对多关联。
如果我们做一个博客系统是针对全球市场的,可能针对不同的国家推出不同的用户系统和功能,每个国家的用户访问仅展示其所在国家的文章,这里就会涉及到三张表 用户表(users)国籍表(countries)文章表(articles),用户与文章是一对多的关系,国家与用户也是一对多的关系,那么怎么实现根据不同的国家显示对应的文章?只要能使国家与文章表建立连接那这个功能实现不就简单了吗?
通过用户表(users)这个中间表,可以使国家与文章表建立连接,因为用户表分别与国家与文章表建立了连接,即可通过用户表(users)这个媒介,可使国家与文章表建立连接
完成这个案例,我们先根据需求建表
1.国家表
2.用户表
3.文章表
通过hasManyThrough()
方法建立远程一对多关联
hasManyThrough($related,$through,$firstKey,$secondKey,$localKey,$secondLocalKey)
$related:关联模型类
$throuth:中间模型类
$firstKey:中间模型类与当前模型类的外键,如果不指定,在本例中按照默认拼接规则为 当前模型类名_id;这里就是(Countrie_id)
$secondKey:中间模型类与关联模型类的关联外键,如果不指定,在本例中按照默认拼接规则为关联模型类_id;这里就为user_id
$localKey:默认当前模型类的主键ID
$secondLocalKey:默认中间模型类的主键ID
在Countrie模型中使用hasManyThrough
方法并返回结果
public function articles(){
return $this->hasManyThrough('App\Article','App\Muser','country_id','uid','id','uid');
//建立远程一多连接 国家---文章;
}
根据国家显示对应记录,这里使用了渴求式加载
public function show(){
$res = Countrie::with('articles')->get();
foreach($res as $v){
dump($v->articles);
}
}
多态关联
多态关联允许一个模型在单个关联上属于多个其他模型。单看这句话有点云里雾里的
举个例子,在一个场景你系统的用户可以对文章和视频评论,使用多态关联,你只需用一个评论表(comments)即可同时满足存储视频,文章的评论。下面介绍三种多态关联
一对一多态关联
一对一多态关联,还是通过举例来理解
引用大佬文章,原文链接
假设在我们的博客系统中用户可以设置头像,而文章也可以设置缩略图,我们知道每个用户只能有一个头像,一篇文章只能设置一个缩略图,用户和头像图片之间是一对一关联,文章和缩略图也是一对一关联,通过多态关联,我们可以让用户和文章共享图片表的一对一关联
为了实现这个效果,我们只需要在图片模型类通过一次定义,就可以动态建立与用户和文章的关联。
要建立这种多态管理,需要设置图片表结构以及用户与文章表的关联,在图片表要额外添加两个字段,1,类型字段(table_type)保存所属模型的类名。2.ID字段(table_id)指向用户或文章的ID字段。结合这两个字段即可确定图片表的图片是属于哪个用户或者哪个文章。
下面举例说明
1.建立图片表
图片表的迁移文件up方法
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->engine = 'innoDB';
$table->charset = 'utf8';
$table->collation = 'utf8_general_ci';
$table->increments('id');
$table->string('path')->comment('图片路径');
$table->morphs('imagetable');
//morphs方法用于建立imagetable_id和imagetable_type两个字段
$table->timestamps();
});
}
表结构
建立多态连接,在Image
模型中定义方法并使用morphTo
返回结果
morphTo($name,$type,$id,$ownerKey)
$name
:关联关系的名称 ,如不指定默认为关联方法名
$type
:存放关联类的字段,如不指定结合$name
构建关联字段(如果name为test,此字段为test_type)
$id
:存放关联id(文章或视频)如不指定结合$name
构建关联字段(如果name为test,此字段为test_id)
$ownerKey
:当前模型主键ID
public function imagetable(){
return $this->morphTo();
//在方法中我们没有向morphTo传入参数,这是因为我们遵从了Eloquent的默认约定
}
查找关联数据
public function show(){
$res = Image::find(1);
dump($res->imagetable);
}
反向多态一对一
建立反向一对一多态关联,分别在Article,Musers定义方法使用morphOne
建立反向关联,即可根据用户/文章获取对应图片
morphOne($related,$name,$type,$id,$localKey)
$related
:反关联模型类
$name
:关联关系的名称
$type
:根据关联名称拼接存放关联类的字段,也可自定义
$id
:根据关联名称拼接存放关联类的id .也可自定义
$localKey
:当前模型的主键id
public function image(){
return $this->morphOne('App\Image','imagetable','imagetable_type','imagetable_id','aid');
}
获取图片
public function show(){
$res = Article::find(1);
dump($res->image);
}
一对多多态关联
学习了一对一多态关联,一对多多态关联相对简单,实际上原理跟一对一多态差不多,只是这里变成了一对多而已
下面举例说明,以评论来说,文章跟评论的关系是一对多,可能我们的系统里面
还会针对某个模块设置评论窗口,如用户信息的评论(可能例子不恰当,反正体先多态关联)这两种评论结构是一样的,没接触这个之前我们可能会创建两种评论表分别存储文章评论和用户信息的评论,但多态关联解决了这一问题,我们只需键一张评论表,利用这张表存储两种评论。
1.建表(评论表)
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->engine = 'innoDB';
$table->charset = 'utf8';
$table->collation = 'utf8_general_ci';
$table->increments('id');
$table->string('con');
$table->morphs('commentable');
//morphs 建立__type ,_id字段
$table->timestamps();
});
}
表结构
在Comment模型定义方法使用morphTo
方法定义与Article
与Muser
之间的一对多关联
morphTo($name,$type,$id,$ownerKey)
$name
:关联关系的名称 ,如不指定默认为关联方法名
$type
:存放关联类的字段,如不指定结合$name
构建关联字段(如果name为test,此字段为test_type)
$id
:存放关联id(文章或视频)如不指定结合$name
构建关联字段(如果name为test,此字段为test_id)
$ownerKey
:当前模型主键ID
public function commentable(){
return $this->morphTo();
//这里我们没有向morphTo传入参数,因为我们遵循了他的默认约定
}
获取关联数据
public function show(){
$res = \App\Comment::find(1);
dump($res->commentable);
}
显示结果
多态一对多反向关联(根据文章或用户查询对应的评论信息)
由于文章/用户跟评论的关系是一对多,所以需要使用morphMany
方法
morphMany($related,$name,$type,$id,$localKey)
$related
:反关联模型类
$name
:关联关系的名称
$type
:根据关联名称拼接存放关联类的字段,也可自定义
$id
:根据关联名称拼接存放关联类的id .也可自定义
$localKey
:当前模型的主键id
public function comment(){
return $this->morphMany('App\Comment','commentable','commentable_type','commentable_id','aid');
}
获取结果
public function show(){
$res = Article::find(1)->comment;
dump($res);
}
用户评论获取同理
- 多对多多态关联
学到了这里,理解多态多对多也不是很难了,还是举例说明,文章与标签的关系,多对多这个大家知道,因此我们需要建立文章表与标签表,以及中间表,中间表存放文章与标签表的id使他们建立连接,这是常规套路,但如果我们的系统大一点,不止文章,还有视频,音频,图片等内容,这些东西也会使用标签,难道每种不同类型的媒体都有建立一张对应的标签表?显然不现实而且还是多对多关系,到时候处理起来也很复杂,或者在中间表里面添加媒体id字段,每个媒体id字段与标签建立连接?这样或许也行吧,或许有更方便的办法–就是马上要学习的多态多对多,多态多对多也是需要中间表,与常规多对多差不多,只是中间表需要_type(模型类型字段),_id(关联模型id)
话不多说直接上案例
我们要实现的功能是 文章与图片表共有一个标签表
1.建表标签表
2.建立中间表
public function up()
{
Schema::create('media_tags', function (Blueprint $table) {
$table->increments('id');
$table->integer('tag_id');
$table->morphs('tagtable');
$table->timestamps();
});
}
在Tag
模型中定义方法使用morphedByMany
与Article
和Image
建立多态多对多连接
morphedByMany($related,$name,$table,$foreignPivoteKey,$relatedPivotKey,$parentKey,$relatedKey)
$relate
·:关联的模型类
$name
:关联的名称,在定义数据库迁移时通过morphs指定的名称一致
$table
:中间表名称,默认为$name
的复数形式
$foreignPivotKey
:当前模型在中间表的外键,默认拼接规则为”当前表_id“
$relatedPivotKey
:中间表的关联ID字段 默认通过$name
+’_id’组合表示即morphs
生成的ID字段,另一外键字段
$parentKey
:当前模型的主键
$relatedKey
:关联模型类的主键,由$relate
指定的模型而定义
public function articles(){
return $this->morphedByMany('App\Article','tagtable','miedia_tags','tag_id','tagtable_id','id','aid');
}
public function images(){
return $this->morphedByMany('App\Image','tagtable','miedia_tags','tag_id','tagtable_id','id','id');
}
返回结果
public function show(){
$res = \App\Tag::find(1)->articles;
dump($res);
}
定义反向多态多对多关联
通过文章或图片查询标签
在Article
或Image
模型中使用morphToMany
方法
morphToMany($related,$name,$table,$foreignPivotKey,$relatedPivotKey,$parentKey,$relatedKey,$inverse)
$relate
·:关联的模型类
$name
:关联的名称,在定义数据库迁移时通过morphs指定的名称一致
$table
:中间表名称,默认为$name
的复数形式
$foreignPivotKey
:关联模型在中间表的外键,默认拼接规则为”当前表_id“
$relatedPivotKey
:中间表的关联ID字段另一外键;
注意这里定义反向关联,foreignPivotKey与relatedPivotKey的值正好相反,相对于上面
$parentKey
:当前模型的主键
$relatedKey
:关联模型类的主键,由$relate
指定的模型而定义
$inverse
若为true,则查询的是关联对象本身,若为false,查询的是关联对象与父模型的对应关系。默认false
public function tag(){
return $this->morphToMany('App\Tag','tagtable','media_tags','tagtable_id','tag_id','aid','id');
}
显示标签
如果将morphToMany
的最后一个参数改为true则查询关联对象本身
通过原生sql语句我们可以更好的理解
当最后一个参数为false时
SQL:
select `tags`.*, `media_tags`.`tagtable_id` as `pivot_tagtable_id`, `media_tags`.`tag_id` as `pivot_tag_id` from `tags` inner join `media_tags` on `tags`.`id` = `media_tags`.`tag_id` where `media_tags`.`tagtable_id` = 1 and `media_tags`.`tagtable_type` = 'App\Article'
当最后一个参数为true时
select `tags`.*, `media_tags`.`tagtable_id` as `pivot_tagtable_id`, `media_tags`.`tag_id` as `pivot_tag_id` from `tags` inner join `media_tags` on `tags`.`id` = `media_tags`.`tag_id` where `media_tags`.`tagtable_id` = 1 and `media_tags`.`tagtable_type` = 'App\Tag'
关联查询
实际上前面提到的渴求式加载与懒惰式加载也归结于关联查询,这里将关联查询补充完整
当我们以动态属性的方式去访问关联关系时为懒惰式加载
如
此时返回的是一个Muser模型实例
public function show(){
$res = Phone::find(1);
dump($res->muser);
}
Muser {#369 ▼
#table: "musers"
#primaryKey: "uid"
+timestamps: false
#connection: "mysql"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:3 [▼
"uid" => 1
"uname" => "tzh"
"country_id" => 2
]
#original: array:3 [▶]
#changes: []
#casts: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
#hidden: []
#visible: []
#fillable: []
#guarded: array:1 [▶]
}
当以方法访问关联关系时,这里返回的不是Model实例,而是一个关联关系实例
public function show(){
$res = Phone::find(1);
dump($res->muser());
}
BelongsTo {#336 ▼
#child: Phone {#337 ▶}
#foreignKey: "uid"
#ownerKey: "uid"
#relation: "muser"
#query: Builder {#294 ▶}
#parent: Phone {#337 ▶}
#related: Muser {#299 ▶}
#withDefault: array:1 [▶]
}
通过方法访问我们可以注入sql语句,限制查询返回的结果
如
public function show(){
$res = Phone::find(1);
dump($res->muser()->where('uid',1)->get());
}
返回结果
Collection {#372 ▼
#items: array:1 [▼
0 => Muser {#373 ▼
#table: "musers"
#primaryKey: "uid"
+timestamps: false
#connection: "mysql"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:3 [▼
"uid" => 1
"uname" => "tzh"
"country_id" => 2
]
基于关联查询限制结果
当获取到模型记录时,你可能希望根据存在的关联对结果进行限制,如,获取有电话号码的用户,为了实现这个功能 可以通过has()方法,将建立关系的方法名传递给has即可
如
public function show(){
$res = Muser::has('phone')->get();
dd($res);
//查询有号码的用户记录
}
返回3条记录
Collection {#343 ▼
#items: array:3 [▼
0 => Muser {#344 ▶}
1 => Muser {#345 ▶}
2 => Muser {#346 ▶}
]
}
你还可以通过算术运算符和数目,进一步限制
如
//查询至少有两个号码的用户
public function show(){
$res = Muser::has('phone','>',2)->get();
dd($res);
}
你还可以通过嵌套关联进行限制
如
获取该号码至少有两个紧急联系人的用户
public function show(){
$res = Muser::has('phone.person','>',2)->get();
dd($res);
}
你还可以使用更高级的语法进行限制,通过whereHas
和orwhereHas
,在has查询里设置[where]条件
如查询用户至少有一个号码,并且号码包含7这个数字
public function show(){
$res = Muser::whereHas('phone',function($query){
$query->where('phone','like','%7%');
})->get();
dd($res);
}
基于不存在的关联限制查询结果
当获取模型记录时,你可能需要根据不存在的关联对结果进行限制,如获取没有电话号码的用户记录,为了实现这个功能你可以使用doesntHave
,并传递关联方法
如
public function show(){
$res = Muser::doesntHave('phone')->get();
dd($res);
//获取没有电话号码的用户
}
同时你还可以基于doesntHave
实现更高级的限制,使用whereDoesntHave
如:
public function show(){
$res = Muser::doesntHave('phone',function($query){
$query->where('phone',1);
})->get();
dd($res);
//获取没有电话号码的用户,并且电话id = 1;
}
关联数据计数
如果你只想统计结果数并不需要加载数据,那么可以使用withCount
方法,此方法会在你的结果集模型中添加一个{关联名_count}字段
如查询每个用户的号码数量
public function show(){
$res = Muser::withCount('phone')->get();
dd($res);
}
返回结果
Collection {#343 ▼
#items: array:3 [▼
0 => Muser {#344 ▼
#table: "musers"
#primaryKey: "uid"
+timestamps: false
#connection: "mysql"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:4 [▼
"uid" => 1
"uname" => "tzh"
"country_id" => 2
"phone_count" => 2
]
#original: array:4 [▶]
#changes: []
#casts: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
#hidden: []
#visible: []
#fillable: []
#guarded: array:1 [▶]
}
你还可以为多个关联计数(多个计数用数组表示),还可以为其添加查询条件(条件作为方法的键)
如 查询用户的国籍数及电话数,并且用户的电话号码包含1
public function show(){
$res = Muser::withCount(['countrie','phone'=>function($query){
$query->where('phone','like','%1%');
}])->get();
dd($res);
}
Collection {#344 ▼
#items: array:3 [▼
0 => Muser {#345 ▼
#table: "musers"
#primaryKey: "uid"
+timestamps: false
#connection: "mysql"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:5 [▼
"uid" => 1
"uname" => "tzh"
"country_id" => 2
"countrie_count" => 1
"phone_count" => 2
]
你还可以为关联数据计数字段取别名,可在同一次关联上多次计数
如统计用户的号码数量,和用户号码包含7的数量
public function show(){
$res = Muser::withCount(['phone','phone as p_num'=>function($query){
$query->where('phone','like','%7%');
}])->get();
dd($res);
}
Collection {#344 ▼
#items: array:3 [▼
0 => Muser {#345 ▼
#table: "musers"
#primaryKey: "uid"
+timestamps: false
#connection: "mysql"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:5 [▼
"uid" => 1
"uname" => "tzh"
"country_id" => 2
"phone_count" => 2
"p_num" => 1
]
渴求式加载
插入&更新关联模型
Eloquent提供了便捷的方法将新的模型增加至关联中,如你需要为一个Muser
模型插入一个新的Phone
,这时你无须为Phone
手动设置musers
属性,直接在关联上使用save
方法插入Phone
即可
如:
为用户id为2的插入电话号码
public function show(){
$user = \App\Muser::find(2);
$user->phone()->save(new \App\Phone(['phone'=>435354]));
}
这里没有使用phone
动态属性访问关联关系,而是使用了phone
方法获取关联实例。在调用save
方法向Phone
模型插入值
这里Eloquent自动在phones表中添加了uid字段,并插入正确的值
使用saveMany
添加多个值
$user = \App\Muser::find(1);
$user->phone()->saveMany([
new \App\Phone(['phone'=>344434]),
new \App\Phone(['phone'=>3252355]),
]);
}
使用create方法添加值
create
方法和save
方法一样也是向模型插入值,不同的是save
接收的是一个完整的Eloquent
实例,而creare
接收的是一个纯数组,需要注意的是使用create
方法需要设置$fillable
允许批量添加的值。
如
public function show(){
$user = \App\Muser::find(2);
$user->phone()->create(['phone'=>1]);
}
使用createMany添加多个值
多个值用数组分割
public function show(){
$user = \App\Muser::find(2);
$user->phone()->createMany([
['phone'=>3],
['phone'=>4]
]);
}
更新belongsTo关联(更新关联关系所属模型外键字段)
如果要更新新创建的模型实例所属模型的外键字段,可用associate
方法实现
如 phones表要更新uid字段
public function show(){
$user = Muser::find(3);
$phone = new Phone;
$phone->muser()->associate($user);
$r= $phone->save();
dump($r);
}
打印结果
新增uid = 3
如果要解除当前模型与所属模型之间的关联,可使用dissociate
方法实现
public function show(){
$phone = new Phone;
$phone->muser()->dissociate();
$r = $phone->save();
dump($r);
}
此方法会插入一条记录,但这条记录的uid
为null,前提是uid
字段允许为空,如果不允许为空会抛出异常
空对象模型
如果外键字段uid
允许为空,当我们访问Phone
模型上的muser
属性(注意这里是属性,而不是方法)时,默认返回null,Eloquent允许我们为这种空对象定义一个默认的类型,这个对象类型在定义关联时指定,通过withDefault()
方法(这个方法好像在前面提到过)
withDefault()
如果不指定值默认返回Model实例
public function muser(){
return $this->belongsTo('App\Muser','uid','uid')->withDefault();
}
public function muser(){
return $this->belongsTo('App\Muser','uid','uid')->withDefault(['name'=>'测试']);
}
显示默认参数
多对多关联
附加/移除
一个学生可以选修多门选修课,同时一门选修课又可以被多个学生选修,学生与选修课之间是多对多的关系,那如果我要给某个学生在增加一门选修课怎么办?
通过中间表插入一条记录实现 使用attach
方法
为学号为1的学生添加一门选修课
public function show(){
$classId = 4;
$stu = Stu::find(1);
$res=$stu->mclass()->attach($classId);
dump($res);
}
同时attach
方法还可指定额外参数,以数组形式
public function show(){
$classId = 4;
$stu = Stu::find(1);
$res=$stu->mclass()->attach($classId,['flag'=>'测试']);
dump($res);
}
由附加就有移除,有时需要移除学生的选修课,删除多对多关联记录,使用detach
方法
如:学号为1的学生,取消其选修的4号课程
public function show(){
$stu = Stu::find(1);
$stu->mclass()->detach(4);
}
移除所有学生选修的课程
$stu->mclass()->detach()
同时attach和detach都允许传入ID数组
$stu->mclass()->detach([1,2,3,4])
$stu->mclass()->attach([class_id=>['额外字段'=>'额外字段值']])
同步关联
使用sync方法构造多对多关联,sync方法接受ID数组,向中间表插入对应关联数据记录,在这里没有被放在数组里的id会从中间表移除。
中间表没有改变之前的记录(只看sid=1)
学号为1 的学生选修了课程id为1,2,3
使用sync进行同步关联
public function show(){
$stu = Stu::find(1);
$res = $stu->mclass()->sync([2,3,9]);
dump($res);
}
根据上面的定义我们知道sync会向中间表插入关联数据记录,并且没有放在数组里的id会被移除,如果是这样的话那1号学生选修的课程1将被移除,同时会向中间表添加课程id=9的记录
接下来看他的执行过程是否与我们想的一样
通过Laravel Debugbar显示的sql语句我们可以很清楚的看到其执行过程
查看数据库
和我们料想得一致,课程id为1的数据被移除,并且新增了课程id为9的记录
sync支持额外数据添加
通过id传递其他额外的数据到中间表
$stu->mclass()->sync([1 => ['flag' => true], 2, 3]);
如果你不想移除不在sync数组里的id的记录可使用syncWithoutDetaching
public function show(){
$stu = Stu::find(1);
// $res = $stu->mclass()->sync([2,3,9]);
$res = $stu->mclass()->syncWithoutDetaching([2,3,1]);
dump($res);
}
切换关联
多对多关联它提供了一个toggle方法用于切换给定ID的附加状态,意思就是如果toggle里面的id存在于中间表,则被移除,如果不存在则会被添加
public function show(){
$stu=Stu::find(1);
$res = $stu->mclass()->toggle([3]);
dump($res);
}
开始数据库有两个sid=1,cid=3的记录
运行上例代码后
发现sid=1,cid=3的两条记录被移除
当在次运行时,按照定义应该会在中间表插入sid=1,cid=3的记录
和我们猜想的一样
插入数据
使用save
插入单个数据(添加时需设置fillable定义运行添加的字段,否则程序可能会抛出异常)
该代码会先在课程表中插入数据,并且更新中间表的记录
public function show(){
$stu = Stu::find(1);
$res = $stu->mclass()->save(
new Mclass(['cname'=>'测试']),['中间表额外字段'=>'额外值']
);
}
完整sql
select * from `stus` where `stus`.`sid` = 1 limit 1
insert into `classes` (`cname`, `updated_at`, `created_at`) values ('测试', '2020-01-30 08:11:18', '2020-01-30 08:11:18')
insert into `stu__classes` (`cid`, `sid`) values (8, 1)
使用saveMany()
插入多个数据
public function show(){
$stu = Stu::find(1);
$res = $stu->mclass()->saveMany([
new Mclass(['cname'=>'测试1']),
new Mclass(['cname'=>'测试2'])
]);
}
插入多条数据添加并添加额外字段,通过键值关联对应记录与额外字段
第一个参数 插入的值(数组),第二个参数插入值对应的额外字段(数组)
public function show(){
$stu = Stu::find(1);
$res = $stu->mclass()->saveMany([
1=>new Mclass(['cname'=>'a']),
2=>new Mclass(['cname'=>'b'])
],
[
1=>['flag'=>1],
2=>['flag'=>2]
]
);
}
更新中间表记录
有时候你需要更新中间表中已经存在的记录,使用updateExistingPivot方法
该方法接受中间记录另一个的外键和一个关联数组进行更新
public function show(){
$stu = Stu::find(1);
$res = $stu->mclass()->updateExistingPivot(15,['flag'=>'更新']);
dump($res);
}
对应sql
update `stu__classes` set `flag` = '更新' where `sid` = 1 and `cid` = 15
更新父级时间戳
当一个模型belongsTo或者belongsToMany另一个模型时,如评论(comment)属于一篇文章(Post),有时更新子模型导致父模型时间戳更新非常有用
如当一个Comment模型更新时,你要自动使父模型Post时间更新,要实现这一概念只需在子模型加一个包含关联名称(对应关联方法的名称)的touches属性即可,支持添加多个关联关系
如:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Stu extends Model
{
protected $primaryKey = 'sid';
public $timesTamp = false;
//子模型更新时,使父模型时间更新
protected $touches = ['mclass'];
public function mclass(){
return $this->belongsToMany('App\Mclass','stu__classes','sid','cid');
}
}
public function show(){
$stu = Stu::find(1);
$stu->sname='唐小涵';
$stu->save();
}