<?php<liu21st@gmail.com>
namespace think;
use think\db\Query;
abstract class Model implements \JsonSerializable, \ArrayAccess
{
use model\concern\Attribute;
use model\concern\RelationShip;
use model\concern\ModelEvent;
use model\concern\TimeStamp;
use model\concern\Conversion;
private $isUpdate = false;
private $force = false;
private $updateWhere;
protected $connection = [];
protected $query;
protected $name;
protected $table;
protected $auto = [];
protected $insert = [];
protected $update = [];
protected static $initialized = [];
protected static $readMaster;
protected $queryInstance;
protected $error;
protected $defaultSoftDelete;
public function __construct($data = [])
{
if (is_object($data)) {
$this->data = get_object_vars($data);
} else {
$this->data = $data;
}
if ($this->disuse) {
foreach ((array) $this->disuse as $key) {
if (array_key_exists($key, $this->data)) {
unset($this->data[$key]);
}
}
}
$this->origin = $this->data;
$config = Container::get('config');
if (empty($this->name)) {
$name = str_replace('\\', '/', static::class);
$this->name = basename($name);
if ($config->get('class_suffix')) {
$suffix = basename(dirname($name));
$this->name = substr($this->name, 0, -strlen($suffix));
}
}
if (is_null($this->autoWriteTimestamp)) {
$this->autoWriteTimestamp = $config->get('database.auto_timestamp');
}
if (is_null($this->dateFormat)) {
$this->dateFormat = $config->get('database.datetime_format');
}
if (is_null($this->resultSetType)) {
$this->resultSetType = $config->get('database.resultset_type');
}
if (is_null($this->query)) {
$this->query = $config->get('database.query');
}
if (!empty($this->connection) && is_array($this->connection)) {
$this->connection = array_merge($config->pull('database'), $this->connection);
}
if ($this->observerClass) {
static::observe($this->observerClass);
}
$this->initialize();
}
public function getName()
{
return $this->name;
}
public function readMaster($all = false)
{
$model = $all ? '*' : static::class;
static::$readMaster[$model] = true;
return $this;
}
public function newInstance($data = [], $isUpdate = false, $where = null)
{
return (new static($data))->isUpdate($isUpdate, $where);
}
protected function buildQuery()
{
$class = $this->query;
$query = (new $class())->connect($this->connection)
->model($this)
->json($this->json)
->setJsonFieldType($this->jsonType);
if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) {
$query->master(true);
}
if (!empty($this->table)) {
$query->table($this->table);
} else {
$query->name($this->name);
}
if (!empty($this->pk)) {
$query->pk($this->pk);
}
return $query;
}
public function setQuery($query)
{
$this->queryInstance = $query;
return $this;
}
public function db($useBaseQuery = true)
{
if ($this->queryInstance) {
return $this->queryInstance;
}
$query = $this->buildQuery();
if ($useBaseQuery) {
if (method_exists($this, 'withNoTrashed')) {
$this->withNoTrashed($query);
}
if (method_exists($this, 'base')) {
call_user_func_array([$this, 'base'], [ & $query]);
}
}
return $query;
}
protected function initialize()
{
if (!isset(static::$initialized[static::class])) {
static::$initialized[static::class] = true;
static::init();
}
}
protected static function init()
{}
protected function autoCompleteData($auto = [])
{
foreach ($auto as $field => $value) {
if (is_integer($field)) {
$field = $value;
$value = null;
}
if (!isset($this->data[$field])) {
$default = null;
} else {
$default = $this->data[$field];
}
$this->setAttr($field, !is_null($value) ? $value : $default);
}
}
public function force($force = true)
{
$this->force = $force;
return $this;
}
public function save($data = [], $where = [], $sequence = null)
{
if (is_string($data)) {
$sequence = $data;
$data = [];
}
if (!$this->checkBeforeSave($data, $where)) {
return false;
}
$result = $this->isUpdate ? $this->updateData($where) : $this->insertData($sequence);
if (false === $result) {
return false;
}
$this->trigger('after_write');
$this->origin = $this->data;
return $result;
}
protected function checkBeforeSave($data, $where)
{
if (!empty($data)) {
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
if (!empty($where)) {
$this->isUpdate = true;
$this->updateWhere = $where;
}
}
$this->autoCompleteData($this->auto);
if (false === $this->trigger('before_write')) {
return false;
}
return true;
}
protected function checkAllowFields(array $append = [])
{
if (empty($this->field) || true === $this->field) {
$query = $this->db(false);
$table = $this->table ?: $query->getTable();
$this->field = $query->getConnection()->getTableFields($table);
$field = $this->field;
} else {
$field = array_merge($this->field, $append);
if ($this->autoWriteTimestamp) {
array_push($field, $this->createTime, $this->updateTime);
}
}
if ($this->disuse) {
$field = array_diff($field, (array) $this->disuse);
}
return $field;
}
protected function updateData($where)
{
$this->autoCompleteData($this->update);
if (false === $this->trigger('before_update')) {
return false;
}
$data = $this->getChangedData();
if (empty($data)) {
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}
return 0;
} elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
$data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
$this->data[$this->updateTime] = $data[$this->updateTime];
}
if (empty($where) && !empty($this->updateWhere)) {
$where = $this->updateWhere;
}
$allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update));
foreach ($this->data as $key => $val) {
if ($this->isPk($key)) {
$data[$key] = $val;
}
}
$pk = $this->getPk();
$array = [];
foreach ((array) $pk as $key) {
if (isset($data[$key])) {
$array[] = [$key, '=', $data[$key]];
unset($data[$key]);
}
}
if (!empty($array)) {
$where = $array;
}
foreach ((array) $this->relationWrite as $name => $val) {
if (is_array($val)) {
foreach ($val as $key) {
if (isset($data[$key])) {
unset($data[$key]);
}
}
}
}
$result = $this->db(false)->where($where)->strict(false)->field($allowFields)->update($data);
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}
$this->trigger('after_update');
return $result;
}
protected function insertData($sequence)
{
$this->autoCompleteData($this->insert);
$this->checkTimeStampWrite();
if (false === $this->trigger('before_insert')) {
return false;
}
$allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert));
$result = $this->db(false)->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence);
if ($result && $insertId = $this->db(false)->getLastInsID($sequence)) {
$pk = $this->getPk();
foreach ((array) $pk as $key) {
if (!isset($this->data[$key]) || '' == $this->data[$key]) {
$this->data[$key] = $insertId;
}
}
}
if (!empty($this->relationWrite)) {
$this->autoRelationInsert();
}
$this->isUpdate = true;
$this->trigger('after_insert');
return $result;
}
public function setInc($field, $step = 1, $lazyTime = 0)
{
$where = $this->getWhere();
if (false === $this->trigger('before_update')) {
return false;
}
$result = $this->db(false)->where($where)->setInc($field, $step, $lazyTime);
if (true !== $result) {
$this->data[$field] += $step;
}
$this->trigger('after_update');
return $result;
}
public function setDec($field, $step = 1, $lazyTime = 0)
{
$where = $this->getWhere();
if (false === $this->trigger('before_update')) {
return false;
}
$result = $this->db(false)->where($where)->setDec($field, $step, $lazyTime);
if (true !== $result) {
$this->data[$field] -= $step;
}
$this->trigger('after_update');
return $result;
}
protected function getWhere()
{
$pk = $this->getPk();
if (is_string($pk) && isset($this->data[$pk])) {
$where[] = [$pk, '=', $this->data[$pk]];
} elseif (!empty($this->updateWhere)) {
$where = $this->updateWhere;
} else {
$where = null;
}
return $where;
}
public function saveAll($dataSet, $replace = true)
{
$result = [];
$db = $this->db(false);
$db->startTrans();
try {
$pk = $this->getPk();
if (is_string($pk) && $replace) {
$auto = true;
}
foreach ($dataSet as $key => $data) {
if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) {
$result[$key] = self::update($data, [], $this->field);
} else {
$result[$key] = self::create($data, $this->field);
}
}
$db->commit();
return $this->toCollection($result);
} catch (\Exception $e) {
$db->rollback();
throw $e;
}
}
public function isUpdate($update = true, $where = null)
{
if (is_bool($update)) {
$this->isUpdate = $update;
if (!empty($where)) {
$this->updateWhere = $where;
}
} else {
$this->isUpdate = true;
$this->updateWhere = $update;
}
return $this;
}
public function delete()
{
if (false === $this->trigger('before_delete')) {
return false;
}
$where = $this->getWhere();
$result = $this->db(false)->where($where)->delete();
if (!empty($this->relationWrite)) {
$this->autoRelationDelete();
}
$this->trigger('after_delete');
$this->data = [];
$this->origin = [];
return $result;
}
public function auto($fields)
{
$this->auto = $fields;
return $this;
}
public static function create($data = [], $field = null)
{
$model = new static();
if (!empty($field)) {
$model->allowField($field);
}
$model->isUpdate(false)->save($data, []);
return $model;
}
public static function update($data = [], $where = [], $field = null)
{
$model = new static();
if (!empty($field)) {
$model->allowField($field);
}
$model->isUpdate(true)->save($data, $where);
return $model;
}
public static function get($data, $with = [], $cache = false, $failException = false)
{
if (is_null($data)) {
return;
}
if (true === $with || is_int($with)) {
$cache = $with;
$with = [];
}
$query = static::parseQuery($data, $with, $cache);
return $query->failException($failException)->find($data);
}
public static function getOrFail($data, $with = [], $cache = false)
{
return self::get($data, $with, $cache, true);
}
public static function all($data = null, $with = [], $cache = false)
{
if (true === $with || is_int($with)) {
$cache = $with;
$with = [];
}
$query = static::parseQuery($data, $with, $cache);
return $query->select($data);
}
protected static function parseQuery(&$data, $with, $cache)
{
$result = self::with($with)->cache($cache);
if (is_array($data) && key($data) !== 0) {
$result = $result->where($data);
$data = null;
} elseif ($data instanceof \Closure) {
$data($result);
$data = null;
} elseif ($data instanceof Query) {
$result = $data->with($with)->cache($cache);
$data = null;
}
return $result;
}
public static function destroy($data)
{
if (empty($data) && 0 !== $data) {
return 0;
}
$model = new static();
$query = $model->db();
if (is_array($data) && key($data) !== 0) {
$query->where($data);
$data = null;
} elseif ($data instanceof \Closure) {
$data($query);
$data = null;
}
$resultSet = $query->select($data);
$count = 0;
if ($resultSet) {
foreach ($resultSet as $data) {
$result = $data->delete();
$count += $result;
}
}
return $count;
}
public function getError()
{
return $this->error;
}
public function __wakeup()
{
$this->initialize();
}
public function __debugInfo()
{
return [
'data' => $this->data,
'relation' => $this->relation,
];
}
public function __set($name, $value)
{
$this->setAttr($name, $value);
}
public function __get($name)
{
return $this->getAttr($name);
}
public function __isset($name)
{
if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) {
return true;
}
return false;
}
public function __unset($name)
{
unset($this->data[$name], $this->relation[$name]);
}
public function offsetSet($name, $value)
{
$this->setAttr($name, $value);
}
public function offsetExists($name)
{
return $this->__isset($name);
}
public function offsetUnset($name)
{
$this->__unset($name);
}
public function offsetGet($name)
{
return $this->getAttr($name);
}
public static function useGlobalScope($use)
{
$model = new static();
return $model->db($use);
}
public function __call($method, $args)
{
return call_user_func_array([$this->db(), $method], $args);
}
public static function __callStatic($method, $args)
{
$model = new static();
return call_user_func_array([$model->db(), $method], $args);
}
}