EloquentRepository.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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. * Init model.
  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. * Get columns of the grid.
  83. *
  84. * @return array
  85. */
  86. public function getGridColumns()
  87. {
  88. return ['*'];
  89. }
  90. /**
  91. * Get columns of the form.
  92. *
  93. * @return array
  94. */
  95. public function getFormColumns()
  96. {
  97. return ['*'];
  98. }
  99. /**
  100. * Get columns of the detail view.
  101. *
  102. * @return array
  103. */
  104. public function getDetailColumns()
  105. {
  106. return ['*'];
  107. }
  108. /**
  109. * Set the relationships that should be eager loaded.
  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. * Get the grid data.
  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. $query = $this->newQuery();
  130. if ($this->relations) {
  131. $query->with($this->relations);
  132. }
  133. $model->getQueries()->unique()->each(function ($value) use (&$query) {
  134. if ($value['method'] == 'paginate') {
  135. $value['arguments'][1] = $this->getGridColumns();
  136. } elseif ($value['method'] == 'get') {
  137. $value['arguments'] = $this->getGridColumns();
  138. }
  139. $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
  140. });
  141. return $query;
  142. }
  143. /**
  144. * Get data to build edit form.
  145. *
  146. * @param Form $form
  147. *
  148. * @return array
  149. */
  150. public function edit(Form $form): array
  151. {
  152. $query = $this->newQuery();
  153. if ($this->isSoftDeletes) {
  154. $query->withTrashed();
  155. }
  156. $this->model = $query
  157. ->with($this->getRelations($form))
  158. ->findOrFail($form->key(), $this->getFormColumns());
  159. return $this->model->toArray();
  160. }
  161. /**
  162. * Get detail data.
  163. *
  164. * @param Show $show
  165. *
  166. * @return array
  167. */
  168. public function detail(Show $show): array
  169. {
  170. $query = $this->newQuery();
  171. if ($this->isSoftDeletes) {
  172. $query->withTrashed();
  173. }
  174. $this->model = $query
  175. ->with($this->getRelations($show))
  176. ->findOrFail($show->key(), $this->getDetailColumns());
  177. return $this->model->toArray();
  178. }
  179. /**
  180. * Store a new record.
  181. *
  182. * @param Form $form
  183. *
  184. * @return mixed
  185. */
  186. public function store(Form $form)
  187. {
  188. $result = null;
  189. DB::transaction(function () use ($form, &$result) {
  190. $model = $this->eloquent();
  191. $updates = $form->updates();
  192. [$relations, $relationKeyMap] = $this->getRelationInputs($model, $updates);
  193. if ($relations) {
  194. $updates = Arr::except($updates, array_keys($relationKeyMap));
  195. }
  196. foreach ($updates as $column => $value) {
  197. $model->setAttribute($column, $value);
  198. }
  199. $result = $model->save();
  200. $this->updateRelation($form, $model, $relations, $relationKeyMap);
  201. });
  202. return $this->eloquent()->getKey();
  203. }
  204. /**
  205. * Get data before update.
  206. *
  207. * @param Form $form
  208. *
  209. * @return array
  210. */
  211. public function getDataWhenUpdating(Form $form): array
  212. {
  213. return $this->edit($form);
  214. }
  215. /**
  216. * Update form data.
  217. *
  218. * @param Form $form
  219. *
  220. * @return bool
  221. */
  222. public function update(Form $form)
  223. {
  224. /* @var EloquentModel $builder */
  225. $model = $this->eloquent();
  226. if (! $model->getKey()) {
  227. $model->exists = true;
  228. $model->setAttribute($model->getKeyName(), $form->key());
  229. }
  230. $result = null;
  231. DB::transaction(function () use ($form, $model, &$result) {
  232. $updates = $form->updates();
  233. [$relations, $relationKeyMap] = $this->getRelationInputs($model, $updates);
  234. if ($relations) {
  235. $updates = Arr::except($updates, array_keys($relationKeyMap));
  236. }
  237. foreach ($updates as $column => $value) {
  238. /* @var EloquentModel $model */
  239. $model->setAttribute($column, $value);
  240. }
  241. $result = $model->update();
  242. $this->updateRelation($form, $model, $relations, $relationKeyMap);
  243. });
  244. return $result;
  245. }
  246. /**
  247. * Swaps the order of this model with the model 'above' this model.
  248. *
  249. * @return bool
  250. */
  251. public function moveOrderUp()
  252. {
  253. $model = $this->eloquent();
  254. if (! $model instanceof Sortable) {
  255. throw new \RuntimeException(
  256. sprintf(
  257. 'The model "%s" must be a type of %s.',
  258. get_class($model),
  259. Sortable::class
  260. )
  261. );
  262. }
  263. $model->moveOrderUp();
  264. return true;
  265. }
  266. /**
  267. * Swaps the order of this model with the model 'below' this model.
  268. *
  269. * @return bool
  270. */
  271. public function moveOrderDown()
  272. {
  273. $model = $this->eloquent();
  274. if (! $model instanceof Sortable) {
  275. throw new \RuntimeException(
  276. sprintf(
  277. 'The model "%s" must be a type of %s.',
  278. get_class($model),
  279. Sortable::class
  280. )
  281. );
  282. }
  283. $model->moveOrderDown();
  284. return true;
  285. }
  286. /**
  287. * Destroy data.
  288. *
  289. * @param Form $form
  290. *
  291. * @return bool
  292. */
  293. public function destroy(Form $form, array $deletingData)
  294. {
  295. $id = $form->key();
  296. $deletingData = collect($deletingData)->keyBy($this->getKeyName());
  297. collect(explode(',', $id))->filter()->each(function ($id) use ($form, $deletingData) {
  298. $data = $deletingData->get($id, []);
  299. if (! $data) {
  300. return;
  301. }
  302. $model = $this->createEloquent($data);
  303. $model->exists = true;
  304. if ($this->isSoftDeletes && $model->trashed()) {
  305. $form->deleteFiles($data, true);
  306. $model->forceDelete();
  307. return;
  308. }
  309. $form->deleteFiles($data);
  310. $model->delete();
  311. });
  312. return true;
  313. }
  314. /**
  315. * @param Form $form
  316. *
  317. * @return array
  318. */
  319. public function getDataWhenDeleting(Form $form): array
  320. {
  321. $query = $this->newQuery();
  322. if ($this->isSoftDeletes) {
  323. $query->withTrashed();
  324. }
  325. $id = $form->key();
  326. return $query
  327. ->with($this->getRelations($form))
  328. ->findOrFail(
  329. collect(explode(',', $id))->filter()->toArray(),
  330. $this->getFormColumns()
  331. )
  332. ->toArray();
  333. }
  334. /**
  335. * @return string
  336. */
  337. public function getParentColumn()
  338. {
  339. return $this->eloquent()->getParentColumn();
  340. }
  341. /**
  342. * Get title column.
  343. *
  344. * @return string
  345. */
  346. public function getTitleColumn()
  347. {
  348. return $this->eloquent()->getTitleColumn();
  349. }
  350. /**
  351. * Get order column name.
  352. *
  353. * @return string
  354. */
  355. public function getOrderColumn()
  356. {
  357. return $this->eloquent()->getOrderColumn();
  358. }
  359. /**
  360. * Save tree order from a tree like array.
  361. *
  362. * @param array $tree
  363. * @param int $parentId
  364. */
  365. public function saveOrder($tree = [], $parentId = 0)
  366. {
  367. $this->eloquent()->saveOrder($tree, $parentId);
  368. }
  369. /**
  370. * Set query callback to model.
  371. *
  372. * @param \Closure|null $query
  373. *
  374. * @return $this
  375. */
  376. public function withQuery($queryCallback)
  377. {
  378. $this->eloquent()->withQuery($queryCallback);
  379. return $this;
  380. }
  381. /**
  382. * Format data to tree like array.
  383. *
  384. * @return array
  385. */
  386. public function toTree()
  387. {
  388. if ($this->relations) {
  389. $this->withQuery(function ($model) {
  390. return $model->with($this->relations);
  391. });
  392. }
  393. return $this->eloquent()->toTree();
  394. }
  395. /**
  396. * @return Builder
  397. */
  398. protected function newQuery()
  399. {
  400. if ($this->queryBuilder) {
  401. return clone $this->queryBuilder;
  402. }
  403. return $this->eloquent()->newQuery();
  404. }
  405. /**
  406. * Get the eloquent model.
  407. *
  408. * @return EloquentModel
  409. */
  410. public function eloquent()
  411. {
  412. return $this->model ?: ($this->model = $this->createEloquent());
  413. }
  414. /**
  415. * @param array $data
  416. *
  417. * @return EloquentModel
  418. */
  419. public function createEloquent(array $data = [])
  420. {
  421. $model = new $this->eloquentClass();
  422. if ($data) {
  423. $model->forceFill($data);
  424. }
  425. return $model;
  426. }
  427. /**
  428. * Get all relations of model from callable.
  429. *
  430. * @return array
  431. */
  432. protected function getRelations($builder)
  433. {
  434. $relations = $columns = [];
  435. if ($builder instanceof Form) {
  436. /** @var Form\Field $field */
  437. foreach ($builder->builder()->fields() as $field) {
  438. $columns[] = $field->column();
  439. }
  440. } elseif ($builder instanceof Show) {
  441. /** @var Show\Field $field */
  442. foreach ($builder->fields() as $field) {
  443. $columns[] = $field->getName();
  444. }
  445. }
  446. $model = $this->eloquent();
  447. foreach (Arr::flatten($columns) as $column) {
  448. if (Str::contains($column, '.')) {
  449. [$relation] = explode('.', $column);
  450. if (method_exists($model, $relation) &&
  451. $model->$relation() instanceof Relations\Relation
  452. ) {
  453. $relations[] = $relation;
  454. }
  455. } elseif (method_exists($model, $column) &&
  456. ! method_exists(EloquentModel::class, $column)
  457. ) {
  458. $relations[] = $column;
  459. }
  460. }
  461. return array_unique(array_merge($relations, $this->relations));
  462. }
  463. /**
  464. * Get inputs for relations.
  465. *
  466. * @param EloquentModel $model
  467. * @param array $inputs
  468. *
  469. * @return array
  470. */
  471. protected function getRelationInputs($model, $inputs = [])
  472. {
  473. $map = [];
  474. $relations = [];
  475. foreach ($inputs as $column => $value) {
  476. $relationColumn = null;
  477. if (method_exists($model, $column)) {
  478. $relationColumn = $column;
  479. } elseif (method_exists($model, $camelColumn = Str::camel($column))) {
  480. $relationColumn = $camelColumn;
  481. }
  482. if (! $relationColumn) {
  483. continue;
  484. }
  485. $relation = call_user_func([$model, $relationColumn]);
  486. if ($relation instanceof Relations\Relation) {
  487. $relations[$column] = $value;
  488. $map[$column] = $relationColumn;
  489. }
  490. }
  491. return [&$relations, $map];
  492. }
  493. /**
  494. * Update relation data.
  495. *
  496. * @param Form $form
  497. * @param EloquentModel $model
  498. * @param array $relationsData
  499. * @param array $relationKeyMap
  500. *
  501. * @throws \Exception
  502. */
  503. protected function updateRelation(Form $form, EloquentModel $model, array $relationsData, array $relationKeyMap)
  504. {
  505. foreach ($relationsData as $name => $values) {
  506. $relationName = $relationKeyMap[$name];
  507. if (! method_exists($model, $relationName)) {
  508. continue;
  509. }
  510. $relation = $model->$relationName();
  511. $oneToOneRelation = $relation instanceof Relations\HasOne
  512. || $relation instanceof Relations\MorphOne
  513. || $relation instanceof Relations\BelongsTo;
  514. $prepared = $form->prepareUpdate([$name => $values], $oneToOneRelation);
  515. if (empty($prepared)) {
  516. continue;
  517. }
  518. switch (true) {
  519. case $relation instanceof Relations\BelongsToMany:
  520. case $relation instanceof Relations\MorphToMany:
  521. if (isset($prepared[$name])) {
  522. $relation->sync($prepared[$name]);
  523. }
  524. break;
  525. case $relation instanceof Relations\HasOne:
  526. $related = $model->$name;
  527. // if related is empty
  528. if (is_null($related)) {
  529. $related = $relation->getRelated();
  530. $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
  531. $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
  532. $related->{$relation->getForeignKeyName()} = $model->{$localKey};
  533. }
  534. foreach ($prepared[$name] as $column => $value) {
  535. $related->setAttribute($column, $value);
  536. }
  537. $related->save();
  538. break;
  539. case $relation instanceof Relations\BelongsTo:
  540. $parent = $model->$name;
  541. // if related is empty
  542. if (is_null($parent)) {
  543. $parent = $relation->getRelated();
  544. }
  545. foreach ($prepared[$name] as $column => $value) {
  546. $parent->setAttribute($column, $value);
  547. }
  548. $parent->save();
  549. // When in creating, associate two models
  550. if (! $model->{$relation->getForeignKey()}) {
  551. $model->{$relation->getForeignKey()} = $parent->getKey();
  552. $model->save();
  553. }
  554. break;
  555. case $relation instanceof Relations\MorphOne:
  556. $related = $model->$name;
  557. if (is_null($related)) {
  558. $related = $relation->make();
  559. }
  560. foreach ($prepared[$name] as $column => $value) {
  561. $related->setAttribute($column, $value);
  562. }
  563. $related->save();
  564. break;
  565. case $relation instanceof Relations\HasMany:
  566. case $relation instanceof Relations\MorphMany:
  567. foreach ($prepared[$name] as $related) {
  568. /** @var Relations\Relation $relation */
  569. $relation = $model->$relationName();
  570. $keyName = $relation->getRelated()->getKeyName();
  571. $instance = $relation->findOrNew(Arr::get($related, $keyName));
  572. if ($related[Form::REMOVE_FLAG_NAME] == 1) {
  573. $instance->delete();
  574. continue;
  575. }
  576. Arr::forget($related, Form::REMOVE_FLAG_NAME);
  577. $instance->fill($related);
  578. $instance->save();
  579. }
  580. break;
  581. }
  582. }
  583. }
  584. }