## 新增單條數據: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,查看一下現在表中數據:

>[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 控制臺查看:

* * * * *
四、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.實例演示