Attribute.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. namespace think\model\concern;
  12. use InvalidArgumentException;
  13. use think\Exception;
  14. use think\Loader;
  15. use think\model\Relation;
  16. trait Attribute
  17. {
  18. /**
  19. * 数据表主键 复合主键使用数组定义
  20. * @var string|array
  21. */
  22. protected $pk = 'id';
  23. /**
  24. * 数据表字段信息 留空则自动获取
  25. * @var array
  26. */
  27. protected $field = [];
  28. /**
  29. * JSON数据表字段
  30. * @var array
  31. */
  32. protected $json = [];
  33. /**
  34. * 数据表废弃字段
  35. * @var array
  36. */
  37. protected $disuse = [];
  38. /**
  39. * 数据表只读字段
  40. * @var array
  41. */
  42. protected $readonly = [];
  43. /**
  44. * 数据表字段类型
  45. * @var array
  46. */
  47. protected $type = [];
  48. /**
  49. * 当前模型数据
  50. * @var array
  51. */
  52. private $data = [];
  53. /**
  54. * 原始数据
  55. * @var array
  56. */
  57. private $origin = [];
  58. /**
  59. * 获取模型对象的主键
  60. * @access public
  61. * @return string|array
  62. */
  63. public function getPk()
  64. {
  65. return $this->pk;
  66. }
  67. /**
  68. * 判断一个字段名是否为主键字段
  69. * @access public
  70. * @param string $key 名称
  71. * @return bool
  72. */
  73. protected function isPk($key)
  74. {
  75. $pk = $this->getPk();
  76. if (is_string($pk) && $pk == $key) {
  77. return true;
  78. } elseif (is_array($pk) && in_array($key, $pk)) {
  79. return true;
  80. }
  81. return false;
  82. }
  83. /**
  84. * 设置允许写入的字段
  85. * @access public
  86. * @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段
  87. * @return $this
  88. */
  89. public function allowField($field)
  90. {
  91. if (is_string($field)) {
  92. $field = explode(',', $field);
  93. }
  94. $this->field = $field;
  95. return $this;
  96. }
  97. /**
  98. * 设置只读字段
  99. * @access public
  100. * @param array|string $field 只读字段
  101. * @return $this
  102. */
  103. public function readonly($field)
  104. {
  105. if (is_string($field)) {
  106. $field = explode(',', $field);
  107. }
  108. $this->readonly = $field;
  109. return $this;
  110. }
  111. /**
  112. * 设置数据对象值
  113. * @access public
  114. * @param mixed $data 数据或者属性名
  115. * @param mixed $value 值
  116. * @return $this
  117. */
  118. public function data($data, $value = null)
  119. {
  120. if (is_string($data)) {
  121. $this->data[$data] = $value;
  122. } else {
  123. // 清空数据
  124. $this->data = [];
  125. if (is_object($data)) {
  126. $data = get_object_vars($data);
  127. }
  128. if ($this->disuse) {
  129. // 废弃字段
  130. foreach ((array) $this->disuse as $key) {
  131. if (array_key_exists($key, $data)) {
  132. unset($data[$key]);
  133. }
  134. }
  135. }
  136. if (true === $value) {
  137. // 数据对象赋值
  138. foreach ($data as $key => $value) {
  139. $this->setAttr($key, $value, $data);
  140. }
  141. } elseif (is_array($value)) {
  142. foreach ($value as $name) {
  143. if (isset($data[$name])) {
  144. $this->data[$name] = $data[$name];
  145. }
  146. }
  147. } else {
  148. $this->data = $data;
  149. }
  150. }
  151. return $this;
  152. }
  153. /**
  154. * 批量设置数据对象值
  155. * @access public
  156. * @param mixed $data 数据
  157. * @param bool $set 是否需要进行数据处理
  158. * @return $this
  159. */
  160. public function appendData($data, $set = false)
  161. {
  162. if ($set) {
  163. // 进行数据处理
  164. foreach ($data as $key => $value) {
  165. $this->setAttr($key, $value, $data);
  166. }
  167. } else {
  168. if (is_object($data)) {
  169. $data = get_object_vars($data);
  170. }
  171. $this->data = array_merge($this->data, $data);
  172. }
  173. return $this;
  174. }
  175. /**
  176. * 获取对象原始数据 如果不存在指定字段返回null
  177. * @access public
  178. * @param string $name 字段名 留空获取全部
  179. * @return mixed
  180. */
  181. public function getOrigin($name = null)
  182. {
  183. if (is_null($name)) {
  184. return $this->origin;
  185. } else {
  186. return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
  187. }
  188. }
  189. /**
  190. * 获取对象原始数据 如果不存在指定字段返回false
  191. * @access public
  192. * @param string $name 字段名 留空获取全部
  193. * @return mixed
  194. * @throws InvalidArgumentException
  195. */
  196. public function getData($name = null)
  197. {
  198. if (is_null($name)) {
  199. return $this->data;
  200. } elseif (array_key_exists($name, $this->data)) {
  201. return $this->data[$name];
  202. } elseif (array_key_exists($name, $this->relation)) {
  203. return $this->relation[$name];
  204. } else {
  205. throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
  206. }
  207. }
  208. /**
  209. * 获取变化的数据 并排除只读数据
  210. * @access public
  211. * @return array
  212. */
  213. public function getChangedData()
  214. {
  215. if ($this->force) {
  216. $data = $this->data;
  217. } else {
  218. $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
  219. if ((empty($a) || empty($b)) && $a !== $b) {
  220. return 1;
  221. }
  222. return is_object($a) || $a != $b ? 1 : 0;
  223. });
  224. }
  225. if (!empty($this->readonly)) {
  226. // 只读字段不允许更新
  227. foreach ($this->readonly as $key => $field) {
  228. if (isset($data[$field])) {
  229. unset($data[$field]);
  230. }
  231. }
  232. }
  233. return $data;
  234. }
  235. /**
  236. * 修改器 设置数据对象值
  237. * @access public
  238. * @param string $name 属性名
  239. * @param mixed $value 属性值
  240. * @param array $data 数据
  241. * @return $this
  242. */
  243. public function setAttr($name, $value, $data = [])
  244. {
  245. if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
  246. // 自动写入的时间戳字段
  247. $value = $this->autoWriteTimestamp($name);
  248. } else {
  249. // 检测修改器
  250. $method = 'set' . Loader::parseName($name, 1) . 'Attr';
  251. if (method_exists($this, $method)) {
  252. $value = $this->$method($value, array_merge($this->data, $data));
  253. } elseif (isset($this->type[$name])) {
  254. // 类型转换
  255. $value = $this->writeTransform($value, $this->type[$name]);
  256. }
  257. }
  258. // 设置数据对象属性
  259. $this->data[$name] = $value;
  260. return $this;
  261. }
  262. /**
  263. * 是否需要自动写入时间字段
  264. * @access public
  265. * @param bool $auto
  266. * @return $this
  267. */
  268. public function isAutoWriteTimestamp($auto)
  269. {
  270. $this->autoWriteTimestamp = $auto;
  271. return $this;
  272. }
  273. /**
  274. * 自动写入时间戳
  275. * @access protected
  276. * @param string $name 时间戳字段
  277. * @return mixed
  278. */
  279. protected function autoWriteTimestamp($name)
  280. {
  281. if (isset($this->type[$name])) {
  282. $type = $this->type[$name];
  283. if (strpos($type, ':')) {
  284. list($type, $param) = explode(':', $type, 2);
  285. }
  286. switch ($type) {
  287. case 'datetime':
  288. case 'date':
  289. $format = !empty($param) ? $param : $this->dateFormat;
  290. $value = $this->formatDateTime(time(), $format);
  291. break;
  292. case 'timestamp':
  293. case 'integer':
  294. default:
  295. $value = time();
  296. break;
  297. }
  298. } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
  299. 'datetime',
  300. 'date',
  301. 'timestamp',
  302. ])) {
  303. $value = $this->formatDateTime(time(), $this->dateFormat);
  304. } else {
  305. $value = $this->formatDateTime(time(), $this->dateFormat, true);
  306. }
  307. return $value;
  308. }
  309. /**
  310. * 数据写入 类型转换
  311. * @access protected
  312. * @param mixed $value 值
  313. * @param string|array $type 要转换的类型
  314. * @return mixed
  315. */
  316. protected function writeTransform($value, $type)
  317. {
  318. if (is_null($value)) {
  319. return;
  320. }
  321. if (is_array($type)) {
  322. list($type, $param) = $type;
  323. } elseif (strpos($type, ':')) {
  324. list($type, $param) = explode(':', $type, 2);
  325. }
  326. switch ($type) {
  327. case 'integer':
  328. $value = (int) $value;
  329. break;
  330. case 'float':
  331. if (empty($param)) {
  332. $value = (float) $value;
  333. } else {
  334. $value = (float) number_format($value, $param, '.', '');
  335. }
  336. break;
  337. case 'boolean':
  338. $value = (bool) $value;
  339. break;
  340. case 'timestamp':
  341. if (!is_numeric($value)) {
  342. $value = strtotime($value);
  343. }
  344. break;
  345. case 'datetime':
  346. $format = !empty($param) ? $param : $this->dateFormat;
  347. $value = is_numeric($value) ? $value : strtotime($value);
  348. $value = $this->formatDateTime($value, $format);
  349. break;
  350. case 'object':
  351. if (is_object($value)) {
  352. $value = json_encode($value, JSON_FORCE_OBJECT);
  353. }
  354. break;
  355. case 'array':
  356. $value = (array) $value;
  357. case 'json':
  358. $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
  359. $value = json_encode($value, $option);
  360. break;
  361. case 'serialize':
  362. $value = serialize($value);
  363. break;
  364. }
  365. return $value;
  366. }
  367. /**
  368. * 获取器 获取数据对象的值
  369. * @access public
  370. * @param string $name 名称
  371. * @param array $item 数据
  372. * @return mixed
  373. * @throws InvalidArgumentException
  374. */
  375. public function getAttr($name, &$item = null)
  376. {
  377. try {
  378. $notFound = false;
  379. $value = $this->getData($name);
  380. } catch (InvalidArgumentException $e) {
  381. $notFound = true;
  382. $value = null;
  383. }
  384. // 检测属性获取器
  385. $method = 'get' . Loader::parseName($name, 1) . 'Attr';
  386. if (method_exists($this, $method)) {
  387. if ($notFound && $relation = $this->isRelationAttr($name)) {
  388. $modelRelation = $this->$relation();
  389. $value = $this->getRelationData($modelRelation);
  390. }
  391. $value = $this->$method($value, $this->data);
  392. } elseif (isset($this->type[$name])) {
  393. // 类型转换
  394. $value = $this->readTransform($value, $this->type[$name]);
  395. } elseif (in_array($name, [$this->createTime, $this->updateTime])) {
  396. if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
  397. 'datetime',
  398. 'date',
  399. 'timestamp',
  400. ])) {
  401. $value = $this->formatDateTime(strtotime($value), $this->dateFormat);
  402. } else {
  403. $value = $this->formatDateTime($value, $this->dateFormat);
  404. }
  405. } elseif ($notFound) {
  406. $relation = $this->isRelationAttr($name);
  407. if ($relation) {
  408. $modelRelation = $this->$relation();
  409. if ($modelRelation instanceof Relation) {
  410. $value = $this->getRelationData($modelRelation);
  411. if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {
  412. foreach ($bindAttr as $key => $attr) {
  413. $key = is_numeric($key) ? $attr : $key;
  414. if (isset($item[$key])) {
  415. throw new Exception('bind attr has exists:' . $key);
  416. } else {
  417. $item[$key] = $value ? $value->getAttr($attr) : null;
  418. }
  419. }
  420. return false;
  421. }
  422. // 保存关联对象值
  423. $this->relation[$name] = $value;
  424. return $value;
  425. }
  426. }
  427. throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
  428. }
  429. return $value;
  430. }
  431. /**
  432. * 数据读取 类型转换
  433. * @access protected
  434. * @param mixed $value 值
  435. * @param string|array $type 要转换的类型
  436. * @return mixed
  437. */
  438. protected function readTransform($value, $type)
  439. {
  440. if (is_null($value)) {
  441. return;
  442. }
  443. if (is_array($type)) {
  444. list($type, $param) = $type;
  445. } elseif (strpos($type, ':')) {
  446. list($type, $param) = explode(':', $type, 2);
  447. }
  448. switch ($type) {
  449. case 'integer':
  450. $value = (int) $value;
  451. break;
  452. case 'float':
  453. if (empty($param)) {
  454. $value = (float) $value;
  455. } else {
  456. $value = (float) number_format($value, $param, '.', '');
  457. }
  458. break;
  459. case 'boolean':
  460. $value = (bool) $value;
  461. break;
  462. case 'timestamp':
  463. if (!is_null($value)) {
  464. $format = !empty($param) ? $param : $this->dateFormat;
  465. $value = $this->formatDateTime($value, $format);
  466. }
  467. break;
  468. case 'datetime':
  469. if (!is_null($value)) {
  470. $format = !empty($param) ? $param : $this->dateFormat;
  471. $value = $this->formatDateTime(strtotime($value), $format);
  472. }
  473. break;
  474. case 'json':
  475. $value = json_decode($value, true);
  476. break;
  477. case 'array':
  478. $value = empty($value) ? [] : json_decode($value, true);
  479. break;
  480. case 'object':
  481. $value = empty($value) ? new \stdClass() : json_decode($value);
  482. break;
  483. case 'serialize':
  484. $value = unserialize($value);
  485. break;
  486. default:
  487. if (false !== strpos($type, '\\')) {
  488. // 对象类型
  489. $value = new $type($value);
  490. }
  491. }
  492. return $value;
  493. }
  494. }