ThinkPHP5模型多种管理方式

Published on 2016 - 12 - 20

一对一关联

定义

定义一对一关联,例如,一个用户都有一个个人资料,我们定义User模型如下:

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile');
    }
}

hasOne方法的参数包括:

hasOne('关联模型名','外键名','主键名',['模型别名定义'],'join类型');

默认的join类型为INNER。

V5.0.3+版本开始,可以支持为关联模型定义需要查询的字段,例如:

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile')->field('id,name,email');
    }
}

关联查找

定义好关联之后,就可以使用下面的方法获取关联数据:

$user = User::find(1);
// 输出Profile关联模型的email属性
echo $user->profile->email;

默认情况下, 我们使用的是user_id 作为外键关联,如果不是的话则需要在关联定义的时候指定,例如:

namespace app\index\model;

use think\Model;

class User extends Model 
{
    public function profile()
    {
        return $this->hasOne('Profile','uid');
    }
}

有一点需要注意的是,关联方法的命名规范是驼峰法,而关联属性则一般是小写+下划线的方式,系统在获取的时候会自动转换对应,读取user_profile关联属性则对应的关联方法应该是userProfile。

设置别名

如果你的模型名和数据库关键字冲突的话,可以设置别名,例如:

namespace app\index\model;

use think\Model;

class User extends Model 
{
    public function profile()
    {
        return $this->hasOne('Profile','uid')->setAlias(['user'=>'member']);
    }
}

关联新增

$user = User::find(1);
// 如果还没有关联数据 则进行新增
$user->profile()->save(['email' => 'thinkphp']);

系统会自动把当前模型的主键传入profile模型。

关联更新

和新增一样使用save方法进行更新关联数据。

$user = User::find(1);
$user->profile->email = 'thinkphp';
$user->profile->save();
// 或者
$user->profile->save(['email' => 'thinkphp']);

定义相对的关联

我们可以在Profile模型中定义一个相对的关联关系,例如:

namespace app\index\model;

use think\Model;

class Profile extends Model 
{
    public function user()
    {
        return $this->belongsTo('User');
    }
}

belongsTo的参数包括:

belongsTo('关联模型名','外键名','关联表主键名',['模型别名定义'],'join类型');

默认的关联外键是user_id,如果不是,需要在第二个参数定义

namespace app\index\model;

use think\Model;

class Profile extends Model 
{
    public function user()
    {
        return $this->belongsTo('User','uid');
    }
}

我们就可以根据档案资料来获取用户模型的信息

$profile = Profile::find(1);
// 输出User关联模型的属性
echo $profile->user->account;

一对多关联

关联定义

一对多关联的情况也比较常见,使用hasMany方法定义,

参数包括:

hasMany('关联模型名','外键名','主键名',['模型别名定义']);

例如一篇文章可以有多个评论

namespace app\index\model;

use think\Model;

class Article extends Model 
{
    public function comments()
    {
        return $this->hasMany('Comment');
    }
}

同样,也可以定义外键的名称

namespace app\index\model;

use think\Model;

class Article extends Model 
{
    public function comments()
    {
        return $this->hasMany('Comment','art_id');
    }
}

如果需要指定查询字段,可以使用下面的方式:

namespace app\index\model;

use think\Model;

class Article extends Model 
{
    public function comments()
    {
        return $this->hasMany('Comment')->field('id,author,content');
    }
}

关联查询

我们可以通过下面的方式获取关联数据

$article = Article::get(1);
// 获取文章的所有评论
dump($article->comments);
// 也可以进行条件搜索
dump($article->comments()->where('status',1)->select());

根据关联条件查询

可以根据关联条件来查询当前模型对象数据,例如:

// 查询评论超过3个的文章
$list = Article::has('comments','>',3)->select();
// 查询评论状态正常的文章
$list = Article::hasWhere('comments',['status'=>1])->select();

关联新增

