首页 » PHP » 正文

thinkphp5学习笔记(8)

(7)关联

ThinkPHP5.0 的关联采用了对象化的操作模式,你无需继承不同的模型类,只是把关联定义成一个方法,
并且直接通过当前模型对象的属性名获取定义的关联数据。
举个例子,有一个用户模型 User ,有一个关联的模型对象 Book ,每个用户有多本书, User 模型定义如
下:

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;

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();
}
}
}
重要:此处注意,博主反复尝试发现一只出错,提示update_time字段不存在,后来发现例子中的插入操作,同样会触发针对profile表的create_time和update_time操作,所以,要不在profile表建立这两个字段;要不在Profile模型中,关闭掉自动写入时间戳功能:protected $autoWriteTimestamp = false;

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

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

http://192.168.0.102/user/39
以上关联查询的时候,只有在获取关联对象( $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://192.168.0.102/user/update/39

关联删除
关联删除代码如下:

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

http://192.168.0.102/user/delete/39

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

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,
`create_time` int(11) UNSIGNED NOT NULL,
`update_time` int(11) UNSIGNED NOT NULL,
`status` tinyint(1) NOT NULL,
`user_id` int(6) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

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

hasMany 的参数如下:

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

Book 模型类定义如下:

如果需要定义对应的关联,则可以使用 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成功';
}

访问:http://192.168.0.102/index/user/addbook

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

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();
}
存在url问题,待解决。

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

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);
博主的文章或程序如果给您带来了价值,感谢您打赏一二
微信扫码支付 支付宝扫码支付

发表评论