EloquentRepository.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. <?php
  2. namespace Dcat\Admin\Repositories;
  3. use Dcat\Admin\Contracts\TreeRepository;
  4. use Dcat\Admin\Form;
  5. use Dcat\Admin\Grid;
  6. use Dcat\Admin\Show;
  7. use Illuminate\Database\Eloquent\Builder;
  8. use Illuminate\Database\Eloquent\Model as EloquentModel;
  9. use Illuminate\Database\Eloquent\Relations;
  10. use Illuminate\Database\Eloquent\SoftDeletes;
  11. use Illuminate\Support\Arr;
  12. use Illuminate\Support\Collection;
  13. use Illuminate\Support\Facades\DB;
  14. use Illuminate\Support\Str;
  15. use Spatie\EloquentSortable\Sortable;
  16. class EloquentRepository extends Repository implements TreeRepository
  17. {
  18. /**
  19. * @var string
  20. */
  21. protected $eloquentClass;
  22. /**
  23. * @var EloquentModel
  24. */
  25. protected $model;
  26. /**
  27. * @var Builder
  28. */
  29. protected $queryBuilder;
  30. /**
  31. * @var array
  32. */
  33. protected $relations = [];
  34. /**
  35. * EloquentRepository constructor.
  36. *
  37. * @param EloquentModel|array|string $modelOrRelations $modelOrRelations
  38. */
  39. public function __construct($modelOrRelations = [])
  40. {
  41. $this->initModel($modelOrRelations);
  42. }
  43. /**
  44. * 初始化模型.
  45. *
  46. * @param EloquentModel|Builder|array|string $modelOrRelations
  47. */
  48. protected function initModel($modelOrRelations)
  49. {
  50. if (is_string($modelOrRelations) && class_exists($modelOrRelations)) {
  51. $this->eloquentClass = $modelOrRelations;
  52. } elseif ($modelOrRelations instanceof EloquentModel) {
  53. $this->eloquentClass = get_class($modelOrRelations);
  54. $this->model = $modelOrRelations;
  55. } elseif ($modelOrRelations instanceof Builder) {
  56. $this->model = $modelOrRelations->getModel();
  57. $this->eloquentClass = get_class($this->model);
  58. $this->queryBuilder = $modelOrRelations;
  59. } else {
  60. $this->with($modelOrRelations);
  61. }
  62. $this->setKeyName($this->eloquent()->getKeyName());
  63. $this->setIsSoftDeletes(
  64. in_array(SoftDeletes::class, class_uses($this->eloquent()))
  65. );
  66. }
  67. /**
  68. * @return string
  69. */
  70. public function getCreatedAtColumn()
  71. {
  72. return $this->eloquent()->getCreatedAtColumn();
  73. }
  74. /**
  75. * @return string
  76. */
  77. public function getUpdatedAtColumn()
  78. {
  79. return $this->eloquent()->getUpdatedAtColumn();
  80. }
  81. /**
  82. * 获取列表页面查询的字段.
  83. *
  84. * @return array
  85. */
  86. public function getGridColumns()
  87. {
  88. return ['*'];
  89. }
  90. /**
  91. * 获取表单页面查询的字段.
  92. *
  93. * @return array
  94. */
  95. public function getFormColumns()
  96. {
  97. return ['*'];
  98. }
  99. /**
  100. * 获取详情页面查询的字段.
  101. *
  102. * @return array
  103. */
  104. public function getDetailColumns()
  105. {
  106. return ['*'];
  107. }
  108. /**
  109. * 设置关联关系.
  110. *
  111. * @param mixed $relations
  112. *
  113. * @return $this
  114. */
  115. public function with($relations)
  116. {
  117. $this->relations = (array) $relations;
  118. return $this;
  119. }
  120. /**
  121. * 查询Grid表格数据.
  122. *
  123. * @param Grid\Model $model
  124. *
  125. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|Collection|array
  126. */
  127. public function get(Grid\Model $model)
  128. {
  129. $this->setSort($model);
  130. $this->setPaginate($model);
  131. $query = $this->newQuery();
  132. if ($this->relations) {
  133. $query->with($this->relations);
  134. }
  135. $model->getQueries()->unique()->each(function ($value) use (&$query) {
  136. if ($value['method'] == 'paginate') {
  137. $value['arguments'][1] = $this->getGridColumns();
  138. } elseif ($value['method'] == 'get') {
  139. $value['arguments'] = [$this->getGridColumns()];
  140. }
  141. $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
  142. });
  143. return $query;
  144. }
  145. /**
  146. * 设置表格数据排序.
  147. *
  148. * @param Grid\Model $model
  149. *
  150. * @return void
  151. */
  152. protected function setSort(Grid\Model $model)
  153. {
  154. [$column, $type] = $model->getSort();
  155. if (empty($column) || empty($type)) {
  156. return;
  157. }
  158. if (Str::contains($column, '.')) {
  159. $this->setRelationSort($model, $column, $type);
  160. } else {
  161. $model->resetOrderBy();
  162. $model->addQuery('orderBy', [$column, $type]);
  163. }
  164. }
  165. /**
  166. * 设置关联数据排序.
  167. *
  168. * @param Grid\Model $model
  169. * @param string $column
  170. * @param string $type
  171. *
  172. * @return void
  173. */
  174. protected function setRelationSort(Grid\Model $model, $column, $type)
  175. {
  176. [$relationName, $relationColumn] = explode('.', $column);
  177. if ($model->getQueries()->contains(function ($query) use ($relationName) {
  178. return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
  179. })) {
  180. $model->addQuery('select', [$this->getGridColumns()]);
  181. $model->resetOrderBy();
  182. $model->addQuery('orderBy', [
  183. $relationColumn,
  184. $type,
  185. ]);
  186. }
  187. }
  188. /**
  189. * 设置分页参数.
  190. *
  191. * @param Grid\Model $model
  192. *
  193. * @return void
  194. */
  195. protected function setPaginate(Grid\Model $model)
  196. {
  197. $paginate = $model->findQueryByMethod('paginate');
  198. $model->rejectQuery(['paginate']);
  199. if (! $model->allowPagination()) {
  200. $model->addQuery('get', [$this->getGridColumns()]);
  201. } else {
  202. $model->addQuery('paginate', $this->resolvePerPage($model, $paginate));
  203. }
  204. }
  205. /**
  206. * 获取分页参数.
  207. *
  208. * @param Grid\Model $model
  209. * @param array|null $paginate
  210. *
  211. * @return array
  212. */
  213. protected function resolvePerPage(Grid\Model $model, $paginate)
  214. {
  215. if ($paginate && is_array($paginate)) {
  216. if ($perPage = request()->input($model->getPerPageName())) {
  217. $paginate['arguments'][0] = (int) $perPage;
  218. }
  219. return $paginate['arguments'];
  220. }
  221. return [
  222. $model->getPerPage(),
  223. $this->getGridColumns(),
  224. $model->getPageName(),
  225. $model->getCurrentPage(),
  226. ];
  227. }
  228. /**
  229. * 查询编辑页面数据.
  230. *
  231. * @param Form $form
  232. *
  233. * @return array
  234. */
  235. public function edit(Form $form): array
  236. {
  237. $query = $this->newQuery();
  238. if ($this->isSoftDeletes) {
  239. $query->withTrashed();
  240. }
  241. $this->model = $query
  242. ->with($this->getRelations())
  243. ->findOrFail($form->getKey(), $this->getFormColumns());
  244. return $this->model->toArray();
  245. }
  246. /**
  247. * 查询详情页面数据.
  248. *
  249. * @param Show $show
  250. *
  251. * @return array
  252. */
  253. public function detail(Show $show): array
  254. {
  255. $query = $this->newQuery();
  256. if ($this->isSoftDeletes) {
  257. $query->withTrashed();
  258. }
  259. $this->model = $query
  260. ->with($this->getRelations())
  261. ->findOrFail($show->getKey(), $this->getDetailColumns());
  262. return $this->model->toArray();
  263. }
  264. /**
  265. * 新增记录.
  266. *
  267. * @param Form $form
  268. *
  269. * @return mixed
  270. */
  271. public function store(Form $form)
  272. {
  273. $result = null;
  274. DB::transaction(function () use ($form, &$result) {
  275. $model = $this->eloquent();
  276. $updates = $form->updates();
  277. [$relations, $relationKeyMap] = $this->getRelationInputs($model, $updates);
  278. if ($relations) {
  279. $updates = Arr::except($updates, array_keys($relationKeyMap));
  280. }
  281. foreach ($updates as $column => $value) {
  282. $model->setAttribute($column, $value);
  283. }
  284. $result = $model->save();
  285. $this->updateRelation($form, $model, $relations, $relationKeyMap);
  286. });
  287. return $this->eloquent()->getKey();
  288. }
  289. /**
  290. * 查询更新前的行数据.
  291. *
  292. * @param Form $form
  293. *
  294. * @return array
  295. */
  296. public function getDataWhenUpdating(Form $form): array
  297. {
  298. return $this->edit($form);
  299. }
  300. /**
  301. * 更新数据.
  302. *
  303. * @param Form $form
  304. *
  305. * @return bool
  306. */
  307. public function update(Form $form)
  308. {
  309. /* @var EloquentModel $builder */
  310. $model = $this->eloquent();
  311. if (! $model->getKey()) {
  312. $model->exists = true;
  313. $model->setAttribute($model->getKeyName(), $form->getKey());
  314. }
  315. $result = null;
  316. DB::transaction(function () use ($form, $model, &$result) {
  317. $updates = $form->updates();
  318. [$relations, $relationKeyMap] = $this->getRelationInputs($model, $updates);
  319. if ($relations) {
  320. $updates = Arr::except($updates, array_keys($relationKeyMap));
  321. }
  322. foreach ($updates as $column => $value) {
  323. /* @var EloquentModel $model */
  324. $model->setAttribute($column, $value);
  325. }
  326. $result = $model->update();
  327. $this->updateRelation($form, $model, $relations, $relationKeyMap);
  328. });
  329. return $result;
  330. }
  331. /**
  332. * 数据行排序上移一个单位.
  333. *
  334. * @return bool
  335. */
  336. public function moveOrderUp()
  337. {
  338. $model = $this->eloquent();
  339. if (! $model instanceof Sortable) {
  340. throw new \RuntimeException(
  341. sprintf(
  342. 'The model "%s" must be a type of %s.',
  343. get_class($model),
  344. Sortable::class
  345. )
  346. );
  347. }
  348. return $model->moveOrderUp() ? true : false;
  349. }
  350. /**
  351. * 数据行排序下移一个单位.
  352. *
  353. * @return bool
  354. */
  355. public function moveOrderDown()
  356. {
  357. $model = $this->eloquent();
  358. if (! $model instanceof Sortable) {
  359. throw new \RuntimeException(
  360. sprintf(
  361. 'The model "%s" must be a type of %s.',
  362. get_class($model),
  363. Sortable::class
  364. )
  365. );
  366. }
  367. return $model->moveOrderDown() ? true : false;
  368. }
  369. /**
  370. * 删除数据.
  371. *
  372. * @param Form $form
  373. *
  374. * @return bool
  375. */
  376. public function destroy(Form $form, array $deletingData)
  377. {
  378. $id = $form->getKey();
  379. $deletingData = collect($deletingData)->keyBy($this->getKeyName());
  380. collect(explode(',', $id))->filter()->each(function ($id) use ($form, $deletingData) {
  381. $data = $deletingData->get($id, []);
  382. if (! $data) {
  383. return;
  384. }
  385. $model = $this->createDeletingModel($id, $data);
  386. if ($this->isSoftDeletes && $model->trashed()) {
  387. $form->deleteFiles($data, true);
  388. $model->forceDelete();
  389. return;
  390. } elseif (! $this->isSoftDeletes) {
  391. $form->deleteFiles($data);
  392. }
  393. $model->delete();
  394. });
  395. return true;
  396. }
  397. /**
  398. * @param mixed $id
  399. * @param array $data
  400. *
  401. * @return \Illuminate\Database\Eloquent\Model
  402. */
  403. protected function createDeletingModel($id, $data)
  404. {
  405. $model = $this->createEloquent();
  406. $keyName = $model->getKeyName();
  407. $model->{$keyName} = $id;
  408. if ($this->isSoftDeletes) {
  409. $deletedColumn = $model->getDeletedAtColumn();
  410. $model->{$deletedColumn} = $data[$deletedColumn] ?? null;
  411. }
  412. $model->exists = true;
  413. return $model;
  414. }
  415. /**
  416. * 查询删除前的行数据.
  417. *
  418. * @param Form $form
  419. *
  420. * @return array
  421. */
  422. public function getDataWhenDeleting(Form $form): array
  423. {
  424. $query = $this->newQuery();
  425. if ($this->isSoftDeletes) {
  426. $query->withTrashed();
  427. }
  428. $id = $form->getKey();
  429. return $query
  430. ->with($this->getRelations())
  431. ->findOrFail(
  432. collect(explode(',', $id))->filter()->toArray(),
  433. $this->getFormColumns()
  434. )
  435. ->toArray();
  436. }
  437. /**
  438. * 获取父级ID字段名称.
  439. *
  440. * @return string
  441. */
  442. public function getParentColumn()
  443. {
  444. $model = $this->eloquent();
  445. if (method_exists($model, 'getParentColumn')) {
  446. return $model->getParentColumn();
  447. }
  448. }
  449. /**
  450. * 获取标题字段名称.
  451. *
  452. * @return string
  453. */
  454. public function getTitleColumn()
  455. {
  456. $model = $this->eloquent();
  457. if (method_exists($model, 'getTitleColumn')) {
  458. return $model->getTitleColumn();
  459. }
  460. }
  461. /**
  462. * 获取排序字段名称.
  463. *
  464. * @return string
  465. */
  466. public function getOrderColumn()
  467. {
  468. $model = $this->eloquent();
  469. if (method_exists($model, 'getOrderColumn')) {
  470. return $model->getOrderColumn();
  471. }
  472. }
  473. /**
  474. * 保存层级数据排序.
  475. *
  476. * @param array $tree
  477. * @param int $parentId
  478. */
  479. public function saveOrder($tree = [], $parentId = 0)
  480. {
  481. $this->eloquent()->saveOrder($tree, $parentId);
  482. }
  483. /**
  484. * 设置数据查询回调.
  485. *
  486. * @param \Closure|null $query
  487. *
  488. * @return $this
  489. */
  490. public function withQuery($queryCallback)
  491. {
  492. $this->eloquent()->withQuery($queryCallback);
  493. return $this;
  494. }
  495. /**
  496. * 获取层级数据.
  497. *
  498. * @return array
  499. */
  500. public function toTree()
  501. {
  502. if ($this->relations) {
  503. $this->withQuery(function ($model) {
  504. return $model->with($this->relations);
  505. });
  506. }
  507. return $this->eloquent()->toTree();
  508. }
  509. /**
  510. * @return Builder
  511. */
  512. protected function newQuery()
  513. {
  514. if ($this->queryBuilder) {
  515. return clone $this->queryBuilder;
  516. }
  517. return $this->eloquent()->newQuery();
  518. }
  519. /**
  520. * 获取model对象.
  521. *
  522. * @return EloquentModel
  523. */
  524. public function eloquent()
  525. {
  526. return $this->model ?: ($this->model = $this->createEloquent());
  527. }
  528. /**
  529. * @param array $data
  530. *
  531. * @return EloquentModel
  532. */
  533. public function createEloquent(array $data = [])
  534. {
  535. $model = new $this->eloquentClass();
  536. if ($data) {
  537. $model->forceFill($data);
  538. }
  539. return $model;
  540. }
  541. /**
  542. * 获取模型的所有关联关系.
  543. *
  544. * @return array
  545. */
  546. protected function getRelations()
  547. {
  548. return $this->relations;
  549. }
  550. /**
  551. * 获取模型关联关系的表单数据.
  552. *
  553. * @param EloquentModel $model
  554. * @param array $inputs
  555. *
  556. * @return array
  557. */
  558. protected function getRelationInputs($model, $inputs = [])
  559. {
  560. $map = [];
  561. $relations = [];
  562. foreach ($inputs as $column => $value) {
  563. $relationColumn = null;
  564. if (method_exists($model, $column)) {
  565. $relationColumn = $column;
  566. } elseif (method_exists($model, $camelColumn = Str::camel($column))) {
  567. $relationColumn = $camelColumn;
  568. }
  569. if (! $relationColumn) {
  570. continue;
  571. }
  572. $relation = call_user_func([$model, $relationColumn]);
  573. if ($relation instanceof Relations\Relation) {
  574. $relations[$column] = $value;
  575. $map[$column] = $relationColumn;
  576. }
  577. }
  578. return [&$relations, $map];
  579. }
  580. /**
  581. * 更新关联关系数据.
  582. *
  583. * @param Form $form
  584. * @param EloquentModel $model
  585. * @param array $relationsData
  586. * @param array $relationKeyMap
  587. *
  588. * @throws \Exception
  589. */
  590. protected function updateRelation(Form $form, EloquentModel $model, array $relationsData, array $relationKeyMap)
  591. {
  592. foreach ($relationsData as $name => $values) {
  593. $relationName = $relationKeyMap[$name];
  594. if (! method_exists($model, $relationName)) {
  595. continue;
  596. }
  597. $relation = $model->$relationName();
  598. $oneToOneRelation = $relation instanceof Relations\HasOne
  599. || $relation instanceof Relations\MorphOne
  600. || $relation instanceof Relations\BelongsTo;
  601. $prepared = $form->prepareUpdate([$name => $values], $oneToOneRelation);
  602. if (empty($prepared)) {
  603. continue;
  604. }
  605. switch (true) {
  606. case $relation instanceof Relations\BelongsToMany:
  607. case $relation instanceof Relations\MorphToMany:
  608. if (isset($prepared[$name])) {
  609. $relation->sync($prepared[$name]);
  610. }
  611. break;
  612. case $relation instanceof Relations\HasOne:
  613. $related = $model->$name;
  614. // if related is empty
  615. if (is_null($related)) {
  616. $related = $relation->getRelated();
  617. $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
  618. $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
  619. $related->{$relation->getForeignKeyName()} = $model->{$localKey};
  620. }
  621. foreach ($prepared[$name] as $column => $value) {
  622. $related->setAttribute($column, $value);
  623. }
  624. $related->save();
  625. break;
  626. case $relation instanceof Relations\BelongsTo:
  627. case $relation instanceof Relations\MorphTo:
  628. $parent = $model->$name;
  629. // if related is empty
  630. if (is_null($parent)) {
  631. $parent = $relation->getRelated();
  632. }
  633. foreach ($prepared[$name] as $column => $value) {
  634. $parent->setAttribute($column, $value);
  635. }
  636. $parent->save();
  637. // When in creating, associate two models
  638. $foreignKeyMethod = version_compare(app()->version(), '5.8.0', '<') ? 'getForeignKey' : 'getForeignKeyName';
  639. if (! $model->{$relation->{$foreignKeyMethod}()}) {
  640. $model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();
  641. $model->save();
  642. }
  643. break;
  644. case $relation instanceof Relations\MorphOne:
  645. $related = $model->$name;
  646. if (is_null($related)) {
  647. $related = $relation->make();
  648. }
  649. foreach ($prepared[$name] as $column => $value) {
  650. $related->setAttribute($column, $value);
  651. }
  652. $related->save();
  653. break;
  654. case $relation instanceof Relations\HasMany:
  655. case $relation instanceof Relations\MorphMany:
  656. foreach ($prepared[$name] as $related) {
  657. /** @var Relations\Relation $relation */
  658. $relation = $model->$relationName();
  659. $keyName = $relation->getRelated()->getKeyName();
  660. $instance = $relation->findOrNew(Arr::get($related, $keyName));
  661. if ($related[Form::REMOVE_FLAG_NAME] == 1) {
  662. $instance->delete();
  663. continue;
  664. }
  665. Arr::forget($related, Form::REMOVE_FLAG_NAME);
  666. $instance->fill($related);
  667. $instance->save();
  668. }
  669. break;
  670. }
  671. }
  672. }
  673. }