$article = Article::find(1);
// 增加一个关联数据
$article->comments()->save(['content'=>'test']);
// 批量增加关联数据
$article->comments()->saveAll([
    ['content'=>'thinkphp'],
    ['content'=>'onethink'],
]);

定义相对的关联

要在 Comment 模型定义相对应的关联,可使用 belongsTo 方法:

name app\index\model;

use think\Model;

class Comment extends Model 
{
    public function article()
    {
        return $this->belongsTo('article');
    }
}

远程一对多

远程一对多关联用于定义有跨表的一对多关系,例如:

  • 每个城市有多个用户
  • 每个用户有多个话题
  • 城市和话题之间并无关联

关联定义

就可以直接通过远程一对多关联获取每个城市的多个话题,City模型定义如下:

namespace app\index\model;

use think\Model;

class City extends Model 
{
    public function topics()
    {
        return $this->hasManyThrough('Topic','User');
    }
}

远程一对多关联,需要同时存在Topic和User模型。

hasManyThrough方法的参数如下:

hasManyThrough('关联模型名','中间模型名','外键名','中间模型关联键名','当前模型主键名',['模型别名定义']);

关联查询

我们可以通过下面的方式获取关联数据

$city = City::get(1);
// 获取同城的所有话题
dump($city->topics);
// 也可以进行条件搜索
dump($city->topics()->where('topic.status',1)->select());

条件搜索的时候,需要带上模型名作为前缀

多对多关联

关联定义

例如,我们的用户和角色就是一种多对多的关系,我们在User模型定义如下:

namespace app\index\model;

use think\Model;

class User extends Model 
{
    public function roles()
    {
        return $this->belongsToMany('Role');
    }
}

belongsToMany方法的参数如下:

belongsToMany('关联模型名','中间表名','外键名','当前模型关联键名',['模型别名定义']);

关联查询

我们可以通过下面的方式获取关联数据

$user = User::get(1);
// 获取用户的所有角色
dump($user->roles);

关联新增

$user = User::get(1);
// 增加关联数据 会自动写入中间表数据
$user->roles()->save(['name'=>'管理员']);
// 批量增加关联数据
$user->roles()->saveAll([
    ['name'=>'管理员'],
    ['name'=>'操作员'],
]);

只新增中间表数据,可以使用

$user = User::get(1);
// 仅增加关联的中间表数据
$user->roles()->save(1);
// 或者
$role = Role::get(1);
$user->roles()->save($role);
// 批量增加关联数据
$user->roles()->saveAll([1,2,3]);

单独更新中间表数据,可以使用:

$user = User::get(1);
// 增加关联的中间表数据
$user->roles()->attach(1);
// 传入中间表的额外属性
$user->roles()->attach(1,['remark'=>'test']);
// 删除中间表数据
$user->roles()->detach([1,2,3]);

定义相对的关联

我们可以在Role模型中定义一个相对的关联关系,例如:

namespace app\index\model;

use think\Model;

class Role extends Model 
{
    public function users()
    {
        return $this->belongsToMany('User');
    }
}

动态属性

模型对象的关联属性可以直接作为当前模型对象的动态属性进行赋值或者取值操作,虽然该属性并非数据表字段,例如:

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile');
    }
}

我们在使用

// 查询模型数据
$user = User::find(1);
// 获取动态属性
dump($user->profile);
// 给关联模型属性赋值
$user->profile->phone = '1234567890';
// 保存关联模型数据
$user->profile->save();

在获取动态属性profile的同时,模型会通过定义的关联方法去查询关联对象的数据并赋值给该动态属性,这是一种关联数据的“惰性加载”,只有真正访问关联属性的时候才会进行关联查询。

关联预载入

关联查询的预查询载入功能,主要解决了N+1次查询的问题,例如下面的查询如果有3个记录,会执行4次查询:

$list = User::all([1,2,3]);
foreach($list as $user){
    // 获取用户关联的profile模型数据
    dump($user->profile);
}

如果使用关联预查询功能,对于一对一关联来说,只有一次查询,对于一对多关联的话,就可以变成2次查询,有效提高性能。

