public function save($data = [], $where = [], $sequence = null) {
if (!empty($data)) {
// 数据自动验证
if (!$this->validateData($data)) {
return false;
}
// 数据对象赋值
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
if (!empty($where)) {
$this->isUpdate = true;
}
}
// 自动关联写入
if (!empty($this->relationWrite)) {
$relation = [];
foreach ($this->relationWrite as $key => $name) {
if (is_array($name)) {
if (key($name) === 0) {
$relation[$key] = [];
foreach ($name as $val) {
if (isset($this->data[$val])) {
$relation[$key][$val] = $this->data[$val];
unset($this->data[$val]);
}
}
} else {
$relation[$key] = $name;
}
} elseif (isset($this->relation[$name])) {
$relation[$name] = $this->relation[$name];
} elseif (isset($this->data[$name])) {
$relation[$name] = $this->data[$name];
unset($this->data[$name]);
}
}
}
// 数据自动完成
$this->autoCompleteData($this->auto);
// 事件回调
if (false === $this->trigger('before_write', $this)) {
return false;
}
$pk = $this->getPk();
if ($this->isUpdate) {
// 检测字段
$this->checkAllowField($this->data, array_merge($this->auto, $this->update));
// 自动更新
$this->autoCompleteData($this->update);
// 获取有更新的数据
$data = $this->getChangedData();
// 事件回调
if (false === $this->trigger('before_update', $this)) {
return false;
}
if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) {
// 关联更新
if (isset($relation)) {
$this->autoRelationUpdate($relation);
}
return 0;
} elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
// 自动写入更新时间
$data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
}
if (empty($where) && !empty($this->updateWhere)) {
$where = $this->updateWhere;
}
// 保留主键数据
foreach ($this->data as $key => $val) {
if ($this->isPk($key)) {
$data[$key] = $val;
}
}
if (is_string($pk) && isset($data[$pk])) {
if (!isset($where[$pk])) {
unset($where);
$where[$pk] = $data[$pk];
}
unset($data[$pk]);
}
// 模型更新
$result = $this->getQuery()->where($where)->update($data);
// 关联更新
if (isset($relation)) {
$this->autoRelationUpdate($relation);
}
// 更新回调
$this->trigger('after_update', $this);
} else {
// 检测字段
$this->checkAllowField($this->data, array_merge($this->auto, $this->insert));
// 自动写入
$this->autoCompleteData($this->insert);
// 自动写入创建时间和更新时间
if ($this->autoWriteTimestamp) {
if ($this->createTime && !isset($this->data[$this->createTime])) {
$this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime);
}
if ($this->updateTime && !isset($this->data[$this->updateTime])) {
$this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
}
}
if (false === $this->trigger('before_insert', $this)) {
return false;
}
$result = $this->getQuery()->insert($this->data);
// 获取自动增长主键
if ($result && is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) {
$insertId = $this->getQuery()->getLastInsID($sequence);
if ($insertId) {
$this->data[$pk] = $insertId;
}
}
// 关联写入
if (isset($relation)) {
foreach ($relation as $name => $val) {
$method = Loader::parseName($name, 1, false);
$this->$method()->save($val);
}
}
// 标记为更新
$this->isUpdate = true;
// 新增回调
$this->trigger('after_insert', $this);
}
// 写入回调
$this->trigger('after_write', $this);
// 重新记录原始数据
$this->origin = $this->data;
return $result;
}
传入data不为空 执行顺序:验证 赋值 关联数据 自动补全 更新或保存数据
传入data为空 执行顺序:关联数据 自动补全 更新或保存数据
即 先验证后补全数据 ,若有需要根据表单其他数据做验证并且设置值的,应该是先验证后赋值。
比如判断传入文件的扩展名,并根据判断补全数据库一个字段,应先验证,再model赋值,两边部分代码会重复。
自动补全仅在save里被调用,所以不能验证自动补全的数据。
protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) {
// 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
if (is_string($rules)) {
$rules = explode('|', $rules);
}
$i = 0;
foreach ($rules as $key => $rule) {
if ($rule instanceof \Closure) {
$result = call_user_func_array($rule, [$value, $data]);
$info = is_numeric($key) ? '' : $key;
} else {
// 判断验证类型
if (is_numeric($key)) {
if (strpos($rule, ':')) {
list($type, $rule) = explode(':', $rule, 2);
if (isset($this->alias[$type])) {
// 判断别名
$type = $this->alias[$type];
}
$info = $type;
} elseif (method_exists($this, $rule)) {
$type = $rule;
$info = $rule;
$rule = '';
} else {
$type = 'is';
$info = $rule;
}
} else {
$info = $type = $key;
}
// 如果不是require 有数据才会行验证
if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) {
// 验证类型
$callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type];
// 验证数据
$result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]);
} else {
$result = true;
}
}
if (false === $result) {
// 验证失败 返回错误信息
if (isset($msg[$i])) {
$message = $msg[$i];
if (is_string($message) && strpos($message, '{%') === 0) {
$message = Lang::get(substr($message, 2, -1));
}
} else {
$message = $this->getRuleMsg($field, $title, $info, $rule);
}
return $message;
} elseif (true !== $result) {
// 返回自定义错误信息
if (is_string($result) && false !== strpos($result, ':')) {
$result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result);
}
return $result;
}
$i++;
}
return $result;
}
这个checkItem是验证数据,被同一个类里具有public访问权限的check方法调用。
没有设置require规则的字段,验证其他规则的情况为非空不验证。
call_user_func_array($callback, [$value, $rule, $data, $field, $title]); 这句调用对应验证方法。
对于自定义的验证规则来说应该可以写成这样:'type' => 'require|checkType',而不是这样:'type' => 'require|checkType:', 即自定义方法后面可以不必用“:”结尾,还没测试过。
check方法中,对于其错误信息直接赋值给$this->error,model也是直接赋值给$this->error,并且model和validate都有一个getError方法,也都是直接返回$this->error。
先自动补全再验证……
public function dataSave($data = null) {
if (empty($data)) {
$data = $this->param;
}
$result = false;
if ($data !== false) {
$data['content'] = htmlspecialchars_decode($data['content']);
$data['content'] = preg_replace('/"/', '', $data['content']);
//手动数据补全
foreach ($this->insert as $key => $value) {
if (is_numeric($key)) {
if (!isset($data[$value])) {
$data[$value] = '';
}
} else {
if (!isset($data[$key])) {
$data[$key] = $value;
}
}
}
$this->autoCompleteData($data);
$data = $this->getData();
//手动数据补全结束
if (isset($data['id']) && empty($data['id'])) {
//新增数据
unset($data['id']);
$id = $this->validate('OnlineTraining')->save($data);
if ($id) {
$data['id'] = $id;
$result = $id;
}
} else {
//更新数据
$status = $this->validate('OnlineTraining')->save($data, array('id' => $data['id']));
if (true === $status) {
$result = $data['id'];
}
}
}
return $result;
}
感觉modle应该加个可以用户调用的数据补全的方法。
上面的例子有个bug……create_time不自动填充,因为手动设置的值不为null。源码里判断创建时间为null才会自动填充。