RelationShip.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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 think\Collection;
  13. use think\db\Query;
  14. use think\Exception;
  15. use think\Loader;
  16. use think\Model;
  17. use think\model\Relation;
  18. use think\model\relation\BelongsTo;
  19. use think\model\relation\BelongsToMany;
  20. use think\model\relation\HasMany;
  21. use think\model\relation\HasManyThrough;
  22. use think\model\relation\HasOne;
  23. use think\model\relation\MorphMany;
  24. use think\model\relation\MorphOne;
  25. use think\model\relation\MorphTo;
  26. /**
  27. * 模型关联处理
  28. */
  29. trait RelationShip
  30. {
  31. /**
  32. * 父关联模型对象
  33. * @var object
  34. */
  35. private $parent;
  36. /**
  37. * 模型关联数据
  38. * @var array
  39. */
  40. private $relation = [];
  41. /**
  42. * 关联写入定义信息
  43. * @var array
  44. */
  45. private $together;
  46. /**
  47. * 关联自动写入信息
  48. * @var array
  49. */
  50. protected $relationWrite;
  51. /**
  52. * 设置父关联对象
  53. * @access public
  54. * @param Model $model 模型对象
  55. * @return $this
  56. */
  57. public function setParent($model)
  58. {
  59. $this->parent = $model;
  60. return $this;
  61. }
  62. /**
  63. * 获取父关联对象
  64. * @access public
  65. * @return Model
  66. */
  67. public function getParent()
  68. {
  69. return $this->parent;
  70. }
  71. /**
  72. * 获取当前模型的关联模型数据
  73. * @access public
  74. * @param string $name 关联方法名
  75. * @return mixed
  76. */
  77. public function getRelation($name = null)
  78. {
  79. if (is_null($name)) {
  80. return $this->relation;
  81. } elseif (array_key_exists($name, $this->relation)) {
  82. return $this->relation[$name];
  83. }
  84. return;
  85. }
  86. /**
  87. * 设置关联数据对象值
  88. * @access public
  89. * @param string $name 属性名
  90. * @param mixed $value 属性值
  91. * @param array $data 数据
  92. * @return $this
  93. */
  94. public function setRelation($name, $value, $data = [])
  95. {
  96. // 检测修改器
  97. $method = 'set' . Loader::parseName($name, 1) . 'Attr';
  98. if (method_exists($this, $method)) {
  99. $value = $this->$method($value, array_merge($this->data, $data));
  100. }
  101. $this->relation[$name] = $value;
  102. return $this;
  103. }
  104. /**
  105. * 绑定(一对一)关联属性到当前模型
  106. * @access protected
  107. * @param string $relation 关联名称
  108. * @param array $attrs 绑定属性
  109. * @return $this
  110. * @throws Exception
  111. */
  112. public function bindAttr($relation, array $attrs = [])
  113. {
  114. $relation = $this->getRelation($relation);
  115. foreach ($attrs as $key => $attr) {
  116. $key = is_numeric($key) ? $attr : $key;
  117. $value = $this->getOrigin($key);
  118. if (!is_null($value)) {
  119. throw new Exception('bind attr has exists:' . $key);
  120. }
  121. $this->setAttr($key, $relation ? $relation->getAttr($attr) : null);
  122. }
  123. return $this;
  124. }
  125. /**
  126. * 关联数据写入
  127. * @access public
  128. * @param array|string $relation 关联
  129. * @return $this
  130. */
  131. public function together($relation)
  132. {
  133. if (is_string($relation)) {
  134. $relation = explode(',', $relation);
  135. }
  136. $this->together = $relation;
  137. $this->checkAutoRelationWrite();
  138. return $this;
  139. }
  140. /**
  141. * 根据关联条件查询当前模型
  142. * @access public
  143. * @param string $relation 关联方法名
  144. * @param mixed $operator 比较操作符
  145. * @param integer $count 个数
  146. * @param string $id 关联表的统计字段
  147. * @param string $joinType JOIN类型
  148. * @return Query
  149. */
  150. public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
  151. {
  152. $relation = (new static())->$relation();
  153. if (is_array($operator) || $operator instanceof \Closure) {
  154. return $relation->hasWhere($operator);
  155. }
  156. return $relation->has($operator, $count, $id, $joinType);
  157. }
  158. /**
  159. * 根据关联条件查询当前模型
  160. * @access public
  161. * @param string $relation 关联方法名
  162. * @param mixed $where 查询条件(数组或者闭包)
  163. * @param mixed $fields 字段
  164. * @return Query
  165. */
  166. public static function hasWhere($relation, $where = [], $fields = '*')
  167. {
  168. return (new static())->$relation()->hasWhere($where, $fields);
  169. }
  170. /**
  171. * 查询当前模型的关联数据
  172. * @access public
  173. * @param string|array $relations 关联名
  174. * @param array $withRelationAttr 关联获取器
  175. * @return $this
  176. */
  177. public function relationQuery($relations, $withRelationAttr = [])
  178. {
  179. if (is_string($relations)) {
  180. $relations = explode(',', $relations);
  181. }
  182. foreach ($relations as $key => $relation) {
  183. $subRelation = '';
  184. $closure = null;
  185. if ($relation instanceof \Closure) {
  186. // 支持闭包查询过滤关联条件
  187. $closure = $relation;
  188. $relation = $key;
  189. }
  190. if (is_array($relation)) {
  191. $subRelation = $relation;
  192. $relation = $key;
  193. } elseif (strpos($relation, '.')) {
  194. list($relation, $subRelation) = explode('.', $relation, 2);
  195. }
  196. $method = Loader::parseName($relation, 1, false);
  197. $relationName = Loader::parseName($relation);
  198. $relationResult = $this->$method();
  199. if (isset($withRelationAttr[$relationName])) {
  200. $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]);
  201. }
  202. $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure);
  203. }
  204. return $this;
  205. }
  206. /**
  207. * 预载入关联查询 返回数据集
  208. * @access public
  209. * @param array $resultSet 数据集
  210. * @param string $relation 关联名
  211. * @param array $withRelationAttr 关联获取器
  212. * @param bool $join 是否为JOIN方式
  213. * @return array
  214. */
  215. public function eagerlyResultSet(&$resultSet, $relation, $withRelationAttr = [], $join = false)
  216. {
  217. $relations = is_string($relation) ? explode(',', $relation) : $relation;
  218. foreach ($relations as $key => $relation) {
  219. $subRelation = '';
  220. $closure = null;
  221. if ($relation instanceof \Closure) {
  222. $closure = $relation;
  223. $relation = $key;
  224. }
  225. if (is_array($relation)) {
  226. $subRelation = $relation;
  227. $relation = $key;
  228. } elseif (strpos($relation, '.')) {
  229. list($relation, $subRelation) = explode('.', $relation, 2);
  230. }
  231. $relation = Loader::parseName($relation, 1, false);
  232. $relationName = Loader::parseName($relation);
  233. $relationResult = $this->$relation();
  234. if (isset($withRelationAttr[$relationName])) {
  235. $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]);
  236. }
  237. $relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join);
  238. }
  239. }
  240. /**
  241. * 预载入关联查询 返回模型对象
  242. * @access public
  243. * @param Model $result 数据对象
  244. * @param string $relation 关联名
  245. * @param array $withRelationAttr 关联获取器
  246. * @param bool $join 是否为JOIN方式
  247. * @return Model
  248. */
  249. public function eagerlyResult(&$result, $relation, $withRelationAttr = [], $join = false)
  250. {
  251. $relations = is_string($relation) ? explode(',', $relation) : $relation;
  252. foreach ($relations as $key => $relation) {
  253. $subRelation = '';
  254. $closure = null;
  255. if ($relation instanceof \Closure) {
  256. $closure = $relation;
  257. $relation = $key;
  258. }
  259. if (is_array($relation)) {
  260. $subRelation = $relation;
  261. $relation = $key;
  262. } elseif (strpos($relation, '.')) {
  263. list($relation, $subRelation) = explode('.', $relation, 2);
  264. }
  265. $relation = Loader::parseName($relation, 1, false);
  266. $relationName = Loader::parseName($relation);
  267. $relationResult = $this->$relation();
  268. if (isset($withRelationAttr[$relationName])) {
  269. $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]);
  270. }
  271. $relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join);
  272. }
  273. }
  274. /**
  275. * 关联统计
  276. * @access public
  277. * @param Model $result 数据对象
  278. * @param array $relations 关联名
  279. * @param string $aggregate 聚合查询方法
  280. * @param string $field 字段
  281. * @return void
  282. */
  283. public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*')
  284. {
  285. foreach ($relations as $key => $relation) {
  286. $closure = $name = null;
  287. if ($relation instanceof \Closure) {
  288. $closure = $relation;
  289. $relation = $key;
  290. } elseif (is_string($key)) {
  291. $name = $relation;
  292. $relation = $key;
  293. }
  294. $relation = Loader::parseName($relation, 1, false);
  295. $count = $this->$relation()->relationCount($result, $closure, $aggregate, $field, $name);
  296. if (empty($name)) {
  297. $name = Loader::parseName($relation) . '_' . $aggregate;
  298. }
  299. $result->setAttr($name, $count);
  300. }
  301. }
  302. /**
  303. * HAS ONE 关联定义
  304. * @access public
  305. * @param string $model 模型名
  306. * @param string $foreignKey 关联外键
  307. * @param string $localKey 当前主键
  308. * @return HasOne
  309. */
  310. public function hasOne($model, $foreignKey = '', $localKey = '')
  311. {
  312. // 记录当前关联信息
  313. $model = $this->parseModel($model);
  314. $localKey = $localKey ?: $this->getPk();
  315. $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
  316. return new HasOne($this, $model, $foreignKey, $localKey);
  317. }
  318. /**
  319. * BELONGS TO 关联定义
  320. * @access public
  321. * @param string $model 模型名
  322. * @param string $foreignKey 关联外键
  323. * @param string $localKey 关联主键
  324. * @return BelongsTo
  325. */
  326. public function belongsTo($model, $foreignKey = '', $localKey = '')
  327. {
  328. // 记录当前关联信息
  329. $model = $this->parseModel($model);
  330. $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName());
  331. $localKey = $localKey ?: (new $model)->getPk();
  332. $trace = debug_backtrace(false, 2);
  333. $relation = Loader::parseName($trace[1]['function']);
  334. return new BelongsTo($this, $model, $foreignKey, $localKey, $relation);
  335. }
  336. /**
  337. * HAS MANY 关联定义
  338. * @access public
  339. * @param string $model 模型名
  340. * @param string $foreignKey 关联外键
  341. * @param string $localKey 当前主键
  342. * @return HasMany
  343. */
  344. public function hasMany($model, $foreignKey = '', $localKey = '')
  345. {
  346. // 记录当前关联信息
  347. $model = $this->parseModel($model);
  348. $localKey = $localKey ?: $this->getPk();
  349. $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
  350. return new HasMany($this, $model, $foreignKey, $localKey);
  351. }
  352. /**
  353. * HAS MANY 远程关联定义
  354. * @access public
  355. * @param string $model 模型名
  356. * @param string $through 中间模型名
  357. * @param string $foreignKey 关联外键
  358. * @param string $throughKey 关联外键
  359. * @param string $localKey 当前主键
  360. * @return HasManyThrough
  361. */
  362. public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '')
  363. {
  364. // 记录当前关联信息
  365. $model = $this->parseModel($model);
  366. $through = $this->parseModel($through);
  367. $localKey = $localKey ?: $this->getPk();
  368. $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
  369. $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
  370. return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey);
  371. }
  372. /**
  373. * BELONGS TO MANY 关联定义
  374. * @access public
  375. * @param string $model 模型名
  376. * @param string $table 中间表名
  377. * @param string $foreignKey 关联外键
  378. * @param string $localKey 当前模型关联键
  379. * @return BelongsToMany
  380. */
  381. public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '')
  382. {
  383. // 记录当前关联信息
  384. $model = $this->parseModel($model);
  385. $name = Loader::parseName(basename(str_replace('\\', '/', $model)));
  386. $table = $table ?: Loader::parseName($this->name) . '_' . $name;
  387. $foreignKey = $foreignKey ?: $name . '_id';
  388. $localKey = $localKey ?: $this->getForeignKey($this->name);
  389. return new BelongsToMany($this, $model, $table, $foreignKey, $localKey);
  390. }
  391. /**
  392. * MORPH One 关联定义
  393. * @access public
  394. * @param string $model 模型名
  395. * @param string|array $morph 多态字段信息
  396. * @param string $type 多态类型
  397. * @return MorphOne
  398. */
  399. public function morphOne($model, $morph = null, $type = '')
  400. {
  401. // 记录当前关联信息
  402. $model = $this->parseModel($model);
  403. if (is_null($morph)) {
  404. $trace = debug_backtrace(false, 2);
  405. $morph = Loader::parseName($trace[1]['function']);
  406. }
  407. if (is_array($morph)) {
  408. list($morphType, $foreignKey) = $morph;
  409. } else {
  410. $morphType = $morph . '_type';
  411. $foreignKey = $morph . '_id';
  412. }
  413. $type = $type ?: get_class($this);
  414. return new MorphOne($this, $model, $foreignKey, $morphType, $type);
  415. }
  416. /**
  417. * MORPH MANY 关联定义
  418. * @access public
  419. * @param string $model 模型名
  420. * @param string|array $morph 多态字段信息
  421. * @param string $type 多态类型
  422. * @return MorphMany
  423. */
  424. public function morphMany($model, $morph = null, $type = '')
  425. {
  426. // 记录当前关联信息
  427. $model = $this->parseModel($model);
  428. if (is_null($morph)) {
  429. $trace = debug_backtrace(false, 2);
  430. $morph = Loader::parseName($trace[1]['function']);
  431. }
  432. $type = $type ?: get_class($this);
  433. if (is_array($morph)) {
  434. list($morphType, $foreignKey) = $morph;
  435. } else {
  436. $morphType = $morph . '_type';
  437. $foreignKey = $morph . '_id';
  438. }
  439. return new MorphMany($this, $model, $foreignKey, $morphType, $type);
  440. }
  441. /**
  442. * MORPH TO 关联定义
  443. * @access public
  444. * @param string|array $morph 多态字段信息
  445. * @param array $alias 多态别名定义
  446. * @return MorphTo
  447. */
  448. public function morphTo($morph = null, $alias = [])
  449. {
  450. $trace = debug_backtrace(false, 2);
  451. $relation = Loader::parseName($trace[1]['function']);
  452. if (is_null($morph)) {
  453. $morph = $relation;
  454. }
  455. // 记录当前关联信息
  456. if (is_array($morph)) {
  457. list($morphType, $foreignKey) = $morph;
  458. } else {
  459. $morphType = $morph . '_type';
  460. $foreignKey = $morph . '_id';
  461. }
  462. return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
  463. }
  464. /**
  465. * 解析模型的完整命名空间
  466. * @access protected
  467. * @param string $model 模型名(或者完整类名)
  468. * @return string
  469. */
  470. protected function parseModel($model)
  471. {
  472. if (false === strpos($model, '\\')) {
  473. $path = explode('\\', static::class);
  474. array_pop($path);
  475. array_push($path, Loader::parseName($model, 1));
  476. $model = implode('\\', $path);
  477. }
  478. return $model;
  479. }
  480. /**
  481. * 获取模型的默认外键名
  482. * @access protected
  483. * @param string $name 模型名
  484. * @return string
  485. */
  486. protected function getForeignKey($name)
  487. {
  488. if (strpos($name, '\\')) {
  489. $name = basename(str_replace('\\', '/', $name));
  490. }
  491. return Loader::parseName($name) . '_id';
  492. }
  493. /**
  494. * 检查属性是否为关联属性 如果是则返回关联方法名
  495. * @access protected
  496. * @param string $attr 关联属性名
  497. * @return string|false
  498. */
  499. protected function isRelationAttr($attr)
  500. {
  501. $relation = Loader::parseName($attr, 1, false);
  502. if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) {
  503. return $relation;
  504. }
  505. return false;
  506. }
  507. /**
  508. * 智能获取关联模型数据
  509. * @access protected
  510. * @param Relation $modelRelation 模型关联对象
  511. * @return mixed
  512. */
  513. protected function getRelationData(Relation $modelRelation)
  514. {
  515. if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) {
  516. $value = $this->parent;
  517. } else {
  518. // 获取关联数据
  519. $value = $modelRelation->getRelation();
  520. }
  521. return $value;
  522. }
  523. /**
  524. * 关联数据自动写入检查
  525. * @access protected
  526. * @return void
  527. */
  528. protected function checkAutoRelationWrite()
  529. {
  530. foreach ($this->together as $key => $name) {
  531. if (is_array($name)) {
  532. if (key($name) === 0) {
  533. $this->relationWrite[$key] = [];
  534. // 绑定关联属性
  535. foreach ((array) $name as $val) {
  536. if (isset($this->data[$val])) {
  537. $this->relationWrite[$key][$val] = $this->data[$val];
  538. }
  539. }
  540. } else {
  541. // 直接传入关联数据
  542. $this->relationWrite[$key] = $name;
  543. }
  544. } elseif (isset($this->relation[$name])) {
  545. $this->relationWrite[$name] = $this->relation[$name];
  546. } elseif (isset($this->data[$name])) {
  547. $this->relationWrite[$name] = $this->data[$name];
  548. unset($this->data[$name]);
  549. }
  550. }
  551. }
  552. /**
  553. * 自动关联数据更新(针对一对一关联)
  554. * @access protected
  555. * @return void
  556. */
  557. protected function autoRelationUpdate()
  558. {
  559. foreach ($this->relationWrite as $name => $val) {
  560. if ($val instanceof Model) {
  561. $val->isUpdate()->save();
  562. } else {
  563. $model = $this->getRelation($name);
  564. if ($model instanceof Model) {
  565. $model->isUpdate()->save($val);
  566. }
  567. }
  568. }
  569. }
  570. /**
  571. * 自动关联数据写入(针对一对一关联)
  572. * @access protected
  573. * @return void
  574. */
  575. protected function autoRelationInsert()
  576. {
  577. foreach ($this->relationWrite as $name => $val) {
  578. $method = Loader::parseName($name, 1, false);
  579. $this->$method()->save($val);
  580. }
  581. }
  582. /**
  583. * 自动关联数据删除(支持一对一及一对多关联)
  584. * @access protected
  585. * @return void
  586. */
  587. protected function autoRelationDelete()
  588. {
  589. foreach ($this->relationWrite as $key => $name) {
  590. $name = is_numeric($key) ? $name : $key;
  591. $result = $this->getRelation($name);
  592. if ($result instanceof Model) {
  593. $result->delete();
  594. } elseif ($result instanceof Collection) {
  595. foreach ($result as $model) {
  596. $model->delete();
  597. }
  598. }
  599. }
  600. }
  601. }