$list = User::with('profile')->select([1,2,3]);
foreach($list as $user){
    // 获取用户关联的profile模型数据
    dump($user->profile);
}

支持预载入多个关联,例如:

$list = User::with('profile,book')->select([1,2,3]);

也可以支持嵌套预载入,例如:

$list = User::with('profile.phone')->select([1,2,3]);
foreach($list as $user){
    // 获取用户关联的phone模型
    dump($user->profile->phone);
}

可以在模型的get和all方法中使用预载入,和使用select方法是等效的:

$list = User::all([1,2,3],'profile,book');

如果要指定属性查询,可以使用:

$list = User::field('id,name')->with(['profile'=>function($query){$query->withField('email,phone');}])->select([1,2,3]);
foreach($list as $user){
    // 获取用户关联的profile模型数据
    dump($user->profile);
}

聚合模型

版本 调整功能
5.0.3 支持使用field属性定义需要的字段
5.0.2 relationModel 属性改为非静态定义

通过聚合模型可以把一对一关联的操作更加简化,只需要把你的模型类继承think\model\Merge,就可以自动完成关联查询、关联保存和关联删除。

例如下面的用户表关联了档案表,两个表信息如下:

think_user

字段名 描述
id 主键
name 用户名
password 密码
nickname 昵称

think_profile

字段名 描述
id 主键
truename 真实姓名
phone 电话
email 邮箱
user_id 用户ID

我们只需要定义好主表的模型,例如下面是User模型的定义:

namespace app\index\model;

use think\model\Merge;

class User extends Merge
{
    // 定义关联模型列表
    protected static $relationModel = ['Profile'];
    // 定义关联外键
    protected $fk = 'user_id';
    protected $mapFields = [
        // 为混淆字段定义映射
        'id'        =>  'User.id',
        'profile_id' =>  'Profile.id',
    ];
}

V5.0.2+版本relationModel 属性不再使用static定义了。

如果需要单独设置关联数据表,可以使用:

namespace app\index\model;

use think\model\Merge;

class User extends Merge
{
    // 设置主表名
    protected $table = 'think_user';
    // 定义关联模型列表
    protected static $relationModel = [
        // 给关联模型设置数据表
        'Profile'   =>  'think_user_profile',
    ];
    // 定义关联外键
    protected $fk = 'user_id';
    protected $mapFields = [
        // 为混淆字段定义映射
        'id'        =>  'User.id',
        'profile_id' =>  'Profile.id',
    ];
}

注意:对于关联表中存在混淆的字段名一定要通过mapFields属性定义。

接下来,我们可以和使用普通模型一样的方法来操作用户模型及其关联数据。

// 关联查询
$user = User::get(1);
echo $user->id;
echo $user->name;
echo $user->phone;
echo $user->email;
echo $user->profile_id;
$user->email = 'thinkphp@qq.com';
// 关联保存
$user->save();
// 关联删除
$user->delete();
// 根据主键关联删除
User::destroy([1,2,3]);

操作两个数据表就和操作一个表一样的感觉,关联表的写入、更新和删除自动采用事务(只要数据库支持事务),一旦主表写入失败或者发生异常就会发生回滚。

如果主表除了Profile关联之外,还有其他的一对多关联,一样可以定义额外的关联,例如:

namespace app\index\model;
use think\model\Merge;

class User extends Merge
{
    // 定义关联模型列表
    protected static $relationModel = ['Profile'];
    // 定义关联外键
    protected $fk = 'user_id';
    protected $mapFields = [
        // 为混淆字段定义映射
        'id'        =>  'User.id',
        'profile_id' =>  'Profile.id',
    ];

    public function articles(){
        return $this->hasMany('Article');
    }
}

对一对多关联进行操作,例如:

$user = User::get(1);
// 读取关联信息
dump($user->articles);
// 或者进行关联预载入
$user = User::get(1,'articles');

注意:不能再次对 已经在relationModel属性中定义过的关联表进行关联定义和预载入查询。

参考文档