Attribute.php 18 KB

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