thinkphp5 的model层

thinkphp3.2与thinkPHP5.0的区别
一、thinkphp3.2与thinkPHP5.0不同之处
5.0版本和之前版本的差异较大,本篇对熟悉3.2版本的用户给出了一些5.0的主要区别。
模块和控制器
控制器的命名空间有所调整,并且可以无需继承任何的控制器类。
应用命名空间统一为app(可定义)而不是模块名;
控制器的类名默认不带Controller后缀;
控制器操作方法采用return方式返回数据 而非直接输出;
废除原来的操作前后置方法;
支持任意层次的控制器定义和访问;
URL访问支持自动定位控制器;
数据库
5.0的数据库查询功能增强,原先需要通过模型才能使用的链式查询可以直接通过Db类调用,原来的M函数调用可以改用db函数,例如:
3.2版本
M(‘User’)->where([‘name’=>’thinkphp’])->find();
5.0版本
db(‘User’)->where(‘name’,’thinkphp’)->find();
主要改进如下:
支持链式查询操作;
数据查询支持返回对象、数组和PDOStatement对象;
数据集查询支持返回数组和Collection对象;
增加查询构造器,查询语法改变;
支持闭包查询;
支持分块查询;
支持视图查询;
增加SQL监听事件;

模型

5.0的模型变化是最大的,基本上模型是完全面向对象的概念,包括关联模型,模型类的后缀不再带Model,直接由命名空间区分,原来的D函数调用改为model函数,并且必须创建对应的模型类,例如:

3.2版本
D('User')->where(['name'=>'thinkphp'])->find();
5.0版本
model('User')->where('name','thinkphp')->find();
主要改进包括:
重构关联模型;
支持聚合模型;
废除视图模型(改为数据库的视图查询方法);
增加获取器和修改器;
增加时间戳自动写入;
增加类型字段转换;
数组访问支持;
JSON序列化支持;

5.0的数据自动完成和3.2版本区别较大,自动完成则通过在模型里面定义修改器来完成。

二、模型

模型定义

模型就是一个实体类,里面包含一些属性和方法。

定义一个User模型类:
namespace app\index\model;

