第十二天:(7)关联【模型和关联】

第十二天:(7)关联【模型和关联】

基本定义

ThinkPHP5.0的关联采用了对象化的操作模式,你无需继承不同的模型类,只是把关联定义成一个方法,并且直接通过当前模型对象的属性名获取定义的关联数据。

举个例子,有一个用户模型User,有一个关联的模型对象Book,每个用户有多本书,User模型定义如下:

<?php
namespace app\index\model;
use think\Model;
class User extends Model{    // 定义关联方法
    public function books()
    {        return $this->hasMany('Book');
    }
}

User模型的books方法就是一个关联定义方法,方法名可以随意命名,但注意要避免和User模型对象的字段属性冲突。

实际获取关联数据的时候,就是采用下面的方式:

$user = User::get(5);// 获取User对象的nickname属性
echo $user->nickname;// 获取User对象的Book关联对象
dump($user->books);// 执行关联的Book对象的查询
$user->books()->where('name','thinkphp')->find();

对于涉及的代码用法,目前不用深究,后面会详细描述。

一般来说,关联关系包括:

  • 一对一关联:HAS_ONE 以及相对的 BELONGS_TO

  • 一对多关联:HAS_MANY 以及相对的 BELONGS_TO

  • 多对多关联:BELONGS_TO_MANY

后面会详细讲解每一种关联的用法。

一对一关联

一对一关联是一种最简单的关联,例如每个用户都有一份档案,每个公司都有一个营业执照等等。

在这之前,我们先创建数据表如下:

