## 新增单条数据:save( )方法
#### 1. 功能:向指定数据表中添加一条记录
>[info] 该方法每次仅能向表中添加一条新记录,添加多条可重复执行,不过后面要学习的saveAll()可一次性添加多条
****
#### 2. 源码:/thinkphp/library/think/Model.php
>[info] save方法是Model类中较复杂的方法之一,因为save方法身兼多职,不仅用于新增,还可以用于更新数据,这里我将用于新增部分作了简单注释
~~~
/**
* 保存当前数据对象,并写入表中
* @access public
* @param array $data 数据
* @param array $where 更新条件
* @param string $sequence 自增序列名
* @return integer|false
*/
public function save($data = [], $where = [], $sequence = null)
{
if (!empty($data)) {
// 数据自动验证
if (!$this->validateData($data)) {
return false;
}
// 数据对象赋值,其实就是给$data[]初始化
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
if (!empty($where)) { //第二个参数$where条件数组不为空
$this->isUpdate = true; //才允许更新,防止全部更新产生误操作
} //即没有设置条件就拒绝更新
}
// 检测字段
if (!empty($this->field)) { //如果字段数组不为空
foreach ($this->data as $key => $val) {//遍历数据对象
if (!in_array($key, $this->field)) { //数据对象中与表字段不对应的元素
unset($this->data[$key]); //应该销毁
}
}
}
// 数据自动完成
$this->autoCompleteData($this->auto);
// 自动写入更新时间
if ($this->autoWriteTimestamp && $this->updateTime) {
$this->setAttr($this->updateTime, null);
}
// 事件回调
if (false === $this->trigger('before_write', $this)) {
return false;
}
$pk = $this->getPk();
if ($this->isUpdate) {
// 自动更新
$this->autoCompleteData($this->update);
// 事件回调
if (false === $this->trigger('before_update', $this)) {
return false;
}
// 去除没有更新的字段
$data = [];
foreach ($this->data as $key => $val) {
if (in_array($key, $this->change) || $this->isPk($key)) {
$data[$key] = $val;
}
}
if (!empty($this->readonly)) {
// 只读字段不允许更新
foreach ($this->readonly as $key => $field) {
if (isset($data[$field])) {
unset($data[$field]);
}
}
}
if (empty($where) && !empty($this->updateWhere)) {
$where = $this->updateWhere;
}
if (is_string($pk) && isset($data[$pk])) {
if (!isset($where[$pk])) {
unset($where);
$where[$pk] = $data[$pk];
}
unset($data[$pk]);
}
$result = $this->db()->where($where)->update($data);
// 清空change
$this->change = [];
// 更新回调
$this->trigger('after_update', $this);
} else {
// 自动写入
$this->autoCompleteData($this->insert);
// 自动写入创建时间
if ($this->autoWriteTimestamp && $this->createTime) {
$this->setAttr($this->createTime, null);
}
if (false === $this->trigger('before_insert', $this)) {
return false;
}
//数据库更新底层仍是调用Db::insert()完成
//db()获取数据库Query对象,这样才能调用数据库Query方法:insert(数据对象$data)
$result = $this->db()->insert($this->data);
// 获取自动增长主键
if ($result && is_string($pk) && !isset($this->data[$pk])) {
$insertId = $this->db()->getLastInsID($sequence);
if ($insertId) {
$this->data[$pk] = $insertId;
}
}
// 标记为更新
$this->isUpdate = true;
// 清空change
$this->change = [];
// 新增回调
$this->trigger('after_insert', $this);
}
// 写入回调
$this->trigger('after_write', $this);
return $result;
}
~~~
>[info] 源码简要解析:新增忽略条件[ isUpsata(false) ],只需要用参数$data初始化模型的$data属性,创建数据对象,就可以自动将数据添加到数据表中。
源码中有二条关键语句,负责写表操作:
1. `$result = $this->db()->insert($this->data);`:将数据插入到表中;
2. `$result = $this->db()->where($where)->update($data);`:根据条件更新表中数据;
>[warning] 新增与更新操作都是:save方法,那么如何区分新增与更新呢?
#### 答案就是:看有没有设置where条件,如果无就是新增,有就是更新。
**由此可见:**最终仍是由数据库查询方法完成相关操作。
* * * * *
#### 3. 参数与返回值
* 参数:这里有三个参数,但与新增有用的只有一个
| 序号 | 参数 | 说明 |
| --- | --- | --- |
| 1 | $data / Array数组 | 用来初始化模型对象的$data属性 |
* 返回值:返回受影响记录数量,成功返回1。
* * * * *
#### 4. 基本语法:
* 格式1:将数据直接写在save方法参数中
~~~
模型对象 -> save(数组);
~~~
* 格式2:先生成数据对象,然后用save方法直接写入表中
~~~
模型对象 -> data(数组) -> save();
~~~
>[info] 这二种语法都可以完成新增操作。但是第二种语法结构更加清晰,可读性好,修改更加方便。第一种更加的简洁紧凑,但可读性不如第二个,想用哪个由你决定吧~~
* * * * *
#### 5. 实例演示
* 先创建一个模型类与表绑定:Staff.php
~~~
<?php
namespace app\index\model;
//导入模型类
use think\model;
class Staff extends model {
//自定义模型类代码
}
~~~
#### **任务:** 新增一条记录到tp5_staff表中
>[info] 根据数据对象创建方式和save参数有无,我们用三种方法来完成这个任务
一、用data方法创建数据对象,来完成新增
* 控制器:Index.php
~~~
<?php
namespace app\index\controller;
//导入自定义模型类
use app\index\model\Staff;
class Index {
public function index(){
//1.创建数据:与表中字段对应
$data = [];
$data['name'] = '周星星';
$data['sex'] = 1;
$data['age'] = 39;
$data['salary'] = 3500;
$data['dept'] = 3;
$data['hiredate'] = date('Y-m-d',time());
//2.创建数据对象
$model = (new Staff) -> data($data);
//3.获取新增操作执行前:数据对象原始数据
$data_before = $model -> getData();
//4.查看新增操作执行前的数据对象:$model
echo '查看新增操作执行前的数据对象:<br />';
dump($data_before);
//5.将数据对象原始数据写入数据表中,返回影响记录数
$affected = $model -> allowField(true) -> save();
//6.获取新增操作执行后:数据对象原始数据
$data_after = $model -> getData();
//7.查看新增操作执行后的数据对象:$model
echo '查看新增操作执行后的数据对象:<br />';
dump($data_after);
//8. 获取新增记录的主键id,等价于: $model -> id
$insert_ID = $data_after['id'];
//6.验证是否新增成功
echo $affected ? '新增成功!新记录主键id是:'.$insert_ID : '新增失败!';
}
}
~~~
>[info] 代码分析:
1. 其实上面源码,部分语句是可以合并的,分开写是为了可读性更好;
2. `$model = (new Staff) -> data($data);` 中,` (new Staff)` 生成模型对象,`data($data);`给模型对象$data属性初始化,完成数据对象`$model`的创建。
3. `$data_before = $model -> getData();`,此时$model已经是经过初始化了的数据对象,它的$data属性是有值的,该语句可直接获取到这个值($data数组);
4. `$data_after = $model -> getData();`,这时获取的原始数据与操作执行前是不一样的,系统自动添加了自增主键id字段,注意区别。
4. `$affected = $model -> allowField(true) -> save();`:将数据写入表中,并返回影响条数,成功返回 1,
5. `allowField(true)`:过滤非数据表字段数据,仅允许与表字段对应的数组值写入。
6. `echo $affected ? '新增成功!新记录主键id是:'.$insert_ID : '新增失败!'; `: `$affected` 作为判断条件,如果新增成功,`$affected`为 1,失败为 0 ,根据该值返回相应的提示信息,反馈给客户。
* 让我们看一下执行结果:
~~~
查看新增操作执行前的数据对象:
array(6) {
["name"] => string(9) "周星星"
["sex"] => int(1)
["age"] => int(39)
["salary"] => int(3500)
["dept"] => int(3)
["hiredate"] => string(10) "2016-11-25"
}
查看新增操作执行后的数据对象:
array(7) {
["name"] => string(9) "周星星"
["sex"] => int(1)
["age"] => int(39)
["salary"] => int(3500)
["dept"] => int(3)
["hiredate"] => string(10) "2016-11-25"
["id"] => string(4) "1033"
}
新增成功!新记录主键id是:1033
~~~
* 对应的SQL语句:
~~~
INSERT INTO `tp5_staff` (`name` , `sex` , `age` , `salary` , `dept` , `hiredate`) VALUES ('周星星' , 1 , 39 , 3500 , 3 , '2016-11-25')
~~~
* 再返回到SQLPRO for MySQL,查看一下现在表中数据:
![](https://box.kancloud.cn/17e7a6382112653cfafbd9d91787a576_822x775.png)
>[warning] 是不是觉得上面的代码太冗长啦,做为程序员的我们,怎么能够容忍?说实话,能学习到ThinkPHP框架开发,都不算是PHP小白了。下面跟我一道,把上面的代码进行精简~~
* * * * *
二、给sava方法传参方式,完成新增操作
* 控制器:Index.php
>[info] 有了上例的基础,看懂下面的代码,是没有难度的
~~~
<?php
namespace app\index\controller;
//导入自定义模型类
use app\index\model\Staff;
class Index {
public function index(){
//1.创建数据:与表中字段对应
$data = [];
$data['name'] = '紫霞仙子';
$data['sex'] = 0;
$data['age'] = 19;
$data['salary'] = 7800;
$data['dept'] = 2;
$data['hiredate'] = date('Y-m-d',time());
//2.创建模型对象
$model = new Staff();
//3.将数据参数传入save方法,将原始数据写入表中,并返回影响记录数
$affected = $model -> allowField(true) -> save($data);
//3.验证是否新增成功
echo $affected ? '新增成功!新记录主键id是:'.$model->getData('id') : '新增失败!';
}
}
~~~
* 源码分析:这里我们是把数据直接传给了save方法,其实把数据参数传给Staff类构造器也可以完成操作。为什么呢?
1. Staff类构造方法,就是给对象的$data属性初始化,来创建数据对象的;
2. save方法,其实底层实现就是把数据对象中的$data原始数据写入到数据表中。
>[warning] 所以说,把数组赋给类,还是save方法,本质上是完全相同 。
#### 不好意思,这里面有点点绕,把前面课程多看几遍吧!
* 现在我们再简单修改一下控制器:Index.php
>[info] 用Staff类的对象构造器创建数据对象,只需实例化传入数据参数即可
~~~
<?php
namespace app\index\controller;
//导入自定义模型类
use app\index\model\Staff;
class Index {
public function index(){
//1.创建数据:与表中字段对应
$data = [];
$data['name'] = '紫霞仙子';
$data['sex'] = 0;
$data['age'] = 19;
$data['salary'] = 7800;
$data['dept'] = 2;
$data['hiredate'] = date('Y-m-d',time());
//2.创建模型对象
$model = new Staff($data);
//3.将数据对象原始数据写入数据表中,返回影响记录数
$affected = $model -> allowField(true) -> save();
//3.验证是否新增成功
echo $affected ? '新增成功!新记录主键id是:'.$model->getData('id') : '新增失败!';
}
}
~~~
>[info] 其实这段代码与上面的代码就一点点区别:
1. 第一段代码中,是这样的:$data是传入save方法的
~~~
/2.创建模型对象
$model = new Staff();
//3.将数据参数传入save方法,将原始数据写入表中,并返回影响记录数
$affected = $model -> allowField(true) -> save($data);
~~~
2. 第二段代码中,仅是将参数从传给save,改为传给Staff类
~~~
//2.创建模型对象
$model = new Staff($data);
//3.将数据对象原始数据写入数据表中,返回影响记录数
$affected = $model -> allowField(true) -> save();
~~~
* 当然,运行结果是完全一致的,这里就不再贴出运行结果啦!
* * * * *
三、用魔术方法创建数据对象,完成新增数据操作
>[info] 先复习一下Model类的魔术方法:__set( )
~~~
/**
* 修改器 设置数据对象的值
* @access public
* @param string $name 名称
* @param mixed $value 值
* @return void
*/
public function __set($name, $value) //魔术方法来动态设置数据对象$data[]
{
$this->setAttr($name, $value); //键值对方式写入
}
~~~
**分析**:__set( )可完成模型对象的原始数据$data属性赋值
* 控制器:Index.php
~~~
<?php
namespace app\index\controller;
//导入自定义模型类
use app\index\model\Staff;
class Index {
public function index(){
//1.创建模型对象
$model = new Staff();
//2.创建数据:与表中字段对应
$model -> name = '朱老师';
$model -> sex = 1;
$model -> age = 31;
$model -> salary = 4600;
$model -> dept = 1;
$model -> hiredate = date('Y-m-d',time());
//3.将数据对象原始数据写入数据表中,返回影响记录数
$affected = $model -> allowField(true) -> save();
//3.验证是否新增成功
echo $affected ? '新增成功!新记录主键id是:'.($model -> id) : '新增失败!';
}
}
~~~
>[warning] $model -> id:是调用Model类中的魔术方法__get( )方法实现的
* 返回结果:
~~~
新增成功!新记录主键id是:1038
~~~
* 生成的SQL语句:
~~~
INSERT INTO `tp5_staff` (`name` , `sex` , `age` , `salary` , `dept` , `hiredate`) VALUES ('朱老师' , 1 , 31 , 4600 , 1 , '2016-11-25')
~~~
* SQLPRO for MySQL 控制台查看:
![](https://box.kancloud.cn/97838a4af6474dc68281302f31bb0d04_585x406.png)
* * * * *
四、save方法多次调用的问题:
>[danger] 特别提示:
1. 用save方法连续新增数据时,第二次调用时,数据对象中存在有第一次调用时创建的自增主键的(如:id),该id值会被认为是更新条件,而将新增误判为更新操作。
2. 所以,同一脚本中,第2次调用save方法新增时,一定要在save方法前添加isUpdata(false)方法,来规避这个问题。
* 我们还是用实例来演示:
* 控制器:Index.php
~~~
<?php
namespace app\index\controller;
//导入自定义模型类
use app\index\model\Staff;
class Index {
public function index(){
//1.创建模型对象
$model = new Staff();
//2.创建数据:与表中字段对应
$model -> name = '王老师';
$model -> sex = 1;
$model -> age = 20;
$model -> salary = 5000;
$model -> dept = 3;
$model -> hiredate = date('Y-m-d',time());
//3.将数据对象原始数据写入数据表中,返回影响记录数
$affected = $model -> allowField(true) -> save();
//4.再创建一个要添加到表中的数据
$model -> name = '张老师';
$model -> sex = 1;
$model -> age = 50;
$model -> salary = 4600;
$model -> dept = 2;
$model -> hiredate = date('Y-m-d',time());
//5.将该数据添加到表中
$affected = $model -> allowField(true) -> save();
//3.验证是否新增成功
echo $affected ? '新增成功!新记录主键id是:'.($model -> id) : '新增失败!';
}
}
~~~
>[info] 上面的代码看上去很简单:添加二条数据到表中,可实际上只会添加name='张老师'的记录,而第一条数据并未没有添加到表中,这是为什么呢?
原因是:第一次添加完name=’王老师‘的记录后,数据对象中自增主键id有了值,而这个值将会被视为更新操作的条件使用,所以第二次执行save方法时,就变成了更新操作,直接根据上一次执行返回的新增id,将name=’王老师‘的记录,更新成了name='王老师'的信息。
* 我们再次修改一下控制器代码:Index.php
将代码中第2次调用save方法部分修改一下:
~~~
······
//4.再创建一个要添加到表中的数据
$model -> name = '张老师';
$model -> sex = 1;
$model -> age = 50;
$model -> salary = 4600;
$model -> dept = 2;
$model -> hiredate = date('Y-m-d',time());
//5.将该数据添加到表中
$affected = $model
-> allowField(true) //过滤非表中字段
-> setAttr('id',null) //将数据对像主键置空
-> isUpdate(false) //忽略主键
-> save(); //执行新增操作
······
~~~
>[info] -> setAttr('id',null) //将数据对像主键置空
-> isUpdate(false) //忽略主键
这是一步双保险操作,有时:isUpdate(false)无效, 这是是ThinkPHP5的小bug
* * * * *
#### 6. 总结
>[danger] 我们通过save方法的学习,又全面复习了之前的知识,下节课要学习的saveAll方法就方便多了。
- 前言[随时更新]
- 开发环境
- 1.Mac环境
- 2.windows环境
- 模型对象
- 1.创建模型对象
- 2.模型初始化
- 数据对象
- 1.定义数据对象
- 2.创建数据对象
- 1.data方法
- 2.setAttr方法
- 3.__set方法
- 4.查询数据对象
- 1.getData方法
- 2.getAttr方法
- 3.__get方法
- OOP难点总结
- 1.get_class( )实例讲解
- 2.get_called_class( )实例讲解
- 3.__call( )实例讲解
- 3.__callStatic( )实例讲解
- 4.call_user_func_array函数[重点]
- 5.普通方法与静态方法
- 6.在Model源码中的应用
- 7.new static 延迟静态绑定
- PHP标准化规范
- 查询数据
- 1.获取单条:get静态方法
- 2.获取单条:对象查询
- 3.获取多条:all静态方法
- 4.获取多条:对象查询
- 5.获取字段值:value方法
- 6.获取列值:column方法
- 7.动态查询:getBy字段名
- 8.助手函数:model查询
- 9.加载器:Loader类查询
- 10.数据库与模型查询对比
- 新增数据
- 1.sava方法
- 2.savaAll方法
- 3.create静态方法
- 4.insert静态调用
- 更新数据
- 1.单条更新:save方法
- 2.批量更新:saveAll方法
- 3.静态更新:update方法
- 4.查询类Query直接更新
- 5. 闭包更新
- 删除数据
- 1.删除当前记录:delete
- 2.静态条件删除:destory
- 获取器
- 1.模型方法:set属性Attr
- 修改器
- 1.set属性Attr
- 时间戳
- 1.MySQL中日期类型复习
- 2.时间戳功能详解
- 软删除[重点]
- 1.traits详解[选学内容]
- 2.SoftDelet类源码分析
- 3. delete实例删除
- 4.destroy条件删除
- 5.restore恢复数据
- 类型转换
- 1. 规则设置
- 2. 实例演示
- 查询范围
- 1. 基本概念
- 2.实例演示