EloquentRepository.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. <?php
  2. namespace Dcat\Admin\Repositories;
  3. use Dcat\Admin\Form;
  4. use Dcat\Admin\Grid;
  5. use Dcat\Admin\Show;
  6. use Illuminate\Database\Eloquent\Builder;
  7. use Illuminate\Database\Eloquent\Model as EloquentModel;
  8. use Illuminate\Database\Eloquent\Relations;
  9. use Illuminate\Database\Eloquent\SoftDeletes;
  10. use Illuminate\Support\Arr;
  11. use Illuminate\Support\Collection;
  12. use Illuminate\Support\Facades\DB;
  13. use Illuminate\Support\Str;
  14. use Spatie\EloquentSortable\Sortable;
  15. class EloquentRepository extends Repository
  16. {
  17. /**
  18. * @var string
  19. */
  20. protected $eloquentClass;
  21. /**
  22. * @var EloquentModel
  23. */
  24. protected $model;
  25. /**
  26. * @var Builder
  27. */
  28. protected $queryBuilder;
  29. /**
  30. * @var array
  31. */
  32. protected $relations = [];
  33. /**
  34. * EloquentRepository constructor.
  35. *
  36. * @param EloquentModel|array|string $modelOrRelations $modelOrRelations
  37. */
  38. public function __construct($modelOrRelations = [])
  39. {
  40. $this->initModel($modelOrRelations);
  41. }
  42. /**
  43. * Init model.
  44. *
  45. * @param EloquentModel|Builder|array|string $modelOrRelations
  46. */
  47. protected function initModel($modelOrRelations)
  48. {
  49. if (is_string($modelOrRelations) && class_exists($modelOrRelations)) {
  50. $this->eloquentClass = $modelOrRelations;
  51. } elseif ($modelOrRelations instanceof EloquentModel) {
  52. $this->eloquentClass = get_class($modelOrRelations);
  53. $this->model = $modelOrRelations;
  54. } elseif ($modelOrRelations instanceof Builder) {
  55. $this->model = $modelOrRelations->getModel();
  56. $this->eloquentClass = get_class($this->model);
  57. $this->queryBuilder = $modelOrRelations;
  58. } else {
  59. $this->with($modelOrRelations);
  60. }
  61. $this->setKeyName($this->eloquent()->getKeyName());
  62. $this->setIsSoftDeletes(
  63. in_array(SoftDeletes::class, class_uses($this->eloquent()))
  64. );
  65. }
  66. /**
  67. * @return string
  68. */
  69. public function getCreatedAtColumn()
  70. {
  71. return $this->eloquent()->getCreatedAtColumn();
  72. }
  73. /**
  74. * @return string
  75. */
  76. public function getUpdatedAtColumn()
  77. {
  78. return $this->eloquent()->getUpdatedAtColumn();
  79. }
  80. /**
  81. * Get columns of the grid.
  82. *
  83. * @return array
  84. */
  85. public function getGridColumns()
  86. {
  87. return ['*'];
  88. }
  89. /**
  90. * Get columns of the form.
  91. *
  92. * @return array
  93. */
  94. public function getFormColumns()
  95. {
  96. return ['*'];
  97. }
  98. /**
  99. * Get columns of the detail view.
  100. *
  101. * @return array
  102. */
  103. public function getDetailColumns()
  104. {
  105. return ['*'];
  106. }
  107. /**
  108. * Set the relationships that should be eager loaded.
  109. *
  110. * @param mixed $relations
  111. *
  112. * @return $this
  113. */
  114. public function with($relations)
  115. {
  116. $this->relations = (array) $relations;
  117. return $this;
  118. }
  119. /**
  120. * Get the grid data.
  121. *
  122. * @param Grid\Model $model
  123. *
  124. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|Collection|array
  125. */
  126. public function get(Grid\Model $model)
  127. {
  128. $query = $this->newQuery();
  129. if ($this->relations) {
  130. $query->with($this->relations);
  131. }
  132. $model->getQueries()->unique()->each(function ($value) use (&$query) {
  133. if ($value['method'] == 'paginate') {
  134. $value['arguments'][1] = $this->getGridColumns();
  135. } elseif ($value['method'] == 'get') {
  136. $value['arguments'] = $this->getGridColumns();
  137. }
  138. $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
  139. });
  140. return $query;
  141. }
  142. /**
  143. * Get data to build edit form.
  144. *
  145. * @param Form $form
  146. *
  147. * @return array
  148. */
  149. public function edit(Form $form): array
  150. {
  151. $query = $this->newQuery();
  152. if ($this->isSoftDeletes) {
  153. $query->withTrashed();
  154. }
  155. $this->model = $query
  156. ->with($this->getRelations($form))
  157. ->findOrFail($form->key(), $this->getFormColumns());
  158. return $this->model->toArray();
  159. }
  160. /**
  161. * Get detail data.
  162. *
  163. * @param Show $show
  164. *
  165. * @return array
  166. */
  167. public function detail(Show $show): array
  168. {
  169. $query = $this->newQuery();
  170. if ($this->isSoftDeletes) {
  171. $query->withTrashed();
  172. }
  173. $this->model = $query
  174. ->with($this->getRelations($show))
  175. ->findOrFail($show->key(), $this->getDetailColumns());
  176. return $this->model->toArray();
  177. }
  178. /**
  179. * Store a new record.
  180. *
  181. * @param Form $form
  182. *
  183. * @return mixed
  184. */
  185. public function store(Form $form)
  186. {
  187. $result = null;
  188. DB::transaction(function () use ($form, &$result) {
  189. $model = $this->eloquent();
  190. $updates = $form->updates();
  191. list($relations, $relationKeyMap) = $this->getRelationInputs($model, $updates);
  192. if ($relations) {
  193. $updates = Arr::except($updates, array_keys($relationKeyMap));
  194. }
  195. foreach ($updates as $column => $value) {
  196. $model->setAttribute($column, $value);
  197. }
  198. $result = $model->save();
  199. $this->updateRelation($form, $model, $relations, $relationKeyMap);
  200. });
  201. return $this->eloquent()->getKey();
  202. }
  203. /**
  204. * Get data before update.
  205. *
  206. * @param Form $form
  207. *
  208. * @return array
  209. */
  210. public function getDataWhenUpdating(Form $form): array
  211. {
  212. return $this->edit($form);
  213. }
  214. /**
  215. * Update form data.
  216. *
  217. * @param Form $form
  218. *
  219. * @return bool
  220. */
  221. public function update(Form $form)
  222. {
  223. /* @var EloquentModel $builder */
  224. $model = $this->eloquent();
  225. if (! $model->getKey()) {
  226. $model->exists = true;
  227. $model->setAttribute($model->getKeyName(), $form->key());
  228. }
  229. $result = null;
  230. DB::transaction(function () use ($form, $model, &$result) {
  231. $updates = $form->updates();
  232. list($relations, $relationKeyMap) = $this->getRelationInputs($model, $updates);
  233. if ($relations) {
  234. $updates = Arr::except($updates, array_keys($relationKeyMap));
  235. }
  236. foreach ($updates as $column => $value) {
  237. /* @var EloquentModel $model */
  238. $model->setAttribute($column, $value);
  239. }
  240. $result = $model->update();
  241. $this->updateRelation($form, $model, $relations, $relationKeyMap);
  242. });
  243. return $result;
  244. }
  245. /**
  246. * Swaps the order of this model with the model 'above' this model.
  247. *
  248. * @return bool
  249. */
  250. public function moveOrderUp()
  251. {
  252. $model = $this->eloquent();
  253. if ($model instanceof Sortable) {
  254. $model->moveOrderUp();
  255. return true;
  256. }
  257. return false;
  258. }
  259. /**
  260. * Swaps the order of this model with the model 'below' this model.
  261. *
  262. * @return bool
  263. */
  264. public function moveOrderDown()
  265. {
  266. $model = $this->eloquent();
  267. if ($model instanceof Sortable) {
  268. $model->moveOrderDown();
  269. return true;
  270. }
  271. return false;
  272. }
  273. /**
  274. * Destroy data.
  275. *
  276. * @param Form $form
  277. *
  278. * @return bool
  279. */
  280. public function destroy(Form $form, array $deletingData)
  281. {
  282. $id = $form->key();
  283. $deletingData = collect($deletingData)->keyBy($this->getKeyName());
  284. collect(explode(',', $id))->filter()->each(function ($id) use ($form, $deletingData) {
  285. $data = $deletingData->get($id, []);
  286. if (! $data) {
  287. return;
  288. }
  289. $model = $this->createEloquent($data);
  290. $model->exists = true;
  291. if ($this->isSoftDeletes && $model->trashed()) {
  292. $form->deleteFiles($data, true);
  293. $model->forceDelete();
  294. return;
  295. }
  296. $form->deleteFiles($data);
  297. $model->delete();
  298. });
  299. return true;
  300. }
  301. /**
  302. * @param Form $form
  303. *
  304. * @return array
  305. */
  306. public function getDataWhenDeleting(Form $form): array
  307. {
  308. $query = $this->newQuery();
  309. if ($this->isSoftDeletes) {
  310. $query->withTrashed();
  311. }
  312. $id = $form->key();
  313. return $query
  314. ->with($this->getRelations($form))
  315. ->findOrFail(
  316. collect(explode(',', $id))->filter()->toArray(),
  317. $this->getFormColumns()
  318. )
  319. ->toArray();
  320. }
  321. /**
  322. * @return Builder
  323. */
  324. protected function newQuery()
  325. {
  326. if ($this->queryBuilder) {
  327. return clone $this->queryBuilder;
  328. }
  329. return $this->eloquent()->newQuery();
  330. }
  331. /**
  332. * Get the eloquent model.
  333. *
  334. * @return EloquentModel
  335. */
  336. public function eloquent()
  337. {
  338. return $this->model ?: ($this->model = $this->createEloquent());
  339. }
  340. /**
  341. * @param array $data
  342. *
  343. * @return EloquentModel
  344. */
  345. public function createEloquent(array $data = [])
  346. {
  347. $model = new $this->eloquentClass();
  348. if ($data) {
  349. $model->forceFill($data);
  350. }
  351. return $model;
  352. }
  353. /**
  354. * Get all relations of model from callable.
  355. *
  356. * @return array
  357. */
  358. protected function getRelations($builder)
  359. {
  360. $relations = $columns = [];
  361. if ($builder instanceof Form) {
  362. /** @var Form\Field $field */
  363. foreach ($builder->builder()->fields() as $field) {
  364. $columns[] = $field->column();
  365. }
  366. } elseif ($builder instanceof Show) {
  367. /** @var Show\Field $field */
  368. foreach ($builder->fields() as $field) {
  369. $columns[] = $field->getName();
  370. }
  371. }
  372. $model = $this->eloquent();
  373. foreach (Arr::flatten($columns) as $column) {
  374. if (Str::contains($column, '.')) {
  375. [$relation] = explode('.', $column);
  376. if (method_exists($model, $relation) &&
  377. $model->$relation() instanceof Relations\Relation
  378. ) {
  379. $relations[] = $relation;
  380. }
  381. } elseif (method_exists($model, $column) &&
  382. ! method_exists(EloquentModel::class, $column)
  383. ) {
  384. $relations[] = $column;
  385. }
  386. }
  387. return array_unique(array_merge($relations, $this->relations));
  388. }
  389. /**
  390. * Get inputs for relations.
  391. *
  392. * @param EloquentModel $model
  393. * @param array $inputs
  394. *
  395. * @return array
  396. */
  397. protected function getRelationInputs($model, $inputs = [])
  398. {
  399. $map = [];
  400. $relations = [];
  401. foreach ($inputs as $column => $value) {
  402. $relationColumn = null;
  403. if (method_exists($model, $column)) {
  404. $relationColumn = $column;
  405. } elseif (method_exists($model, $camelColumn = Str::camel($column))) {
  406. $relationColumn = $camelColumn;
  407. }
  408. if (! $relationColumn) {
  409. continue;
  410. }
  411. $relation = call_user_func([$model, $relationColumn]);
  412. if ($relation instanceof Relations\Relation) {
  413. $relations[$column] = $value;
  414. $map[$column] = $relationColumn;
  415. }
  416. }
  417. return [&$relations, $map];
  418. }
  419. /**
  420. * Update relation data.
  421. *
  422. * @param Form $form
  423. * @param EloquentModel $model
  424. * @param array $relationsData
  425. * @param array $relationKeyMap
  426. *
  427. * @throws \Exception
  428. */
  429. protected function updateRelation(Form $form, EloquentModel $model, array $relationsData, array $relationKeyMap)
  430. {
  431. foreach ($relationsData as $name => $values) {
  432. $relationName = $relationKeyMap[$name];
  433. if (! method_exists($model, $relationName)) {
  434. continue;
  435. }
  436. $relation = $model->$relationName();
  437. $oneToOneRelation = $relation instanceof Relations\HasOne
  438. || $relation instanceof Relations\MorphOne
  439. || $relation instanceof Relations\BelongsTo;
  440. $prepared = $form->prepareUpdate([$name => $values], $oneToOneRelation);
  441. if (empty($prepared)) {
  442. continue;
  443. }
  444. switch (true) {
  445. case $relation instanceof Relations\BelongsToMany:
  446. case $relation instanceof Relations\MorphToMany:
  447. if (isset($prepared[$name])) {
  448. $relation->sync($prepared[$name]);
  449. }
  450. break;
  451. case $relation instanceof Relations\HasOne:
  452. $related = $model->$name;
  453. // if related is empty
  454. if (is_null($related)) {
  455. $related = $relation->getRelated();
  456. $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
  457. $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
  458. $related->{$relation->getForeignKeyName()} = $model->{$localKey};
  459. }
  460. foreach ($prepared[$name] as $column => $value) {
  461. $related->setAttribute($column, $value);
  462. }
  463. $related->save();
  464. break;
  465. case $relation instanceof Relations\BelongsTo:
  466. $parent = $model->$name;
  467. // if related is empty
  468. if (is_null($parent)) {
  469. $parent = $relation->getRelated();
  470. }
  471. foreach ($prepared[$name] as $column => $value) {
  472. $parent->setAttribute($column, $value);
  473. }
  474. $parent->save();
  475. // When in creating, associate two models
  476. if (! $model->{$relation->getForeignKey()}) {
  477. $model->{$relation->getForeignKey()} = $parent->getKey();
  478. $model->save();
  479. }
  480. break;
  481. case $relation instanceof Relations\MorphOne:
  482. $related = $model->$name;
  483. if (is_null($related)) {
  484. $related = $relation->make();
  485. }
  486. foreach ($prepared[$name] as $column => $value) {
  487. $related->setAttribute($column, $value);
  488. }
  489. $related->save();
  490. break;
  491. case $relation instanceof Relations\HasMany:
  492. case $relation instanceof Relations\MorphMany:
  493. foreach ($prepared[$name] as $related) {
  494. /** @var Relations\Relation $relation */
  495. $relation = $model->$relationName();
  496. $keyName = $relation->getRelated()->getKeyName();
  497. $instance = $relation->findOrNew(Arr::get($related, $keyName));
  498. if ($related[Form::REMOVE_FLAG_NAME] == 1) {
  499. $instance->delete();
  500. continue;
  501. }
  502. Arr::forget($related, Form::REMOVE_FLAG_NAME);
  503. $instance->fill($related);
  504. $instance->save();
  505. }
  506. break;
  507. }
  508. }
  509. }
  510. }