RelationShip.php 20 KB

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