Model.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. <?php
  2. namespace Dcat\Admin\Grid;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Exception\AdminException;
  5. use Dcat\Admin\Grid;
  6. use Dcat\Admin\Repositories\Repository;
  7. use Illuminate\Contracts\Support\Arrayable;
  8. use Illuminate\Database\Eloquent\Relations\Relation;
  9. use Illuminate\Database\Query\Builder;
  10. use Illuminate\Http\Request;
  11. use Illuminate\Pagination\AbstractPaginator;
  12. use Illuminate\Pagination\LengthAwarePaginator;
  13. use Illuminate\Pagination\Paginator;
  14. use Illuminate\Support\Arr;
  15. use Illuminate\Support\Collection;
  16. use Illuminate\Support\Str;
  17. /**
  18. * @mixin Builder
  19. */
  20. class Model
  21. {
  22. use Grid\Concerns\HasTree;
  23. /**
  24. * @var Request
  25. */
  26. protected $request;
  27. /**
  28. * @var Repository
  29. */
  30. protected $repository;
  31. /**
  32. * @var AbstractPaginator
  33. */
  34. protected $paginator;
  35. /**
  36. * Array of queries of the model.
  37. *
  38. * @var \Illuminate\Support\Collection
  39. */
  40. protected $queries;
  41. /**
  42. * Sort parameters of the model.
  43. *
  44. * @var array
  45. */
  46. protected $sort;
  47. /**
  48. * @var Collection
  49. */
  50. protected $data;
  51. /**
  52. * @var callable
  53. */
  54. protected $builder;
  55. /*
  56. * 20 items per page as default.
  57. *
  58. * @var int
  59. */
  60. protected $perPage = 20;
  61. /**
  62. * @var string
  63. */
  64. protected $pageName = 'page';
  65. /**
  66. * @var int
  67. */
  68. protected $currentPage;
  69. /**
  70. * If the model use pagination.
  71. *
  72. * @var bool
  73. */
  74. protected $usePaginate = true;
  75. /**
  76. * The query string variable used to store the per-page.
  77. *
  78. * @var string
  79. */
  80. protected $perPageName = 'per_page';
  81. /**
  82. * The query string variable used to store the sort.
  83. *
  84. * @var string
  85. */
  86. protected $sortName = '_sort';
  87. /**
  88. * @var bool
  89. */
  90. protected $simple = false;
  91. /**
  92. * @var Grid
  93. */
  94. protected $grid;
  95. /**
  96. * @var Relation
  97. */
  98. protected $relation;
  99. /**
  100. * @var array
  101. */
  102. protected $eagerLoads = [];
  103. /**
  104. * @var array
  105. */
  106. protected $constraints = [];
  107. /**
  108. * Create a new grid model instance.
  109. *
  110. * @param Repository|\Illuminate\Database\Eloquent\Model $repository
  111. * @param Request $request
  112. */
  113. public function __construct(Request $request, $repository = null)
  114. {
  115. if ($repository) {
  116. $this->repository = Admin::repository($repository);
  117. }
  118. $this->request = $request;
  119. $this->initQueries();
  120. }
  121. /**
  122. * @return void
  123. */
  124. protected function initQueries()
  125. {
  126. $this->queries = new Collection();
  127. }
  128. /**
  129. * @return Repository|null
  130. */
  131. public function repository()
  132. {
  133. return $this->repository;
  134. }
  135. /**
  136. * @return Collection
  137. */
  138. public function getQueries()
  139. {
  140. return $this->queries = $this->queries->unique();
  141. }
  142. /**
  143. * @param Collection $query
  144. * @return void
  145. */
  146. public function setQueries(Collection $query)
  147. {
  148. $this->queries = $query;
  149. }
  150. /**
  151. * @return AbstractPaginator|LengthAwarePaginator
  152. */
  153. public function paginator(): ?AbstractPaginator
  154. {
  155. $this->buildData();
  156. return $this->paginator;
  157. }
  158. /**
  159. * 是否使用 simplePaginate方法进行分页.
  160. *
  161. * @param bool $value
  162. * @return $this
  163. */
  164. public function simple(bool $value = true)
  165. {
  166. $this->simple = $value;
  167. return $this;
  168. }
  169. /**
  170. * @return string
  171. */
  172. public function getPaginateMethod()
  173. {
  174. return $this->simple ? 'simplePaginate' : 'paginate';
  175. }
  176. /**
  177. * @param int $total
  178. * @param Collection|array $data
  179. * @return LengthAwarePaginator|Paginator
  180. */
  181. public function makePaginator($total, $data, string $url = null)
  182. {
  183. if ($this->simple) {
  184. $paginator = new Paginator($data, $this->getPerPage(), $this->getCurrentPage());
  185. } else {
  186. $paginator = new LengthAwarePaginator(
  187. $data,
  188. $total,
  189. $this->getPerPage(), // 传入每页显示行数
  190. $this->getCurrentPage() // 传入当前页码
  191. );
  192. }
  193. return $paginator->setPath(
  194. $url ?: url()->current()
  195. );
  196. }
  197. /**
  198. * Get primary key name of model.
  199. *
  200. * @return string|array
  201. */
  202. public function getKeyName()
  203. {
  204. return $this->grid->getKeyName();
  205. }
  206. /**
  207. * Enable or disable pagination.
  208. *
  209. * @param bool $use
  210. *
  211. * @reutrn $this;
  212. */
  213. public function usePaginate($use = true)
  214. {
  215. $this->usePaginate = $use;
  216. return $this;
  217. }
  218. /**
  219. * @return bool
  220. */
  221. public function allowPagination()
  222. {
  223. return $this->usePaginate;
  224. }
  225. /**
  226. * Get the query string variable used to store the per-page.
  227. *
  228. * @return string
  229. */
  230. public function getPerPageName()
  231. {
  232. return $this->grid->makeName($this->perPageName);
  233. }
  234. /**
  235. * @param int $perPage
  236. */
  237. public function setPerPage(int $perPage)
  238. {
  239. $this->perPage = $perPage;
  240. return $this;
  241. }
  242. /**
  243. * @return string
  244. */
  245. public function getPageName()
  246. {
  247. return $this->grid->makeName($this->pageName);
  248. }
  249. /**
  250. * @param string $name
  251. * @return $this
  252. */
  253. public function setPageName($name)
  254. {
  255. $this->pageName = $name;
  256. return $this;
  257. }
  258. /**
  259. * Get the query string variable used to store the sort.
  260. *
  261. * @return string
  262. */
  263. public function getSortName()
  264. {
  265. return $this->grid->makeName($this->sortName);
  266. }
  267. /**
  268. * @param string $name
  269. * @return $this
  270. */
  271. public function setSortName($name)
  272. {
  273. $this->sortName = $name;
  274. return $this;
  275. }
  276. /**
  277. * Set parent grid instance.
  278. *
  279. * @param Grid $grid
  280. * @return $this
  281. */
  282. public function setGrid(Grid $grid)
  283. {
  284. $this->grid = $grid;
  285. return $this;
  286. }
  287. /**
  288. * Get parent gird instance.
  289. *
  290. * @return Grid
  291. */
  292. public function grid()
  293. {
  294. return $this->grid;
  295. }
  296. /**
  297. * Get filter of Grid.
  298. *
  299. * @return Filter
  300. */
  301. public function filter()
  302. {
  303. return $this->grid->filter();
  304. }
  305. /**
  306. * Get constraints.
  307. *
  308. * @return array|bool
  309. */
  310. public function getConstraints()
  311. {
  312. return $this->constraints;
  313. }
  314. /**
  315. * @param array $constraints
  316. * @return $this
  317. */
  318. public function setConstraints(array $constraints)
  319. {
  320. $this->constraints = $constraints;
  321. return $this;
  322. }
  323. /**
  324. * Build.
  325. *
  326. * @return Collection
  327. */
  328. public function buildData()
  329. {
  330. if (is_null($this->data)) {
  331. $this->setData($this->fetch());
  332. }
  333. return $this->data;
  334. }
  335. /**
  336. * @param Collection|callable|array|AbstractPaginator $data
  337. * @return $this
  338. */
  339. public function setData($data)
  340. {
  341. if (is_callable($data)) {
  342. $this->builder = $data;
  343. return $this;
  344. }
  345. if ($data instanceof AbstractPaginator) {
  346. $this->setPaginator($data);
  347. $data = $data->getCollection();
  348. } elseif ($data instanceof Collection) {
  349. } elseif ($data instanceof Arrayable || is_array($data)) {
  350. $data = collect($data);
  351. }
  352. if ($data instanceof Collection) {
  353. $this->data = $data;
  354. } else {
  355. $this->data = collect();
  356. }
  357. $this->stdObjToArray($this->data);
  358. return $this;
  359. }
  360. /**
  361. * Add conditions to grid model.
  362. *
  363. * @param array $conditions
  364. * @return $this
  365. */
  366. public function addConditions(array $conditions)
  367. {
  368. foreach ($conditions as $condition) {
  369. call_user_func_array([$this, key($condition)], current($condition));
  370. }
  371. return $this;
  372. }
  373. /**
  374. * @return Collection|array
  375. *
  376. * @throws \Exception
  377. */
  378. protected function fetch()
  379. {
  380. if ($this->paginator) {
  381. return $this->paginator->getCollection();
  382. }
  383. if ($this->builder && is_callable($this->builder)) {
  384. $results = call_user_func($this->builder, $this);
  385. } else {
  386. $results = $this->repository->get($this);
  387. }
  388. if (is_array($results) || $results instanceof Collection) {
  389. return $results;
  390. }
  391. if ($results instanceof AbstractPaginator) {
  392. $this->setPaginator($results);
  393. return $results->getCollection();
  394. }
  395. throw new AdminException('Grid query error');
  396. }
  397. /**
  398. * @param AbstractPaginator $paginator
  399. * @return void
  400. */
  401. protected function setPaginator(AbstractPaginator $paginator)
  402. {
  403. $this->paginator = $paginator;
  404. if ($this->simple) {
  405. if (method_exists($paginator, 'withQueryString')) {
  406. $paginator->withQueryString();
  407. } else {
  408. $paginator->appends(request()->all());
  409. }
  410. }
  411. $paginator->setPageName($this->getPageName());
  412. }
  413. /**
  414. * @param Collection $collection
  415. * @return Collection
  416. */
  417. protected function stdObjToArray(Collection $collection)
  418. {
  419. return $collection->transform(function ($item) {
  420. if ($item instanceof \stdClass) {
  421. return (array) $item;
  422. }
  423. return $item;
  424. });
  425. }
  426. /**
  427. * Get current page.
  428. *
  429. * @return int|null
  430. */
  431. public function getCurrentPage()
  432. {
  433. if (! $this->usePaginate) {
  434. return;
  435. }
  436. return $this->currentPage ?: ($this->currentPage = ($this->request->get($this->getPageName()) ?: 1));
  437. }
  438. /**
  439. * @param int $currentPage
  440. */
  441. public function setCurrentPage(int $currentPage)
  442. {
  443. $this->currentPage = $currentPage;
  444. return $this;
  445. }
  446. /**
  447. * Get items number of per page.
  448. *
  449. * @return int|null
  450. */
  451. public function getPerPage()
  452. {
  453. if (! $this->usePaginate) {
  454. return;
  455. }
  456. $perPage = $this->request->get($this->getPerPageName()) ?: $this->perPage;
  457. if ($perPage) {
  458. return (int) $perPage;
  459. }
  460. return null;
  461. }
  462. /**
  463. * Find query by method name.
  464. *
  465. * @param $method
  466. * @return Collection
  467. */
  468. public function findQueryByMethod($method)
  469. {
  470. return $this->queries->where('method', $method);
  471. }
  472. /**
  473. * @param string|callable $method
  474. * @return $this
  475. */
  476. public function filterQueryBy($method)
  477. {
  478. $this->queries = $this->queries->filter(function ($query, $k) use ($method) {
  479. if (
  480. (is_string($method) && $query['method'] === $method)
  481. || (is_array($method) && in_array($query['method'], $method, true))
  482. ) {
  483. return false;
  484. }
  485. if (is_callable($method)) {
  486. return call_user_func($method, $query, $k);
  487. }
  488. return true;
  489. });
  490. return $this;
  491. }
  492. /**
  493. * Get the grid sort.
  494. *
  495. * @return array exp: ['name', 'desc']
  496. */
  497. public function getSort()
  498. {
  499. if (empty($this->sort)) {
  500. $this->sort = $this->request->get($this->getSortName());
  501. }
  502. if (empty($this->sort['column']) || empty($this->sort['type'])) {
  503. return [null, null, null];
  504. }
  505. return [$this->sort['column'], $this->sort['type'], $this->sort['cast'] ?? null];
  506. }
  507. /**
  508. * @param string|array $method
  509. * @return void
  510. */
  511. public function rejectQuery($method)
  512. {
  513. $this->queries = $this->queries->reject(function ($query) use ($method) {
  514. if (is_callable($method)) {
  515. return call_user_func($method, $query);
  516. }
  517. return in_array($query['method'], (array) $method, true);
  518. });
  519. }
  520. /**
  521. * Reset orderBy query.
  522. *
  523. * @return void
  524. */
  525. public function resetOrderBy()
  526. {
  527. $this->rejectQuery(['orderBy', 'orderByDesc']);
  528. }
  529. /**
  530. * @param string $method
  531. * @param array $arguments
  532. * @return $this
  533. */
  534. public function __call($method, $arguments)
  535. {
  536. return $this->addQuery($method, $arguments);
  537. }
  538. /**
  539. * @param string $method
  540. * @param array $arguments
  541. * @return $this
  542. */
  543. public function addQuery(string $method, array $arguments = [])
  544. {
  545. $this->queries->push([
  546. 'method' => $method,
  547. 'arguments' => $arguments,
  548. ]);
  549. return $this;
  550. }
  551. public function getSortQueries()
  552. {
  553. return $this->findQueryByMethod('orderBy')
  554. ->merge($this->findQueryByMethod('orderByDesc'))
  555. ->merge($this->findQueryByMethod('latest'))
  556. ->merge($this->findQueryByMethod('oldest'));
  557. }
  558. public function getSortDescMethods()
  559. {
  560. return ['orderByDesc', 'latest'];
  561. }
  562. /**
  563. * @param Builder $query
  564. * @param bool $fetch
  565. * @param string[] $columns
  566. * @return Builder|Paginator|Collection
  567. */
  568. public function apply($query, bool $fetch = false, $columns = null)
  569. {
  570. $this->getQueries()->unique()->each(function ($value) use (&$query, $fetch, $columns) {
  571. if (! $fetch && in_array($value['method'], ['paginate', 'simplePaginate', 'get'], true)) {
  572. return;
  573. }
  574. if ($columns) {
  575. if (in_array($value['method'], ['paginate', 'simplePaginate'], true)) {
  576. $value['arguments'][1] = $columns;
  577. } elseif ($value['method'] === 'get') {
  578. $value['arguments'] = [$columns];
  579. }
  580. }
  581. $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
  582. });
  583. return $query;
  584. }
  585. /**
  586. * Set the relationships that should be eager loaded.
  587. *
  588. * @param mixed $relations
  589. * @return $this|Model
  590. */
  591. public function with($relations)
  592. {
  593. if (is_array($relations)) {
  594. if (Arr::isAssoc($relations)) {
  595. $relations = array_keys($relations);
  596. }
  597. $this->eagerLoads = array_merge($this->eagerLoads, $relations);
  598. }
  599. if (is_string($relations)) {
  600. if (Str::contains($relations, '.')) {
  601. $relations = explode('.', $relations)[0];
  602. }
  603. if (Str::contains($relations, ':')) {
  604. $relations = explode(':', $relations)[0];
  605. }
  606. if (in_array($relations, $this->eagerLoads)) {
  607. return $this;
  608. }
  609. $this->eagerLoads[] = $relations;
  610. }
  611. return $this->addQuery('with', (array) $relations);
  612. }
  613. /**
  614. * @return void
  615. */
  616. public function reset()
  617. {
  618. $this->data = null;
  619. $this->model = null;
  620. $this->initQueries();
  621. }
  622. }