| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 | <?php// +----------------------------------------------------------------------// | ThinkPHP [ WE CAN DO IT JUST THINK ]// +----------------------------------------------------------------------// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.// +----------------------------------------------------------------------// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )// +----------------------------------------------------------------------// | Author: liu21st <liu21st@gmail.com>// +----------------------------------------------------------------------namespace think\model\concern;use InvalidArgumentException;use think\db\Expression;use think\Exception;use think\Loader;use think\model\Relation;trait Attribute{    /**     * 数据表主键 复合主键使用数组定义     * @var string|array     */    protected $pk = 'id';    /**     * 数据表字段信息 留空则自动获取     * @var array     */    protected $field = [];    /**     * JSON数据表字段     * @var array     */    protected $json = [];    /**     * JSON数据取出是否需要转换为数组     * @var bool     */    protected $jsonAssoc = false;    /**     * JSON数据表字段类型     * @var array     */    protected $jsonType = [];    /**     * 数据表废弃字段     * @var array     */    protected $disuse = [];    /**     * 数据表只读字段     * @var array     */    protected $readonly = [];    /**     * 数据表字段类型     * @var array     */    protected $type = [];    /**     * 当前模型数据     * @var array     */    private $data = [];    /**     * 修改器执行记录     * @var array     */    private $set = [];    /**     * 原始数据     * @var array     */    private $origin = [];    /**     * 动态获取器     * @var array     */    private $withAttr = [];    /**     * 获取模型对象的主键     * @access public     * @return string|array     */    public function getPk()    {        return $this->pk;    }    /**     * 判断一个字段名是否为主键字段     * @access public     * @param  string $key 名称     * @return bool     */    protected function isPk($key)    {        $pk = $this->getPk();        if (is_string($pk) && $pk == $key) {            return true;        } elseif (is_array($pk) && in_array($key, $pk)) {            return true;        }        return false;    }    /**     * 获取模型对象的主键值     * @access public     * @return integer     */    public function getKey()    {        $pk = $this->getPk();        if (is_string($pk) && array_key_exists($pk, $this->data)) {            return $this->data[$pk];        }        return;    }    /**     * 设置允许写入的字段     * @access public     * @param  array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段     * @return $this     */    public function allowField($field)    {        if (is_string($field)) {            $field = explode(',', $field);        }        $this->field = $field;        return $this;    }    /**     * 设置只读字段     * @access public     * @param  array|string $field 只读字段     * @return $this     */    public function readonly($field)    {        if (is_string($field)) {            $field = explode(',', $field);        }        $this->readonly = $field;        return $this;    }    /**     * 设置数据对象值     * @access public     * @param  mixed $data  数据或者属性名     * @param  mixed $value 值     * @return $this     */    public function data($data, $value = null)    {        if (is_string($data)) {            $this->data[$data] = $value;            return $this;        }        // 清空数据        $this->data = [];        if (is_object($data)) {            $data = get_object_vars($data);        }        if ($this->disuse) {            // 废弃字段            foreach ((array) $this->disuse as $key) {                if (array_key_exists($key, $data)) {                    unset($data[$key]);                }            }        }        if (true === $value) {            // 数据对象赋值            foreach ($data as $key => $value) {                $this->setAttr($key, $value, $data);            }        } elseif (is_array($value)) {            foreach ($value as $name) {                if (isset($data[$name])) {                    $this->data[$name] = $data[$name];                }            }        } else {            $this->data = $data;        }        return $this;    }    /**     * 批量设置数据对象值     * @access public     * @param  mixed $data  数据     * @param  bool  $set   是否需要进行数据处理     * @return $this     */    public function appendData($data, $set = false)    {        if ($set) {            // 进行数据处理            foreach ($data as $key => $value) {                $this->setAttr($key, $value, $data);            }        } else {            if (is_object($data)) {                $data = get_object_vars($data);            }            $this->data = array_merge($this->data, $data);        }        return $this;    }    /**     * 获取对象原始数据 如果不存在指定字段返回null     * @access public     * @param  string $name 字段名 留空获取全部     * @return mixed     */    public function getOrigin($name = null)    {        if (is_null($name)) {            return $this->origin;        }        return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;    }    /**     * 获取对象原始数据 如果不存在指定字段返回false     * @access public     * @param  string $name 字段名 留空获取全部     * @return mixed     * @throws InvalidArgumentException     */    public function getData($name = null)    {        if (is_null($name)) {            return $this->data;        } elseif (array_key_exists($name, $this->data)) {            return $this->data[$name];        } elseif (array_key_exists($name, $this->relation)) {            return $this->relation[$name];        }        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);    }    /**     * 获取变化的数据 并排除只读数据     * @access public     * @return array     */    public function getChangedData()    {        if ($this->force) {            $data = $this->data;        } else {            $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {                if ((empty($a) || empty($b)) && $a !== $b) {                    return 1;                }                return is_object($a) || $a != $b ? 1 : 0;            });        }        if (!empty($this->readonly)) {            // 只读字段不允许更新            foreach ($this->readonly as $key => $field) {                if (isset($data[$field])) {                    unset($data[$field]);                }            }        }        return $data;    }    /**     * 修改器 设置数据对象值     * @access public     * @param  string $name  属性名     * @param  mixed  $value 属性值     * @param  array  $data  数据     * @return void     */    public function setAttr($name, $value, $data = [])    {        if (isset($this->set[$name])) {            return;        }        if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {            // 自动写入的时间戳字段            $value = $this->autoWriteTimestamp($name);        } else {            // 检测修改器            $method = 'set' . Loader::parseName($name, 1) . 'Attr';            if (method_exists($this, $method)) {                $origin = $this->data;                $value  = $this->$method($value, array_merge($this->data, $data));                $this->set[$name] = true;                if (is_null($value) && $origin !== $this->data) {                    return;                }            } elseif (isset($this->type[$name])) {                // 类型转换                $value = $this->writeTransform($value, $this->type[$name]);            }        }        // 设置数据对象属性        $this->data[$name] = $value;    }    /**     * 是否需要自动写入时间字段     * @access public     * @param  bool $auto     * @return $this     */    public function isAutoWriteTimestamp($auto)    {        $this->autoWriteTimestamp = $auto;        return $this;    }    /**     * 自动写入时间戳     * @access protected     * @param  string $name 时间戳字段     * @return mixed     */    protected function autoWriteTimestamp($name)    {        if (isset($this->type[$name])) {            $type = $this->type[$name];            if (strpos($type, ':')) {                list($type, $param) = explode(':', $type, 2);            }            switch ($type) {                case 'datetime':                case 'date':                    $value = $this->formatDateTime('Y-m-d H:i:s.u');                    break;                case 'timestamp':                case 'integer':                default:                    $value = time();                    break;            }        } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [            'datetime',            'date',            'timestamp',        ])) {            $value = $this->formatDateTime('Y-m-d H:i:s.u');        } else {            $value = time();        }        return $value;    }    /**     * 数据写入 类型转换     * @access protected     * @param  mixed        $value 值     * @param  string|array $type  要转换的类型     * @return mixed     */    protected function writeTransform($value, $type)    {        if (is_null($value)) {            return;        }        if ($value instanceof Expression) {            return $value;        }        if (is_array($type)) {            list($type, $param) = $type;        } elseif (strpos($type, ':')) {            list($type, $param) = explode(':', $type, 2);        }        switch ($type) {            case 'integer':                $value = (int) $value;                break;            case 'float':                if (empty($param)) {                    $value = (float) $value;                } else {                    $value = (float) number_format($value, $param, '.', '');                }                break;            case 'boolean':                $value = (bool) $value;                break;            case 'timestamp':                if (!is_numeric($value)) {                    $value = strtotime($value);                }                break;            case 'datetime':                $value = is_numeric($value) ? $value : strtotime($value);                $value = $this->formatDateTime('Y-m-d H:i:s.u', $value);                break;            case 'object':                if (is_object($value)) {                    $value = json_encode($value, JSON_FORCE_OBJECT);                }                break;            case 'array':                $value = (array) $value;            case 'json':                $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;                $value  = json_encode($value, $option);                break;            case 'serialize':                $value = serialize($value);                break;        }        return $value;    }    /**     * 获取器 获取数据对象的值     * @access public     * @param  string $name 名称     * @param  array  $item 数据     * @return mixed     * @throws InvalidArgumentException     */    public function getAttr($name, &$item = null)    {        try {            $notFound = false;            $value    = $this->getData($name);        } catch (InvalidArgumentException $e) {            $notFound = true;            $value    = null;        }        // 检测属性获取器        $fieldName = Loader::parseName($name);        $method    = 'get' . Loader::parseName($name, 1) . 'Attr';        if (isset($this->withAttr[$fieldName])) {            if ($notFound && $relation = $this->isRelationAttr($name)) {                $modelRelation = $this->$relation();                $value         = $this->getRelationData($modelRelation);            }            $closure = $this->withAttr[$fieldName];            $value   = $closure($value, $this->data);        } elseif (method_exists($this, $method)) {            if ($notFound && $relation = $this->isRelationAttr($name)) {                $modelRelation = $this->$relation();                $value         = $this->getRelationData($modelRelation);            }            $value = $this->$method($value, $this->data);        } elseif (isset($this->type[$name])) {            // 类型转换            $value = $this->readTransform($value, $this->type[$name]);        } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {            if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [                'datetime',                'date',                'timestamp',            ])) {                $value = $this->formatDateTime($this->dateFormat, $value);            } else {                $value = $this->formatDateTime($this->dateFormat, $value, true);            }        } elseif ($notFound) {            $value = $this->getRelationAttribute($name, $item);        }        return $value;    }    /**     * 获取关联属性值     * @access protected     * @param  string   $name  属性名     * @param  array    $item  数据     * @return mixed     */    protected function getRelationAttribute($name, &$item)    {        $relation = $this->isRelationAttr($name);        if ($relation) {            $modelRelation = $this->$relation();            if ($modelRelation instanceof Relation) {                $value = $this->getRelationData($modelRelation);                if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {                    foreach ($bindAttr as $key => $attr) {                        $key = is_numeric($key) ? $attr : $key;                        if (isset($item[$key])) {                            throw new Exception('bind attr has exists:' . $key);                        } else {                            $item[$key] = $value ? $value->getAttr($attr) : null;                        }                    }                    return false;                }                // 保存关联对象值                $this->relation[$name] = $value;                return $value;            }        }        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);    }    /**     * 数据读取 类型转换     * @access protected     * @param  mixed        $value 值     * @param  string|array $type  要转换的类型     * @return mixed     */    protected function readTransform($value, $type)    {        if (is_null($value)) {            return;        }        if (is_array($type)) {            list($type, $param) = $type;        } elseif (strpos($type, ':')) {            list($type, $param) = explode(':', $type, 2);        }        switch ($type) {            case 'integer':                $value = (int) $value;                break;            case 'float':                if (empty($param)) {                    $value = (float) $value;                } else {                    $value = (float) number_format($value, $param, '.', '');                }                break;            case 'boolean':                $value = (bool) $value;                break;            case 'timestamp':                if (!is_null($value)) {                    $format = !empty($param) ? $param : $this->dateFormat;                    $value  = $this->formatDateTime($format, $value, true);                }                break;            case 'datetime':                if (!is_null($value)) {                    $format = !empty($param) ? $param : $this->dateFormat;                    $value  = $this->formatDateTime($format, $value);                }                break;            case 'json':                $value = json_decode($value, true);                break;            case 'array':                $value = empty($value) ? [] : json_decode($value, true);                break;            case 'object':                $value = empty($value) ? new \stdClass() : json_decode($value);                break;            case 'serialize':                try {                    $value = unserialize($value);                } catch (\Exception $e) {                    $value = null;                }                break;            default:                if (false !== strpos($type, '\\')) {                    // 对象类型                    $value = new $type($value);                }        }        return $value;    }    /**     * 设置数据字段获取器     * @access public     * @param  string|array $name       字段名     * @param  callable     $callback   闭包获取器     * @return $this     */    public function withAttribute($name, $callback = null)    {        if (is_array($name)) {            foreach ($name as $key => $val) {                $key = Loader::parseName($key);                $this->withAttr[$key] = $val;            }        } else {            $name = Loader::parseName($name);            $this->withAttr[$name] = $callback;        }        return $this;    }}
 |