use think\Model; //定义时继承副类Modex class Users extends Model { //默认表名绑定类名,类名大写,表前缀在数据库配置文件中配置 模型名 约定对应数据表(假设数据库的前缀定义是 ecs_) Users=>ecs_users //注意:以下情况使用较少 //设置数据表(不含前缀) protected $name = “admin”; //设置完整数据表(含前缀) protected $table = ‘ecs_admin’; //设置主键(默认主键自动识别) protected $pk = ‘uid’; }
//使用实例
例如在Index模块的index控制器下面使用
引入命名空间才能使用
app/Index/Model/Users; public function test(){ //查询users表中id=1的数据(先查询表结构,再根据主键查询) $a=Users::get(1); } //插入操作

模型调用
模型类可以使用静态调用或者实例化调用两种方式,例如:

// 静态调用
$user = User::get(1);
$user->name = ‘thinkphp’;
$user->save();

// 实例化模型
$user = new User;
$user->name= ‘thinkphp’;
$user->save();
// 使用 Loader 类实例化(单例)
$user = Loader::model(‘User’);
// 或者使用助手函数model
$user = model(‘User’);
$user->name= ‘thinkphp’;
$user->save();
实例化模型类主要用于调用模型的自定义方法

##模型初始化
模型同样支持初始化,与控制器的初始化不同的是,模型的初始化是重写Model的initialize,具体如下
namespace app\index\model;
use think\Model;
class Index extends Model
{
//自定义初始化
protected function initialize()
{
//需要调用Modelinitialize方法
parent::initialize();
//TODO:自定义的初始化
}
}
模型的增删改查

1.添加数据

//添加数据前会先执行这个语句,查询是否有这个字段
show columns From “ecs_users”;
第一种是实例化模型对象后赋值并保存:

$user = new User;
$user->name= ‘thinkphp’;
$user->email= ‘thinkphp@qq.com’;
$user->save();

也可以使用data方法批量赋值:
$user = new User;
$user->data([
‘name’ => ‘thinkphp’,
‘email’ => ‘thinkphp@qq.com
]);
$user->save();
save方法说明:该方法会根据你给出的类名和属性名组装成insert语句并执行。
第二种方法插入:
$userArr[‘email’]=”hello@qq.com”;
$userArr[‘mobile’]=’13662992253’;
User::create($userArr);//和save方法不同的是,create方法返回的是当前模型的对象实例

批量插入:
$users = new Users;
$list=[
[‘email’=>’zhangshan.com’,’mobile’=>’212121’],
[‘email’=>’zhashan.com’,’mobile’=>’212221’],
];
//注意:这是一条条记录插入,效率不高
$users->saveAll($list);

如果需要过滤非数据表字段的数据,可以使用:

$user = new User($_POST);
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save();
如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:

$user = new User($_POST);
// post数组中只有name和email字段会写入
$user->allowField([‘name’,’email’])->save();
save方法新增数据返回的是写入的记录数。
获取自增ID
如果要获取新增数据的自增ID,可以使用下面的方式:

$user = new User;
$user->name = ‘thinkphp’;
$user->email = ‘thinkphp@qq.com’;
$user->save();
// 获取自增ID
echo $user->id;
注意这里其实是获取模型的主键,如果你的主键不是id,而是user_id的话,其实获取自增ID就变成这样:
$user = new User;
$user->name = ‘thinkphp’;
$user->email = ‘thinkphp@qq.com’;
$user->save();
// 获取自增ID
echo $user->user_id;
注意不要在同一个实例里面多次新增数据,如果确实需要多次新增,那么可以用下面的方式:

$user = new User;
$user->name = ‘thinkphp’;
$user->email = ‘thinkphp@qq.com’;
$user->save();
$user->name = ‘onethink’;
$user->email = ‘onethink@qq.com’;
// 第二次开始必须使用下面的方式新增
$user->isUpdate(false)->save();
助手函数
系统提供了model助手函数用于快速实例化模型,并且使用单例实现,例如:

// 使用model助手函数实例化User模型
$user = model(‘User’);
// 模型对象赋值
$user->data([
‘name’ => ‘thinkphp’,
‘email’ => ‘thinkphp@qq.com
]);
$user->save();

2.查找并更新

在取出数据后,更改字段内容后更新数据。

$user = User::get(1);
$user->name = ‘thinkphp’;
$user->email = ‘thinkphp@qq.com’;
$user->save();
直接更新数据
也可以直接带更新条件来更新数据

$user = new User;
// save方法第二个参数为更新条件
$user->save([
‘name’ => ‘thinkphp’,
‘email’ => ‘thinkphp@qq.com
],[‘id’ => 1]);

过滤非数据表字段的数据,可以使用:
$user = new User();
// 过滤post数组中的非数据表字段数据,更新id=1的表的记录
$user->allowField(true)->save($_POST,[‘id’ => 1]);
指定某些字段写入,可以使用:
$user = new User();
// post数组中只有name和email字段会写入
$user->allowField([‘name’,’email’])->save($_POST, [‘id’ => 1]);
批量更新数据
可以使用saveAll方法批量更新数据,例如:
批量更新仅能根据主键值进行更新

$user = new User;
$list = [
[‘id’=>1, ‘name’=>’thinkphp’, ‘email’=>'thinkphp@qq.com‘],
[‘id’=>2, ‘name’=>’onethink’, ‘email’=>'onethink@qq.com‘]
];
$user->saveAll($list);
$user = new User;
$user->where(‘id’, 1)
->update([‘name’ => ‘thinkphp’]);
或者使用:
$user = new User;
$user->update([‘id’ => 1, ‘name’ => ‘thinkphp’]);
如果传入update的数据包含主键的话,可以无需使用where方法。
静态方法
模型支持静态方法直接更新数据,例如:

User::where(‘id’, 1)
->update([‘name’ => ‘thinkphp’]);
或者使用:

User::update([‘id’ => 1, ‘name’ => ‘thinkphp’]);
闭包更新
可以通过闭包函数使用更复杂的更新条件,例如:

$user = new User;
$user->save([‘name’ => ‘thinkphp’],function($query){
// 更新status值为1 并且id大于10的数据
$query->where(‘status’, 1)->where(‘id’, ‘>’, 10);
});

显式更新数据:
// 实例化模型
$user = new User;
// 显式指定更新数据操作
$user->isUpdate(true)
->save([‘id’ => 1, ‘name’ => ‘thinkphp’]);
显式新增数据:
$user = User::get(1);
$user->name = ‘thinkphp’;
// 显式指定当前操作为新增操作,isUpdate(false)表示当前为insert操作
$user->isUpdate(false)->save();
注意不要在一个模型实例里面做多次更新,会导致部分重复数据不再更新,正确的方式应该是先查询后更新或者使用模型类的update方法更新。

3.删除当前模型

删除模型数据,可以在实例化后调用delete方法。
//根据主键删除主键为1的数据
$user = User::get(1);
$user->delete();
根据主键删除
或者直接调用静态方法

User::destroy(1);
// 支持批量删除多个数据
User::destroy(‘1,2,3’);
// 或者
User::destroy([1,2,3]);
V5.0.9+版本开始当destroy方法传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的
条件删除
使用数组进行条件删除,例如:

// 删除状态为0的数据
User::destroy([‘status’ => 0]);
还支持使用闭包删除,例如:
//闭包删除可以加多种条件
User::destroy(function($query){
$query->where(‘id’,’>’,10);
});
或者通过数据库类的查询条件删除
User::where(‘id’,’>’,10)->delete();

4.获取单个数据

获取单个数据的方法包括:

取出主键为1的数据
$user = User::get(1);
echo $user->name;

根据某个条件查询数据getByXxxx()方法
//查询手机号为123456的数据,根据某个字段查询
$user=User::getByMobile(‘123456’);
echo $user[‘mobile’];

// 使用数组查询
$user = User::get([‘name’ => ‘111222’,’mobile’=>’123456’]);
//使用where查询
$user = User::where([‘name’ => ‘111222’,’mobile’=>’123456’])->find();
// 使用闭包查询
$user = User::get(function($query){
$query->where(‘name’, ‘thinkphp’);
});
echo $user->name;
如果你是在模型内部,请不要使用$this->name的方式来获取数据,请使用$this->getAttr(‘name’) 替代。
或者在实例化模型后调用查询方法

$user = new User();

取出多个数据:

// 根据主键获取多个数据,获取主键值为1,2,3的数据
$list = User::all(‘1,2,3’);
// 或者使用数组
$list = User::all([1,2,3]);
// 使用数组查询,数组方式只能定义查询条件
$list = User::all([‘status’=>1]);
// 使用闭包查询,闭包方式可以支持更多的连贯操作,包括排序、数量限制等。
$list = User::all(function($query){
$query->where(‘status’, 1)->limit(3)->order(‘id’, ‘asc’);
});
foreach($list as $key=>$user){
echo $user->name;
}
在实例化模型后调用查询方法

$user = new User();
// 查询数据集
$user->where(‘name’, ‘thinkphp’)
->limit(10)
->order(‘id’, ‘desc’)
->select();
模型的all方法或者select方法返回的是一个包含模型对象的二维数组或者数据集对象。
获取某个字段或者某个列的值
// 获取某个用户的积分
User::where(‘id’,10)->value(‘score’);
// 获取某个列的所有值
User::where(‘status’,1)->column(‘name’);
// 以id为索引
User::where(‘status’,1)->column(‘name’,’id’);
查询缓存
get方法和all方法的第三个参数表示是否使用查询缓存,或者设置缓存标识。
$user = User::get(1,’’,true);
$list = User::all(‘1,2,3’,’’,true);//true表示开启查询缓存。

5.聚合

在模型中也可以调用数据库的聚合方法进行查询,例如:

方法|说明
count|统计数量,参数是要统计的字段名(可选)
max|获取最大值,参数是要统计的字段名(必须)
min|获取最小值,参数是要统计的字段名(必须)
avg|获取平均值,参数是要统计的字段名(必须)
sum|获取总分,参数是要统计的字段名(必须)
静态调用:
//统计数量

$user = new User;
User::count();
User::where(‘status’,’>’,0)->count();
//统计status=1的score的平均值
User::where(‘status’,1)->avg(‘score’);
//统计score的最大值
User::max(‘score’);

获取器(读取器)

读取器方法的命名规范:get+属性名的驼峰命名+Attr
程序在读取属性时自动根据规则监测是否有这个属性对应的方法
$user=Users::get(1);
echo $user->email;//自动监测是否有getEmailAttr方法,如果有写则调用并把原来的值传给该方法,并由该方法返回相应的数据,没有找到则调用默认数据。
获取器的作用是在[获取数据]的字段值后自动进行处理,例如,我们需要对状态值进行转换,可以使用:

class User extends Model
{
public function getStatusAttr($value)
{
$status = [-1=>’删除’,0=>’禁用’,1=>’正常’,2=>’待审核’];
return $status[$value];
}
}
$user = User::get(1);
echo $user->status; // 例如输出“正常”
获取器还可以定义数据表中不存在的字段,例如:

class User extends Model
{
//获取器方法的第二个参数传入的是当前的所有数据数组。
public function getStatusTextAttr($value,$data)
{
$status = [-1=>’删除’,0=>’禁用’,1=>’正常’,2=>’待审核’];
return $status[$data[‘status’]];
}
}
我们就可以直接使用status_text字段的值了,例如:
$user = User::get(1);
echo $user->status_text; // 例如输出“正常”,获取器只有当获取某个数据属性的时候自动触发

获取原始数据
如果你定义了获取器的情况下,希望获取数据表中的原始数据,可以使用:

$user = User::get(1);
// 获取原始字段数据
echo $user->getData(‘status’);
// 获取全部原始数据
dump($user->getData());

修改器(写入器)

命名规范:set+属性名的驼峰命名+Attr
修改器的作用是可以在数据赋值的时候自动进行转换处理,例如:

class User extends Model
{
public function setNameAttr($value)
{
return strtolower($value);//把字符转换成小写并返回
}
}
如下代码实际保存到数据库中的时候会转为小写

$user = new User();
$user->name = ‘THINKPHP’;//插入或者更新数据的时候自动监测是否有修改器,如果有则调用相应的修改器,并把返回的结果插入数据库,例如:插入省市区的id的时候可以通过修改器来获取id对应的地名
$user->save();
echo $user->name; // thinkphp
也可以进行序列化字段的组装:

class User extends Model
{
public function setNameAttr($value,$data)
{
return serialize($data);
}
}
修改器方法的第二个参数会自动传入当前的所有数据数组。
批量修改
除了赋值的方式可以触发修改器外,还可以直接使用save方法触发,例如:

$user = new User();
$data[‘name’] = ‘THINKPHP’;
$data[‘email’] = ‘thinkphp@qq.com’;
$user->save($data);//保存数据的同时监测是否有修改器
echo $user->name; // thinkphp

时间戳

autoWriteTimestamp属性支持设置为时间日期类名
系统支持自动写入创建和更新的时间戳字段,有两种方式配置支持。

第一种方式,是在数据库配置文件中添加全局设置:

// 开启自动写入时间戳字段
‘auto_timestamp’ => true,
// 关闭全局自动写入时间字段
‘auto_timestamp’ => false,

写入数据的时候,系统会自动写入create_time和update_time字段,而不需要定义修改器,例如:
$user = new User();
$user->name = ‘THINKPHP’;
$user->save();
echo $user->create_time; // 输出类似 2016-10-12 14:20:10
echo $user->update_time; // 输出类似 2016-10-12 14:20:10

如果你的数据表字段不是默认值的话(create_time,update_time),可以按照下面的方式定义:

class User extends Model
{
// 定义时间戳字段名
protected $createTime = ‘create_at’;//【自定义的数据库字段】
protected $updateTime = ‘update_at’;//【自定义的数据库字段】
}
下面是修改字段后的输出代码:

$user = new User();
$user->name = ‘THINKPHP’;
$user->save();
echo $user->create_at; // 输出类似 2016-10-12 14:20:10
echo $user->update_at; // 输出类似 2016-10-12 14:20:10
如果你只需要使用create_time字段而不需要自动写入update_time,则可以单独设置关闭某个字段,例如:
class User extends Model
{
// 关闭自动写入update_time字段
protected $updateTime = false;
}
如果不需要任何自动写入的时间戳字段的话,可以关闭时间戳自动写入功能,关闭当前model设置如下:
class User extends Model
{
// 关闭自动写入时间戳
protected $autoWriteTimestamp = false;
}

只读字段

只读字段用来保护某些特殊的字段值不被更改,这个字段的值一旦写入,就无法更改。 要使用只读字段的功能,我们只需要在模型中定义readonly属性:
namespace app\index\model;

use think\Model;

class User extends Model
{
//定义了当前模型的name和email字段为只读字段,不允许被更改。也就是说当执行更新方法之前会自动过滤掉只读字段的值,避免更新到数据库。
protected $readonly = [‘name’,’email’];
}

下面举个例子说明下:

$user = User::get(5);
// 更改某些字段的值
$user->name = ‘TOPThink’;
$user->email = ‘Topthink@gmail.com’;
$user->address = ‘上海静安区’;
// 保存更改后的用户数据
$user->save();
事实上,由于我们对name和email字段设置了只读,因此只有address字段的值被更新了,而name和email的值仍然还是更新之前的值。

软删除

在实际项目中,对数据频繁使用删除操作会导致性能问题,软删除的作用就是把数据加上删除标记,而不是真正的删除,同时也便于需要的时候进行数据的恢复。

要使用软删除功能,需要引入SoftDelete trait,例如User模型按照下面的定义就可以使用软删除功能:
namespace app\index\model;

use think\Model;
use traits\model\SoftDelete;//必须引入

class User extends Model
{
use SoftDelete;
//5.0.2版本之前deleteTime属性必须使用static定义。
protected $deleteTime = ‘delete_time’;//【需要软删除的数据库字段为detele_time】
}
// 软删除
1.方式1
User::destroy(1);
2.方式2
User::get(1);//软删除后获取不到数据,为空
$user->delete();
默认情况下查询的数据不包含软删除数据,如果需要包含软删除的数据,可以使用下面的方式查询:
User::withTrashed()->find();
$res=User::withTrashed()->select();
echo $res->getData();
如果仅仅需要查询软删除的数据,可以使用:
User::onlyTrashed()->find();
User::onlyTrashed()->select();
// 真实删除
1.方式1
User::destroy(1,true);
2.方式2
$user = User::get(1);
$user->delete(true);

类型转换

支持给字段设置类型自动转换,会在写入和读取的时候自动进行类型转换处理,例如:

class User extends Model
{
protected $type = [
‘status’ => ‘integer’,
‘score’ => ‘float’,
‘birthday’ => ‘datetime’,
‘info’ => ‘array’,
];
}
下面是一个类型自动转换的示例:

$user = new User;
$user->status = ‘1’;//指定为int类型
$user->score = ‘90.50’;//指定为float类型
$user->birthday = ‘2015/5/1’;//指定为日期类型
$user->info = [‘a’=>1,’b’=>2];//指定为数组类型
$user->save();
var_dump($user->status); // int 1
var_dump($user->score); // float 90.5;
var_dump($user->birthday); // string ‘2015-05-01 00:00:00’
var_dump($user->info);// array (size=2) ‘a’ => int 1 ‘b’ => int 2
数据库查询默认取出来的数据都是字符串类型,如果需要转换为其他的类型,需要设置,支持的类型包括如下类型:

integer

设置为integer(整型)后,该字段写入和输出的时候都会自动转换为整型。

float

该字段的值写入和输出的时候自动转换为浮点型。

boolean

该字段的值写入和输出的时候自动转换为布尔型。

array

如果设置为强制转换为array类型,系统会自动把数组编码为json格式字符串写入数据库,取出来的时候会自动解码。
object

该字段的值在写入的时候会自动编码为json字符串,输出的时候会自动转换为stdclass对象。
serialize

指定为序列化类型的话,数据会自动序列化写入,并且在读取的时候自动反序列化。

json

指定为json类型的话,数据会自动json_encode写入,并且在读取的时候自动json_decode处理。
timestamp

指定为时间戳字段类型的话,该字段的值在写入时候会自动使用strtotime生成对应的时间戳,输出的时候会自动转换为dateFormat属性定义的时间字符串格式,默认的格式为Y-m-d H:i:s,如果希望改变其他格式,可以定义如下:
class User extends Model
{
protected $dateFormat = ‘Y/m/d’;
protected $type = [
‘status’ => ‘integer’,
‘score’ => ‘float’,
‘birthday’ => ‘timestamp’,
];
}
或者在类型转换定义的时候使用:

class User extends Model
{
//定义各个字段的类型
protected $type = [
‘status’ => ‘integer’,
‘score’ => ‘float’,
‘birthday’ => ‘timestamp:Y/m/d’,
];
}
然后就可以

$user = User::find(1);
echo $user->birthday; // 2015/5/1
datetime

和timestamp类似,区别在于写入和读取数据的时候都会自动处理成时间字符串Y-m-d H:i:s的格式。

数据完成

关系区分:修改器和数据完成功能一样,修改器在数据赋值的时候对数据进行处理,数据完成支持auto、insert和update三个属性,可以分别在写入、新增和更新的时候进行处理,数据完成和修改器只能使用一个,否则将可能导致插入数据库的数据发生错乱,例如出现密码被两次加密的情况。
数据自动完成指在不需要手动赋值的情况下对字段的值进行处理后写入数据库。

系统支持auto、insert和update三个属性,可以分别在写入、新增和更新的时候进行字段的自动完成机制,auto属性自动完成包含新增和更新操作,例如我们定义User模型类如下:
namespace app\index\model;

use think\Model;

class User extends Model
{
protected $auto = [];
protected $insert = [‘ip’,’status’ => 1];
protected $update = [‘login_ip’];

protected function setIpAttr()
{
return request()->ip();
}
}
在新增数据的时候,会对ip和 status 字段自动完成或者处理。
$user = new User;
$user->name = ‘ThinkPHP’;
$user->save();
echo $user->name; // thinkphp
echo $user->status; // 1
在保存操作的时候,会自动完成ip字段的赋值。
$user = User::find(1);
$user->name = ‘THINKPHP’;
$user->save();
echo $user->name; // thinkphp
echo $user->ip; // 127.0.0.1

模型分层

ThinkPHP支持模型的分层 ,除了Model层之外,我们可以项目的需要设计和创建其他的模型层。

通常情况下,不同的分层模型仍然是继承系统的\think\Model类或其子类,所以,其基本操作和Model类的操作是一致的。
例如在index模块的设计中需要区分数据层、逻辑层、服务层等不同的模型层,我们可以在模块目录下面创建model、logic和service目录,把对用户表的所有模型操作分成三层:
数据层:app\index\model\User 用于定义数据相关的自动验证和自动完成和数据存取接口
逻辑层:app\index\logic\User 用于定义用户相关的业务逻辑
服务层:app\index\service\User 用于定义用户相关的服务接口等

JSON序列化

1.可以调用模型的toJson方法进行JSON序列化
$user = User::get(1);
设置无需输出的字段
echo $user->hidden([‘create_time’,’update_time’])->toJson();
或者追加其它的字段:
$user = User::get(1);
echo $user->append([‘status_text’])->toJson();
设置允许输出的属性:
$user = User::get(1);
echo $user->visible([‘id’,’name’,’email’])->toJson();
2.模型对象可以直接被JSON序列化==直接echo 一个模型对象,例如:
echo json_encode(User::get(1));===echo User::get(1);
输出结果类似于:
{“id”:”1”,”name”:””,”title”:””,”status”:”1”,”update_time”:”1430409600”,”score”:”90.5”}

关联

一般来说,关联关系包括:
一对一关联(一个用户一个档案):HAS_ONE以及相对的BELONGS_TO
一对多关联(一个用户多条评论):HAS_MAMY以及相对的BELONGS_TO
多对多关联(多个同学对应多个老师,一个同学听多个老师的课,一个老师教多个同学):BELONGS_TO_MANY
使用流程示例:
1.创建Users模型
2.创建Comment模型
3.在Users模型中添加方法HAS_ONE、HAS_MAMY
4.在Comment模型中添加方法BELONGS_TO
5.测试

一对一关联

一对一:一个用户一个档案;一夫一妻制等
定义
关联方法的命名规范是驼峰法,而关联属性则一般是小写+下划线的方式,系统在获取的时候会自动转换对应,读取user_profile关联属性则对应的关联方法应该是userProfile。
定义一对一关联,例如,一个用户(users)都有一个个人资料(profile),我们定义User模型如下:

hasOne方法的参数包括:
hasOne(‘关联模型名’,‘外键名’,‘主键名’,[‘模型别名定义’],‘join类型’);
默认的join类型为INNER(内联接)。其他(Outer join:外部联接;cross join:交叉联接)
关联模型定义需要查询的字段,例如:
namespace app\index\model;

use think\Model;

class User extends Model
{
public function profile()
{//关联模型Profile,默认情况下, 我们使用的是user_id 作为外键关联
return $this->hasOne(‘Profile’)->field(‘id,name,email’);
}
}
关联查找
定义好关联之后,就可以使用下面的方法获取关联数据:
$user = User::get(1);
// 输出Profile关联模型的email属性
echo $user->profile->email;//把profile模型(表)中的email数据输出
默认情况下, 我们使用的是user_id 作为外键关联,如果不是的话则需要在关联定义的时候指定,例如:
namespace app\index\model;

use think\Model;

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

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

namespace app\index\model;

use think\Model;

class User extends Model
{
public function profile()
{
//用别名函数设置当前模型名称为member
return $this->hasOne(‘Profile’,’uid’)->setAlias([‘user’=>’member’]);
}
}
关联新增
$user = User::get(1);
// 如果还没有关联数据 则进行新增
系统会自动把当前模型的主键user_id传入profile模型,然后在ProFile模型中找到主键uid=user_id的数据,然后对该数据记录进行更新
$user->profile()->save([‘email’ => ‘thinkphp’]);//数组写法

使用save方法进行更新关联数据。
$user = User::get(1);
$user->profile->email = ‘thinkphp’;//直接赋值的写法
$user->profile->save();

定义相对的关联
我们可以在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::get(1);
// 输出User关联模型的属性
echo $profile->user->account;

可以在定义关联的时候使用bind方法绑定属性到父模型,例如:

namespace app\index\model;

use think\Model;

class User extends Model
{
public function profile()
{//把user和profile里面的相同字段nickname,email相互关联
return $this->hasOne(‘Profile’,’uid’)->bind(‘nickname,email’);
}
}
或者使用数组的方式指定绑定属性别名

namespace app\index\model;

use think\Model;

class User extends Model
{
public function profile()
{
return $this->hasOne(‘Profile’,’uid’)->bind([
‘email’,
‘truename’=> ‘nickname’,//user表中的truename和profile表中的nickname关联
‘profile_id’ => ‘id’,//user表中的profile_id和profile表中的id关联
]);
}
}
然后使用关联预载入查询的时候,可以使用

$user = User::get(1,’profile’);
// 输出Profile关联模型的email属性
echo $user->email;
echo $user->profile_id;
绑定关联属性不影响原有关联属性的读取,绑定关联模型的属性支持读取器。

我们可以使用together方法更方便的进行关联自动写入操作。
把blog和content进行关联
写入
$blog = new Blog;
$blog->name = ‘thinkphp’;//更改当前模型的name的值
$blog->title = ‘ThinkPHP5关联实例’;
$content = new Content;
$content->data = ‘实例内容’;//更改关联模型的data值
$blog->content = $content;//当前模型blog和content模型进行关联
//// 更新当前模型及关联模型
$blog->together(‘content’)->save();

// 查询
$blog = Blog::get(1);
$blog->title = ‘更改标题’;//更改当前模型的title
$blog->content->data = ‘更新内容’;//更改关联模型content的data
// 更新当前模型及关联模型
$blog->together(‘content’)->save();
删除

// 查询主键为1的记录
$blog = Blog::get(1);
// 删除当前及关联模型
$blog->together(‘content’)->delete();

一对多关联

一对多:一个用户多条评论;一篇文章可以有多个评论;
一对多关联的情况也比较常见,使用hasMany方法定义,
参数包括:
hasMany(‘关联模型名’,‘外键名’,‘主键名’,[‘模型别名定义’]);
例如一篇文章可以有多个评论

namespace app\index\model;

use think\Model;

class Article extends Model
{
public function comments()
{//Article和Comment表进行关联,外键是comments表的主键comments_id,主键名是article表的article_id
return $this->hasMany(‘Comment’,’comments_id’,’article_id’)->field(‘id,author,content’);
}
}

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

// 获取id=1的文章的所有评论
$article=Article::get(1);
dump($article->comments);
foreach($article->comments as)
// 也可以进行条件搜索
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为1的数据记录
//设置comment中的字段content内容为test
$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’);
}
}