DROP TABLE IF EXISTS `think_user`;CREATE TABLE IF NOT EXISTS `think_user` (  `id` int(6) UNSIGNED NOT NULL AUTO_INCREMENT,  `nickname` varchar(25) NOT NULL,  `name` varchar(25) NOT NULL,  `password` varchar(50) NOT NULL,  `create_time` int(11) UNSIGNED NOT NULL,  `update_time` int(11) UNSIGNED NOT NULL,  `status` tinyint(1) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `think_profile`;CREATE TABLE IF NOT EXISTS `think_profile` (  `id` int(6) UNSIGNED NOT NULL AUTO_INCREMENT,  `truename` varchar(25) NOT NULL,  `birthday` int(11) NOT NULL,  `address` varchar(255) DEFAULT NULL,  `email` varchar(255) DEFAULT NULL,  `user_id` int(6) UNSIGNED NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

关联定义

我们以用户和档案的一对一关联为例,在User模型类中添加关联定义方法,然后在方法中调用hasOne方法即可:

<?php
namespace 
app\index\model;
use think\Model;
class User extends Model{    // 开启自动写入时间戳
    protected $autoWriteTimestamp = true;    // 定义自动完成的属性
    protected $insert = ['status' => 1];    // 定义关联方法
    public function profile()
    {        // 用户HAS ONE档案关联
        return $this->hasOne('Profile');
    }
}

hasOne方法有5个参数,依次分别是:

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

默认的外键是:当前模型名_id,主键则是自动获取,如果你的表设计符合这一规范的话,只需要设置关联的模型名即可.

通常关联模型和当前模型都是相同的命名空间,如果关联模型在不同的命名空间,需要指定完整的类名,例如:

// 关联admin模块下面的模型对象
return $this->hasOne('\app\admin\Profile');

在关联查询的时候,默认使用当前模型的名称(小写)作为数据表别名,可以指定查询使用的数据表别名,例如:

// 用户HAS ONE档案关联
return $this->hasOne('Profile','user_id','id',['user'=>'member','profile'=>'info']);

要进行模型的关联操作,我们必须同时定义好关联模型,Profile模型定义如下:

<?php
namespace app\index\model;

use think\Model;class Profile extends Model{    
    protected $type       = [        
        'birthday' => 'timestamp:Y-m-d',
    ];

}

可以看到Profile模型中并没有定义关联方法。如果你的关联操作都是基于User模型的话,Profile模型中并不需要定义关联方法。

如果你需要基于Profile模型来进行关联操作,则需要在Profile模型中定义对应的BELONGS_TO关联,如下:

<?php
namespace app\index\model;
use think\Model;
class Profile extends Model{    
    protected $type       = [        
    'birthday' => 'timestamp:Y-m-d',
    ];    
    public function user()
    {        // 档案 BELONGS TO 关联用户
        return $this->belongsTo('User');
    }
}

belongsTo方法和hasOne一样,也有5个参数:

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

关联写入

首先来看下如何进行关联数据的写入,创建User控制器的add操作方法如下:

<?php
namespace app\index\controller;
use app\index\model\Profile;
use app\index\model\User as UserModel;
class User{    // 关联新增数据
    public function add()
    {        $user           = new UserModel;        
    $user->name     = 'thinkphp';        
    $user->password = '123456';        
    $user->nickname = '流年';        
        if ($user->save()) {            // 写入关联数据
            $profile           = new Profile;            
            $profile->truename = '刘晨';            
            $profile->birthday = '1977-03-05';            
            $profile->address  = '中国上海';            
            $profile->email    = 'thinkphp@qq.com';            
            $user->profile()->save($profile);            
            return '用户新增成功';
        } else {            
            return $user->getError();
        }
    }
}

关联模型的写入调用了关联方法profile(),该方法返回的是一个Relation对象,执行save方法会自动传入当前模型User的主键作为关联键值,所以不需要手动传入Profile模型的user_id属性。

save方法也可以直接使用数组而不是Profile对象,例如:

<?php
namespace 
app\index\controller;
use app\index\model\Profile;
use app\index\model\User as UserModel;
class User extends Controller{    // 关联新增数据
    public function add()
    {        
    $user           = new UserModel;        
    $user->name     = 'thinkphp';        
    $user->password = '123456';        
    $user->nickname = '流年';        
    if ($user->save()) {            // 写入关联数据
            $profile['truename'] = '刘晨';            
            $profile['birthday'] = '1977-03-05';            
            $profile['address']  = '中国上海';            
            $profile['email']    = 'thinkphp@qq.com';            
            $user->profile()->save($profile);            
            return '用户[ ' . $user->name . ' ]新增成功';
        } else {            
            return $user->getError();
        }
    }
}

关联查询

一对一的关联查询很简单,直接把关联对象当成属性来用即可,例如:

public function read($id){    
$user = UserModel::get($id);    
echo $user->name . '<br/>';    
echo $user->nickname . '<br/>';    
echo $user->profile->truename . '<br/>';    
echo $user->profile->email . '<br/>';
}

访问URL地址:

http://tp5.com/user/1

最后输出结果为:

thinkphp流年
刘晨
thinkphp@qq.com

以上关联查询的时候,只有在获取关联对象($user->profile)的时候才会进行实际的关联查询,缺点是会可能进行多次查询,但可以使用预载入查询来提高查询性能,对于一对一关联来说,只需要进行一次查询即可获取关联对象数据,例如:

public function read($id){    
$user = UserModel::get($id,'profile');    
echo $user->name . '<br/>';    
echo $user->nickname . '<br/>';    
echo $user->profile->truename . '<br/>';   
 echo $user->profile->email . '<br/>';
}

get方法使用第二个参数就表示进行关联预载入查询。

关联更新

一对一的关联更新如下:

public function update($id){    
$user       = UserModel::get($id);    
$user->name = 'framework';    
if ($user->save()) {        // 更新关联数据
        $user->profile->email = 'liu21st@gmail.com';        
        $user->profile->save();        return '用户[ ' . $user->name . ' ]更新成功';
    } else {        
        return $user->getError();
    }
}

访问URL地址:

http://tp5.com/user/update/1

最后输出结果为:

用户更新成

关联删除

关联删除代码如下:

public function delete($id){    
$user = UserModel::get($id);    
if ($user->delete()) {        // 删除关联数据
        $user->profile->delete();        
        return '用户[ ' . $user->name . ' ]删除成功';
    } else {        
        return $user->getError();
    }
}

访问URL地址:

http://tp5.com/user/delete/1

页面输出结果为:

用户删除成功

一对多关联

每个作者写有多本书就是一个典型的一对多关联,首先创建如下数据表:

DROP TABLE IF EXISTS `think_book`;CREATE TABLE IF NOT EXISTS `think_book` (  `id` int(8) UNSIGNED NOT NULL AUTO_INCREMENT,  `title` varchar(255) NOT NULL,  `publish_time` int(11) UNSIGNED DEFAULT NULL,  `status` tinyint(1) NOT NULL,  `user_id` int(6) UNSIGNED NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

关联定义

User模型类添加Book关联如下:

<?php
namespace app\index\model;
use think\Model;
class User extends Model{    // 开启自动写入时间戳
    protected $autoWriteTimestamp = true;    // 定义自动完成的属性
    protected $insert = ['status' => 1];    // 定义关联方法
    public function profile()
    {        
    return $this->hasOne('Profile');
    }    // 定义关联
    public function books()
    {        
    return $this->hasMany('Book');
    }
}

hasMany的参数如下:

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

Book模型类定义如下:

<?php
namespace app\index\model;

use think\Model;
class Book extends Model{    
protected $type       = [        
'publish_time' => 'timestamp:Y-m-d',
    ];    // 开启自动写入时间戳
    protected $autoWriteTimestamp = true;    // 定义自动完成的属性
    protected $insert = ['status' => 1];

}

如果需要定义对应的关联,则可以使用belongsTo方法:

<?php
namespace app\index\model;
use think\Model;
class Book extends Model{    
protected $type       = [        'publish_time' => 'timestamp:Y-m-d',
    ];    // 开启自动写入时间戳
    protected $autoWriteTimestamp = true;    // 定义自动完成的属性
    protected $insert = ['status' => 1];    // 定义关联方法
    public function user()
    {        
    return $this->belongsTo('User');
    }

}

关联新增

添加addBook方法用于新增关联数据:

public function addBook(){    
$user               = UserModel::get(1);    
$book               = new Book;    
$book->title        = 'ThinkPHP5快速入门';    
$book->publish_time = '2016-05-06';    
$user->books()->save($book);    
return '添加Book成功';
}

对于一对多关联,也可以批量增加数据:

public function addBook(){    
$user  = UserModel::get(1);    
$books = [
        ['title' => 'ThinkPHP5快速入门', 'publish_time' => '2016-05-06'],
        ['title' => 'ThinkPHP5开发手册', 'publish_time' => '2016-03-06'],
    ];    
    $user->books()->saveAll($books);    
    return '添加Book成功';
}

关联查询

可以直接调用模型的属性获取全部关联数据,例如:

public function read(){    
$user  = UserModel::get(1);    
$books = $user->books;
    dump($books);
}

一对多查询同样可以使用预载入查询,例如:

public function read(){    
$user  = UserModel::get(1,'books');    
$books = $user->books;
    dump($books);
}

一对多预载入查询会在原先延迟查询的基础上增加一次查询,可以解决典型的N+1次查询问题。

如果要过滤查询,可以调用关联方法:

public function read(){    
$user  = UserModel::get(1);    // 获取状态为1的关联数据
    $books = $user->books()->where('status',1)->select();
    dump($books);    // 获取作者写的某本书
    $book  = $user->books()->getByTitle('ThinkPHP5快速入门');
    dump($book);
}

还可以根据关联数据来查询当前模型数据,例如:

public function read(){    // 查询有写过书的作者列表
    $user = UserModel::has('books')->select();    // 查询写过三本书以上的作者
    $user = UserModel::has('books', '>=', 3)->select();    // 查询写过ThinkPHP5快速入门的作者
    $user = UserModel::hasWhere('books', ['title' => 'ThinkPHP5快速入门'])->select();
}

关联更新

下面来进行关联数据的更新

public function update($id){    
$user        = UserModel::get($id);    
$book        = $user->books()->getByTitle('ThinkPHP5开发手册');    
$book->title = 'ThinkPHP5快速入门';    $book->save();
}

或者使用查询构建器的update方法进行更新(但可能无法触发关联模型的事件)。

public function update($id){    
$user = UserModel::get($id);    
$user->books()->where('title', 'ThinkPHP5快速入门')->update(['title' => 'ThinkPHP5开发手册']);
}

关联删除

删除部分关联数据:

public function delete($id){    
    $user = UserModel::get($id);    // 删除部分关联数据
    $book = $user->books()->getByTitle('ThinkPHP5开发手册');    
    $book->delete();
}

删除所有的关联数据:

public function delete($id){    
$user = UserModel::get($id);   
if($user->delete()){        // 删除所有的关联数据
        $user->books()->delete();
    }
}

多对多关联

一个用户会有多个角色,同时一个角色也会包含多个用户,这就是一个典型的多对多关联,先创建一个角色表think_role结构如下:

DROP TABLE IF EXISTS `think_role`;CREATE TABLE IF NOT EXISTS `think_role` (  `id` int(5) UNSIGNED NOT NULL AUTO_INCREMENT,  `name` varchar(25) NOT NULL,  `title` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

多对多关联通常一定会有一个中间表,也称为枢纽表,所以需要创建一个用户角色的中间表,这里创建了一个think_access表,结构如下:

DROP TABLE IF EXISTS `think_access`;CREATE TABLE IF NOT EXISTS `think_access` (  `user_id` int(6) UNSIGNED NOT NULL,  `role_id` int(5) UNSIGNED NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;

关联定义

给User模型添加多对多关联方法定义

<?php
namespace app\index\model;
use think\Model;
class User extends Model{    // 开启自动写入时间戳
    protected $autoWriteTimestamp = true;    // 定义自动完成的属性
    protected $insert = ['status' => 1];    // 定义一对一关联
    public function profile()
    {        
    return $this->hasOne('Profile');
    }    // 定义一对多关联
    public function books()
    {        
    return $this->hasMany('Book');
    }    // 定义多对多关联
    public function roles()
    {        // 用户 BELONGS_TO_MANY 角色
        return $this->belongsToMany('Role', 'think_access');
    }
}

belongsToMany的参数如下:

belongsToMany('关联模型名','中间表名称','关联外键','关联模型主键','别名定义')

Role模型定义如下:

<?php
namespace app\index\model;
use think\Model;
class Role extends Model{    
public function user()
    {        // 角色 BELONGS_TO_MANY 用户
        return $this->belongsToMany('User', 'think_access');
    }

}

对于枢纽表并不需要创建模型类,在多对多关联关系中,并不需要直接操作枢纽表。

关联新增

给某个用户增加编辑角色,并且由于这个角色还没创建过,所以可以使用下面的方式:

// 关联新增数据
public function add(){    
$user = UserModel::getByNickname('张三');    // 新增用户角色 并自动写入枢纽表
    $user->roles()->save(['name' => 'editor', 'title' => '编辑']);    
    return '用户角色新增成功';
}

也可以批量新增用户的角色如下:

// 关联新增数据
public function add(){    
$user = UserModel::getByNickname('张三');    // 给当前用户新增多个用户角色
    $user->roles()->saveAll([
        ['name' => 'leader', 'title' => '领导'],
        ['name' => 'admin', 'title' => '管理员'],
    ]);    
    return '用户角色新增成功';
}

现在给另外一个用户增加编辑角色,由于该角色已经存在了,所以只需要使用attach方法增加枢纽表的关联数据:

// 关联新增数据
public function add(){    
$user = UserModel::getByNickname('张三');    
$role = Role::getByName('editor');    // 添加枢纽表数据
    $user->roles()->attach($role);    
    return '用户角色添加成功';
}

或者直接使用角色Id添加关联数据

// 关联新增数据
public function add(){    
$user = UserModel::getByNickname('张三');    
$user->roles()->attach(1);    
return '用户角色添加成功';
}

关联删除

如果需要解除用户的管理角色,可以使用detach方法删除关联的枢纽表数据,但不会删除关联模型数据,例如:

// 关联删除数据
public function delete(){    
$user = UserModel::get(2);    
$role = Role::getByName('admin');    // 删除关联数据 但不删除关联模型数据
    $user->roles()->detach($role);    
    return '用户角色删除成功';
}

如果有必要,也可以删除枢纽表的同时删除关联模型,下面的例子会解除用户的编辑角色并且同时删除编辑这个角色身份:

// 关联删除数据public function delete(){    
$user = UserModel::getByNickname('张三');    
$role = Role::getByName('editor');    // 删除关联数据 并同时删除关联模型数据
    $user->roles()->detach($role,true);    
    return '用户角色删除成功';
}

关联查询

获取用户张三的所有角色的话,直接使用:

// 关联查询
public function read(){    
$user = UserModel::getByNickname('张三');
    dump($user->roles);
}

同样支持对多对多关联使用预载入查询:

// 关联查询
public function read(){    // 预载入查询
    $user = UserModel::get(2,'roles');
    dump($user->roles);
}

到目前为止,我们已经掌握了关联的基础用法,更多的关联使用请关注后续相关的专题。



回复列表



回复操作

正在加载验证码......

请先拖动验证码到相应位置