一、序言
最近在写ThinkPHP关联模型的时候一些用法总忘,我就想通过写博客的方式复习和整理下一些用法。
具体版本:
- topthink/framework:6.1.4
- topthink/think-orm:2.0.61
二、实例应用
1、一对多的关联
本文案例:一个用户对应多篇文章,一篇文章对应多条评论。
以下是用户、文章、评论的模型建立:
(1)用户模型
<?php
/**
* Created by PhpStorm
* Author: fengzi
* Date: 2023/12/19
* Time: 14:50
*/
namespace app\common\model;
/**
* 用户模型
*/
class UserModel extends ComBaseModel
{
protected $name='user';
/**
* 文章关联表:一对多
* hasMany()
* 第一个参数:要关联的模型类
* 第二个参数:在关联表中表示当前模型的外键名
* 第三个参数:当前模型(userModel)的主键名
* @return \think\model\relation\HasMany
* @Author: fengzi
* @Date: 2024/7/3 11:40
*/
public function article()
{
return $this->hasMany(ArticleModel::class,'user_id','id');
}
/**
* 评论表模型:远程一对多关联
* hasManyThrough()
* 第一个参数(必须):关联的模型类
* 第二个参数(必须):中间表的模型类
* 第三个参数:在中间模型中表示当前模型的外键
* 第四个参数:在关联模型中表示当前模型的外键
* 第五个参数:当前模型主键
* 第六个参数:中间模型主键
* @return \think\model\relation\HasManyThrough
* @Author: fengzi
* @Date: 2024/7/3 11:42
*/
public function articleComment()
{
return $this->hasManyThrough(ArticleCommentModel::class, ArticleModel::class, 'user_id', 'user_id', 'id', 'id');
}
}
(2)文章模型
<?php
/**
* Created by PhpStorm
* Author: fengzi
* Date: 2023/12/19
* Time: 14:50
*/
namespace app\common\model;
/**
* 文章模型
*/
class ArticleModel extends ComBaseModel
{
protected $name='article';
/**
* 用户模型
* belongsTo()
* 第一个参数:关联模型类
* 第二个参数:在当前模型(ArticleModel)中表示关联模型(UserModel)的外键
* 第三个参数:关联模型主键
* @return \think\model\relation\BelongsTo
* @Author: fengzi
* @Date: 2024/7/3 13:54
*/
public function user()
{
return $this->belongsTo(UserModel::class, 'user_id', 'id');
}
/**
* 评论关联表:一对多
* hasMany()
* 第一个参数:要关联的模型类
* 第二个参数:在关联表中表示当前模型的外键名
* 第三个参数:当前模型(ArticleModel)的主键名
* @return \think\model\relation\HasMany
* @Author: fengzi
* @Date: 2024/7/3 11:40
*/
public function comment()
{
return $this->hasMany(ArticleCommentModel::class, 'article_id', 'id');
}
}
(3)评论模型
<?php
/**
* Created by PhpStorm
* Author: fengzi
* Date: 2023/12/19
* Time: 14:50
*/
namespace app\common\model;
/**
* 文章评论模型
*/
class ArticleCommentModel extends ComBaseModel
{
protected $name='article_comment';
/**
* 评论模型
* belongsTo()
* 第一个参数:关联模型类
* 第二个参数:在当前模型(ArticleCommentModel)中表示关联模型(ArticleModel)的外键
* 第三个参数:关联模型主键
* @return \think\model\relation\BelongsTo
* @Author: fengzi
* @Date: 2024/7/3 13:54
*/
public function article()
{
return $this->belongsTo(ArticleModel::class, 'article_id', 'id');
}
}
2、一对多查询:
(1)一对多的简单查询示例
public function many()
{
// 实例化模型
$userModel = app(UserModel::class);
$articleModel = app(ArticleModel::class);
$articleCommentModel = app(ArticleCommentModel::class);
/** 关联查询 */
// 查询ID=1的用户
$user = $userModel->find(1);
dump($user->toArray());
// 查询ID=1的用户的所有文章,【article()】就是UserModel模型中定义的关联方法
$article = $user->article()->select()->toArray();
dump($article);
/** 根据关联条件查询 */
// 查询评论大于等于4个的文章,【comment】就是ArticleModel模型中定义的【comment()】关联方法
$lists = $articleModel->has('comment','>=',4)->select()->toArray();
dump($lists);
// 查询评论评论来源为APP的文章(评论来源source:1web,2App),【comment】就是ArticleModel模型中定义的【comment()】关联方法
$lists = $articleModel->hasWhere('comment',['source'=>2])->select()->toArray();
dump($lists);
/** 复杂的关联条件查询 */
// 当有复杂的查询条件时,可以先通过评论表进行查询,把查询结果的对象当作条件,再关联文章表
// 不需要select()或find()方法进行查询,直接返回查询对象即可
$articleCommentWhere = $articleCommentModel->where([['source', '=', 2], ['content', 'like', '%有点耐%'], ['user_id', '=', 1]]);
// 【comment】就是ArticleModel模型中定义的【comment()】关联方法
$lists = $articleModel->hasWhere('comment', $articleCommentWhere)->select()->toArray();
dd($lists);
}
(2)一对多的预查询加载
- 一对多关联查询的时候只能用【with()】,不能用【withJion()】。
- 当用户模型(UserModel)中关联文章变成一对一的时候【return $this->hasOne(ArticleModel::class,'user_id','id');】,【with()】和【withJion()】两个都可以使用。但需注意以下几点:
- 使用with()的时候,关联数据可以正常输出,withCount()等方法可以同时使用。
- 使用withJoin()的时候,关联数据可以正常输出,但withCount()等方法不可以同时使用。
- withCount()、withSum()、withAvg()、withMax()等类似的方法所输出的字段格式为:关联名称(article)+ “_” + [count | sum | avg | max]。例如:article_count
public function many()
{
// 实例化模型
$userModel = app(UserModel::class);
// 查询ID=1的用户,关联查询文章,【article】就是UserModel模型中定义的关联方法
$userWith = $userModel->with(['article'])
->withCount(['article'])
->withSum(['article'], 'read_num')
->withAvg(['article'], 'read_num')
->withMax(['article'], 'read_num')
->find(1)->toArray();
dump($userWith);
// 查询ID=1的用户,关联查询文章,【article】就是UserModel模型中定义的关联方法
$userWithJon = $userModel->withJoin(['article'])
->withCount(['article'])
->withSum(['article'], 'read_num')
->withAvg(['article'], 'read_num')
->withMax(['article'], 'read_num')
->find(1)->toArray();
dd($userWithJon);
}
- 关联查询使用闭包时,如果只需显示关联模型中的几个字段,field() 方法中必须带上两个模型中的关联字段(下图中的关联字段就是UserModel模型中article()里面的第二个参数[ user_id ])。当然,带上关联字段后,关联字段也会显示出来。
- 关联查询使用闭包时,如果要过滤关联模型中的几个字段不显示,使用 withoutField() 方法。这时就不用带上关联字段。
3、一对多关联新增
常用新增的使用(下图有执行插入操作后的数据情况),我总共使用了四种方法进行插入,下图可以看见结果。
- 使用【insert】和【insertGetId】虽说可以插入关联,但是关联字段[ user_id ]不会自动写入数据。而且在设置了自动写入时间的情况下,[ create_time ] 和 [ update_time ] 也没有数据。
- 使用【save】可以插入关联数据,也可以自动写入关联字段[ user_id ]和[ create_time ] 和 [ update_time ] 的数据。【saveAll】也是可行的。
- 使用【create】不能写入数据,会直接报错。
- 从用户模型添加关联数据到文章模型时,我们只需要在用户模型中设置关联模式。在关联模型(ArticleModel)中的相对关联不是必须设置的。
/** * 用户模型中的文章关联:一对多(必须设置才能实现关联数据的添加) * hasMany() * 第一个参数:要关联的模型类 * 第二个参数:在关联表中表示当前模型的外键名 * 第三个参数:当前模型(userModel)的主键名 * @return \think\model\relation\HasMany * @Author: fengzi * @Date: 2024/7/3 11:40 */ public function article() { return $this->hasMany(ArticleModel::class,'user_id','id'); }
/** * 文章模型中相对关联的用户模型(通过用户模型添加文章的关联数据时非必须) * belongsTo() * 第一个参数:关联模型类 * 第二个参数:在当前模型(ArticleModel)中表示关联模型(UserModel)的外键 * 第三个参数:关联模型主键 * @return \think\model\relation\BelongsTo * @Author: fengzi * @Date: 2024/7/3 13:54 */ public function user() { return $this->belongsTo(UserModel::class, 'user_id', 'id'); }
public function many()
{
// 实例化模型
$userModel = app(UserModel::class);
// 查询ID=10的用户
$user = $userModel->find(10);
// 使用insert添加ID=10用户的关联文章
$user->article()->insert(['title' => '厦门旅游', 'status' => 1]);
// 使用insertGetId添加ID=10用户的关联文章
$user->article()->insertGetId(['title' => '黄山旅游', 'status' => 2]);
// 使用save添加ID=10用户的关联文章
$user->article()->save(['title' => '泰山旅游', 'status' => 1]);
// 使用create添加ID=10用户的关联文章
$user->article()->create(['title' => '泰山旅游', 'status' => 2]);
dd($user->toArray());
}
4、一对多关联删除
一对多的关联删除跟【ThinkPHP一对一关联模型的运用(ORM)】文章中的【1.6.1、with 的关联删除】小节差不多,在此就不在重复赘述了。
在此我再补充一些细节:
- 关联删除的时候用select()方法查出来的数据不能用together()进行关联数据的删除。
- 链式操作的方式使用together()的时候一定要注意一个细节:前面查找的数据一定不能为空,不然链式操作直接跟together()会报错( Call to a member function together() on null )。
- 必须以查询结果的对象进行together()操作(使用find()方法后获取到一个查询结果对象)。不能直接条件查询后进行together()操作,不然会报错“method not exist:think\db\Query->together”。
public function many() { // 实例化模型 $userModel = app(UserModel::class); // 正确使用:必须以查询结果的对象进行together()操作,使用find()方法后获取到一个查询结果对象。 $res = $userModel->with(['article'])->where('id', 1)->find()->together(['article'])->delete(); // 错误使用:不能直接条件查询后进行together()操作,不然会报错“method not exist:think\db\Query->together” $res = $userModel->with(['article'])->where('id', 1)->together(['article'])->delete(); dd($res); }
5、一对多的关联编辑
一对多的关联编辑跟【ThinkPHP一对一关联模型的运用(ORM)】文章中的【1.5、with 关联修改】小节差不多,在此就举几个简单的例子:
public function many()
{
// 实例化模型
$userModel = app(UserModel::class);
// 获取用户ID=4的信息
$res = $userModel->with(['article'])->where('id', 4)->find();
// 打印修改前的数据
dump($res->toArray());
// 修改用户名
$res->name = 'xiaohuang';
// 保存修改
$res->save();
// 添加标题为“海口旅游攻略”的新文章
$res->article()->save(['title' => '海口旅游攻略']);
// 修改文章ID=7的阅读量
$res->article()->save(['id'=>7, 'read_num' => 1000]);
// 查询打印修改后的数据
$info = $userModel->with(['article'])->where('id', 4)->find();
dd($info->toArray());
}