diff --git a/library/think/Model.php b/library/think/Model.php index c5f38c2a..c071718f 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -15,6 +15,11 @@ use think\Cache; abstract class Model implements \JsonSerializable, \ArrayAccess { + const HAS_ONE = 1; + const HAS_MANY = 2; + const BELONGS_TO = 3; + const BELONGS_TO_MANY = 4; + // 当前实例 private static $instance; // 数据库对象池 @@ -46,8 +51,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $insert = []; // 更新的字段完成 protected $update = []; - // 关联 - protected $relation = []; + + // 当前执行的关联类型 + private $relation; /** * 架构函数 @@ -155,12 +161,27 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (method_exists($this, $method)) { return $this->$method($value, $this->data); } - if (is_null($value)) { - // 检测关联数据 - $method = 'getRelation' . Loader::parseName($name, 1); - if (method_exists($this, $method)) { - return $this->$method(); + if (is_null($value) && method_exists($this, $name)) { + // 执行关联定义方法 ( 关联定义方法始终返回Db对象) + $db = $this->$name(); + // 判断关联类型执行查询 + switch ($this->relation) { + case self::HAS_ONE: + $result = $db->find(); + break; + case self::HAS_MANY: + $result = $db->select(); + break; + case self::BELONGS_TO: + $result = $db->find(); + break; + case self::BELONGS_TO_MANY: + $result = $db->select(); + break; } + // 避免影响其它操作方法 + $this->relation = null; + return $result; } return $value; } @@ -205,12 +226,19 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 保存当前数据对象的值 无需任何参数(自动识别新增或者更新) + * 保存当前数据对象的值(自动识别新增或者更新) * @access public + * @param array $data 数据 + * @param array $where 更新条件 * @return void */ - public function save() + public function save($data = [], $where = []) { + if (!empty($data)) { + foreach ($data as $key => $value) { + $this->__set($key, $value); + } + } $data = $this->data; // 数据自动验证 @@ -226,9 +254,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $data[$name] = is_callable($rule) ? call_user_func_array($rule, [ & $data]) : $rule; } - if ($this->checkPkExists($data)) { + // 检测是否为更新数据 + if ($this->isUpdate($data)) { - if (false === self::trigger('before_update', $data)) { + if (false === $this->trigger('before_update', $this)) { return false; } // 更新的时候检测字段更改 @@ -245,21 +274,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $data[$name] = is_callable($rule) ? call_user_func_array($rule, [ & $data]) : $rule; } - $result = self::db()->update($data); - - // 关联更新 - if (!empty($this->relation)) { - foreach ($this->relation as $key => $val) { - if (isset($data[$key])) { - $this->relationUpdate($key, $data[$key], $val); - } - } + $db = self::db(); + if (!empty($where)) { + $db->where($where); } - self::trigger('after_update', $data); + $result = $db->update($data); + + $this->trigger('after_update', $this); return $result; } else { - if (false === self::trigger('before_insert', $data)) { + if (false === $this->trigger('before_insert', $this)) { return false; } @@ -277,15 +302,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->data[$this->pk] = $insertId; } - // 关联写入 - if (!empty($this->relation)) { - foreach ($this->relation as $key => $val) { - if (isset($data[$key])) { - $this->relationInsert($key, $data[$key], $val); - } - } - } - self::trigger('after_insert', $data); + $this->trigger('after_insert', $this); return $insertId ?: $result; } } @@ -299,21 +316,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { $data = $this->data; - if (false === self::trigger('before_delete', $data)) { + if (false === $this->trigger('before_delete', $this)) { return false; } $result = self::db()->delete($data); - // 关联删除 - if ($result) { - if (!empty($this->relation)) { - foreach ($this->relation as $key => $val) { - $this->relationDelete($key, $data, $val); - } - } - } - self::trigger('after_delete', $data); + $this->trigger('after_delete', $this); return $result; } @@ -422,12 +431,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 检查数据中是否存在主键值 + * 是否为更新操作 * @access public * @param mixed $data 数据 * @return bool */ - public function checkPkExists($data = []) + public function isUpdate($data = []) { $data = $data ?: $this->data; $pk = $this->pk; @@ -442,6 +451,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } } + // TODO 完善没有主键或者其他的情况 return false; } @@ -472,7 +482,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param string $event 事件名 * @param mixed $params 传入参数(引用) */ - protected static function trigger($event, &$params) + protected function trigger($event, &$params) { if (isset(self::$event[$event]) && is_callable(self::$event[$event])) { $result = call_user_func_array(self::$event[$event], [ & $params]); @@ -536,26 +546,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * 删除记录 * @access public * @param mixed $data 主键列表 - * @param bool $findFirst 是否查询 * @return integer */ - public static function destroy($data, $findFirst = false) + public static function destroy($data) { - // 保留删除的数据备用 - if ($findFirst) { - $resultSet = self::all($data); + $model = new static(); + $resultSet = $model->select($data); + if ($resultSet) { + foreach ($resultSet as $data) { + $result = $data->delete(); + } } - - if (false === self::trigger('before_delete', $findFirst ? $resultSet : $data)) { - return false; - } - - $result = self::db()->delete($data); - - // 关联删除 - - // 删除回调 - self::trigger('after_delete', $findFirst ? $resultSet : $data); return $result; } @@ -571,136 +572,44 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $model; } - // 指定关联 - public function relation($name, $relation = '') - { - if (is_array($name)) { - $this->relation = array_merge($this->relation, $name); - } else { - $this->relation[$name] = $relation; - } - return $this; - } - // HAS ONE public function hasOne($model, $foreignKey = '', $localKey = '') { - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->pk; - $foreignKey = $foreignKey ?: $this->name . '_id'; - return $model::where($foreignKey, $this->data[$localKey])->find(); + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->pk; + $foreignKey = $foreignKey ?: $this->name . '_id'; + $this->relation = self::HAS_ONE; + return $model::where($foreignKey, $this->data[$localKey]); } // BELONGS TO public function belongsTo($model, $localKey = '', $foreignKey = '') { - $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: $this->pk; - $localKey = $localKey ?: basename(str_replace('\\', '/', $model)) . '_id'; - return $model::where($foreignKey, $this->data[$localKey])->find(); + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->pk; + $localKey = $localKey ?: basename(str_replace('\\', '/', $model)) . '_id'; + $this->relation = self::BELONGS_TO; + return $model::where($foreignKey, $this->data[$localKey]); } // HAS MANY public function hasMany($model, $foreignKey = '', $localKey = '') { - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->pk; - $foreignKey = $foreignKey ?: $this->name . '_id'; - return $model::where($foreignKey, $this->data[$localKey])->select(); + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->pk; + $foreignKey = $foreignKey ?: $this->name . '_id'; + $this->relation = self::HAS_MANY; + return $model::where($foreignKey, $this->data[$localKey]); } // BELONGS TO MANY public function belongsToMany($model, $localKey = '', $foreignKey = '') { - $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: $this->pk; - $localKey = $localKey ?: basename(str_replace('\\', '/', $model)) . '_id'; - return $model::where($foreignKey, $this->data[$localKey])->select(); - } - - // 关联写入 - public function relationInsert($className, $data, $relation = []) - { - if (empty($relation) && isset($this->relation[$className])) { - $relation = $this->relation[$className]; - } - $type = isset($relation['relation_type']) ? $relation['relation_type'] : $relation; - $foreignKey = isset($relation['foreign_key']) ? $relation['foreign_key'] : strtolower($this->name) . '_id'; - $className = isset($relation['class_name']) ? $relation['class_name'] : $className; - - $model = $this->parseModel(ucfirst($className)); - switch ($type) { - case 'has_one': - $data[$foreignKey] = $this->data[$this->pk]; - $model::create($data); - break; - case 'belongs_to': - break; - case 'has_many': - foreach ($data as $key => &$val) { - $val[$foreignKey] = $this->data[$this->pk]; - } - $model::insertAll($data); - break; - } - return $this; - } - - // 关联更新 - public function relationUpdate($className, $data, $relation = []) - { - if (empty($relation) && isset($this->relation[$className])) { - $relation = $this->relation[$className]; - } - $type = isset($relation['relation_type']) ? $relation['relation_type'] : $relation; - $foreignKey = isset($relation['foreign_key']) ? $relation['foreign_key'] : strtolower($this->name) . '_id'; - $className = isset($relation['class_name']) ? $relation['class_name'] : $className; - - $model = $this->parseModel(ucfirst($className)); - switch ($type) { - case 'has_one': - $class = new $model; - if ($class->checkPkExists($data)) { - $class::update($data); - } else { - $class::where($foreignKey, $this->data[$this->pk])->update($data); - } - break; - case 'belongs_to': - break; - case 'has_many': - $class = new $model; - foreach ($data as $key => $val) { - $class::update($val); - } - break; - } - return $this; - } - - // 关联删除 - public function relationDelete($className, $data = '', $relation = []) - { - if (empty($relation) && isset($this->relation[$className])) { - $relation = $this->relation[$className]; - } - $type = isset($relation['relation_type']) ? $relation['relation_type'] : $relation; - $foreignKey = isset($relation['foreign_key']) ? $relation['foreign_key'] : strtolower($this->name) . '_id'; - $className = isset($relation['class_name']) ? $relation['class_name'] : $className; - - $model = $this->parseModel(ucfirst($className)); - $id = $data ? $data[$this->pk] : $this->data[$this->pk]; - switch ($type) { - case 'has_one': - $model::where($foreignKey, $id)->delete(); - break; - case 'belongs_to': - break; - case 'has_many': - $model::where($foreignKey, $id)->delete(); - break; - } - return $this; + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->pk; + $localKey = $localKey ?: basename(str_replace('\\', '/', $model)) . '_id'; + $this->relation = self::BELONGS_TO_MANY; + return $model::where($foreignKey, $this->data[$localKey]); } /** diff --git a/library/think/ORM.php b/library/think/ORM.php deleted file mode 100644 index e504a10d..00000000 --- a/library/think/ORM.php +++ /dev/null @@ -1,78 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class ORM -{ - protected static $instance = []; - protected static $config = []; - - /** - * 设置数据对象的值 - * @access public - * @param string $name 名称 - * @param mixed $value 值 - * @return void - */ - public function __set($name, $value) - { - self::__callStatic('__set', [$name, $value]); - } - - /** - * 获取数据对象的值 - * @access public - * @param string $name 名称 - * @return mixed - */ - public function __get($name) - { - return self::__callStatic('__get', [$name]); - } - - /** - * 检测数据对象的值 - * @access public - * @param string $name 名称 - * @return boolean - */ - public function __isset($name) - { - return self::__callStatic('__isset', [$name]); - } - - /** - * 销毁数据对象的值 - * @access public - * @param string $name 名称 - * @return void - */ - public function __unset($name) - { - self::__callStatic('__unset', [$name]); - } - - public function __call($method, $params) - { - return self::__callStatic($method, $params); - } - - public static function __callStatic($method, $params) - { - $name = basename(str_replace('\\', '/', get_called_class())); - if (!isset(self::$instance[$name])) { - // 自动实例化模型类 - self::$instance[$name] = new \think\Model($name, static::$config); - } - return call_user_func_array([self::$instance[$name], $method], $params); - } -} diff --git a/library/think/db/Driver.php b/library/think/db/Driver.php index 9eeea298..42e08fe0 100644 --- a/library/think/db/Driver.php +++ b/library/think/db/Driver.php @@ -963,9 +963,9 @@ abstract class Driver public function bind($key, $value = false, $type = PDO::PARAM_STR) { if (is_array($key)) { - $this->options['bind'] = $key; + $this->bind = array_merge($this->bind, $key); } else { - $this->options['bind'][$key] = [$value, $type]; + $this->bind[$key] = [$value, $type]; } return $this; } @@ -1224,16 +1224,16 @@ abstract class Driver if (isset($options['where']['AND'])) { foreach ($options['where']['AND'] as $key => $val) { $key = trim($key); - if (in_array($key, $fields, true) && is_scalar($val) && empty($options['bind'][$key])) { - $this->_parseType($options['where']['AND'], $key, $options['bind'], $options['table']); + if (in_array($key, $fields, true) && is_scalar($val) && empty($this->bind[$key])) { + $this->_parseType($options['where']['AND'], $key, $options['table']); } } } if (isset($options['where']['OR'])) { foreach ($options['where']['OR'] as $key => $val) { $key = trim($key); - if (in_array($key, $fields, true) && is_scalar($val) && empty($options['bind'][$key])) { - $this->_parseType($options['where']['OR'], $key, $options['bind'], $options['table']); + if (in_array($key, $fields, true) && is_scalar($val) && empty($this->bind[$key])) { + $this->_parseType($options['where']['OR'], $key, $options['table']); } } } @@ -1244,9 +1244,6 @@ abstract class Driver $options['table'] .= ' ' . $options['alias']; } - // 参数绑定 全局化 - $this->bind = !empty($options['bind']) ? $options['bind'] : []; - // 查询过后清空sql表达式组装 避免影响下次查询 $this->options = []; return $options; @@ -1257,13 +1254,12 @@ abstract class Driver * @access protected * @param array $data 数据 * @param string $key 字段名 - * @param array $bind 参数绑定列表 * @param string $tableName 表名 * @return void */ - protected function _parseType(&$data, $key, &$bind, $tableName = '') + protected function _parseType(&$data, $key, $tableName = '') { - if (':' == substr($data[$key], 0, 1) && isset($bind[substr($data[$key], 1)])) { + if (':' == substr($data[$key], 0, 1) && isset($this->bind[substr($data[$key], 1)])) { // 已经绑定 无需再次绑定 请确保bind方法优先执行 return; } @@ -1277,8 +1273,8 @@ abstract class Driver } elseif (false !== strpos($type[$key], 'bool')) { $data[$key] = (bool) $data[$key]; } - $bind[$key] = [$data[$key], isset($binds[$key]) ? $binds[$key] : \PDO::PARAM_STR]; - $data[$key] = ':' . $key; + $this->bind[$key] = [$data[$key], isset($binds[$key]) ? $binds[$key] : \PDO::PARAM_STR]; + $data[$key] = ':' . $key; } /** @@ -1345,7 +1341,7 @@ abstract class Driver $result[$item] = 'NULL'; } elseif (is_scalar($val)) { // 过滤非标量数据 - $this->_parseType($data, $key, $this->bind); + $this->_parseType($data, $key); $result[$item] = $data[$key]; } } @@ -1712,6 +1708,19 @@ abstract class Driver return sprintf(" FORCE INDEX ( %s ) ", $index); } + /** + * 获取参数绑定信息并清空 + * @access protected + * @param bool $reset 获取后清空 + * @return array + */ + protected function getBindParams() + { + $bind = $this->bind; + $this->bind = []; + return $bind; + } + /** * 插入记录 * @access public @@ -1730,7 +1739,7 @@ abstract class Driver // 兼容数字传入方式 $sql = ($replace ? 'REPLACE' : 'INSERT') . ' INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') VALUES (' . implode(',', $values) . ')'; $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : ''); - $result = $this->execute($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false); + $result = $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false); return $result; } @@ -1755,7 +1764,7 @@ abstract class Driver } $sql = 'INSERT INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') ' . implode(' UNION ALL ', $values); $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : ''); - return $this->execute($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false); + return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false); } /** @@ -1776,7 +1785,7 @@ abstract class Driver $fields = array_map([$this, 'parseKey'], $fields); $sql = 'INSERT INTO ' . $this->parseTable($table) . ' (' . implode(',', $fields) . ') '; $sql .= $this->buildSelectSql($options); - return $this->execute($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false); + return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false); } /** @@ -1833,7 +1842,7 @@ abstract class Driver . $this->parseLimit(!empty($options['limit']) ? $options['limit'] : ''); } $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : ''); - return $this->execute($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false); + return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false); } /** @@ -1871,7 +1880,7 @@ abstract class Driver . $this->parseLimit(!empty($options['limit']) ? $options['limit'] : ''); } $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : ''); - return $this->execute($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false); + return $this->execute($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false); } public function buildSql($sub = true) @@ -1897,7 +1906,7 @@ abstract class Driver $options = $this->_parseOptions(); $sql = $this->buildSelectSql($options); - $resultSet = $this->query($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false, isset($options['fetch_mode']) ? $options['fetch_mode'] : true); + $resultSet = $this->query($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false, isset($options['fetch_mode']) ? $options['fetch_mode'] : true); if (!empty($resultSet)) { if (is_string($resultSet)) { @@ -1970,7 +1979,7 @@ abstract class Driver $options = $this->_parseOptions(); $options['limit'] = 1; $sql = $this->buildSelectSql($options); - $result = $this->query($sql, isset($options['bind']) ? $options['bind'] : [], !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false, isset($options['fetch_mode']) ? $options['fetch_mode'] : true); + $result = $this->query($sql, $this->getBindParams(), !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false, isset($options['fetch_mode']) ? $options['fetch_mode'] : true); // 数据处理 if (!empty($result)) {