多对多关联

多对多:多个同学对应多个老师;多个用户多个角色(一个用户有多个角色,一个角色对应多个用户)
belongsToMany方法的参数如下:
belongsToMany(‘关联模型名’,‘中间表名’,‘外键名’,‘当前模型关联键名’,[‘模型别名定义’]);
tpshop举例:地区表region;配送区域表shipping_area;他们通过area_region配送区域和地区关联表关联(也叫中间表,枢纽表),一个城市对应多个地区,一个地区对应多个城市,广州是珠三角,中国一线城市,珠三角地区包括深圳,广州
该中间表只有两个字段

创建地区模型
namespace app\index\model;
use think\Model;
class Region extends Model
{
public function shippingArea()
{//关联ShippingArea模型,中间表是tp_area_region,通过这shipping_area_id,region_id两个字段关联
return $this->belongsToMany(‘ShippingArea’,’area_region’,’shipping_area_id’,’region_id’);
}
}
创建配送区域的模型
namespace app\index\model;
use think\Model;
class ShippingArea extends Model
{
public function region()
{//关联Region模型,中间表是tp_area_region,通过这shipping_area_id,region_id两个字段关联,字段顺序不影响
return $this->belongsToMany(‘ShippingArea’,’area_region’,’shipping_area_id’,’region_id’);
}
}
//不需要配置中间模型
关联定义
例如,我们的用户和角色就是一种多对多的关系,我们在User模型定义如下:
namespace app\index\model;
use think\Model;
class User extends Model
{
public function roles()
{
return $this->belongsToMany(‘Role’);
}
}

关联查询
我们可以通过下面的方式获取关联数据
//查出北京市的地区记录
$region=Region::getByName(‘北京市’);
//新增配送区域,并自动写入枢纽表
$region->shippingArea()->save([‘shipping_area_name’]=>’中国首都’)

// 获取用户的所有角色

关联新增

// 批量增加关联数据
$region->shippingArea()->saveAll([
[‘shipping_area_name’=>’北京’],
[‘shipping_area_name’=>’广州’],
]);
只新增中间表数据,可以使用

// 仅增加关联的中间表数据
$region->shippingArea()->save(1);

// 批量增加关联数据
$region->shippingArea()->saveAll([1,2,3]);
单独更新中间表数据,可以使用:

// 增加关联的中间表数据
$region->shippingArea()->attach(1);
// 传入中间表的额外属性
$region->shippingArea()->attach(1,[‘remark’=>’test’]);
// 删除中间表数据
$region->shippingArea()->detach([1,2,3]);