Model.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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. *
  145. * @return void
  146. */
  147. public function setQueries(Collection $query)
  148. {
  149. $this->queries = $query;
  150. }
  151. /**
  152. * @return AbstractPaginator|LengthAwarePaginator
  153. */
  154. public function paginator(): ?AbstractPaginator
  155. {
  156. $this->buildData();
  157. return $this->paginator;
  158. }
  159. /**
  160. * 是否使用 simplePaginate方法进行分页.
  161. *
  162. * @param bool $value
  163. *
  164. * @return $this
  165. */
  166. public function simple(bool $value = true)
  167. {
  168. $this->simple = $value;
  169. return $this;
  170. }
  171. /**
  172. * @return string
  173. */
  174. public function getPaginateMethod()
  175. {
  176. return $this->simple ? 'simplePaginate' : 'paginate';
  177. }
  178. /**
  179. * @param int $total
  180. * @param Collection|array $data
  181. *
  182. * @return LengthAwarePaginator|Paginator
  183. */
  184. public function makePaginator($total, $data, string $url = null)
  185. {
  186. if ($this->simple) {
  187. $paginator = new Paginator($data, $this->getPerPage(), $this->getCurrentPage());
  188. } else {
  189. $paginator = new LengthAwarePaginator(
  190. $data,
  191. $total,
  192. $this->getPerPage(), // 传入每页显示行数
  193. $this->getCurrentPage() // 传入当前页码
  194. );
  195. }
  196. return $paginator->setPath(
  197. $url ?: url()->current()
  198. );
  199. }
  200. /**
  201. * Get primary key name of model.
  202. *
  203. * @return string|array
  204. */
  205. public function getKeyName()
  206. {
  207. return $this->grid->getKeyName();
  208. }
  209. /**
  210. * Enable or disable pagination.
  211. *
  212. * @param bool $use
  213. *
  214. * @reutrn $this;
  215. */
  216. public function usePaginate($use = true)
  217. {
  218. $this->usePaginate = $use;
  219. return $this;
  220. }
  221. /**
  222. * @return bool
  223. */
  224. public function allowPagination()
  225. {
  226. return $this->usePaginate;
  227. }
  228. /**
  229. * Get the query string variable used to store the per-page.
  230. *
  231. * @return string
  232. */
  233. public function getPerPageName()
  234. {
  235. return $this->grid->makeName($this->perPageName);
  236. }
  237. /**
  238. * @param int $perPage
  239. */
  240. public function setPerPage(int $perPage)
  241. {
  242. $this->perPage = $perPage;
  243. return $this;
  244. }
  245. /**
  246. * @return string
  247. */
  248. public function getPageName()
  249. {
  250. return $this->grid->makeName($this->pageName);
  251. }
  252. /**
  253. * @param string $name
  254. *
  255. * @return $this
  256. */
  257. public function setPageName($name)
  258. {
  259. $this->pageName = $name;
  260. return $this;
  261. }
  262. /**
  263. * Get the query string variable used to store the sort.
  264. *
  265. * @return string
  266. */
  267. public function getSortName()
  268. {
  269. return $this->grid->makeName($this->sortName);
  270. }
  271. /**
  272. * @param string $name
  273. *
  274. * @return $this
  275. */
  276. public function setSortName($name)
  277. {
  278. $this->sortName = $name;
  279. return $this;
  280. }
  281. /**
  282. * Set parent grid instance.
  283. *
  284. * @param Grid $grid
  285. *
  286. * @return $this
  287. */
  288. public function setGrid(Grid $grid)
  289. {
  290. $this->grid = $grid;
  291. return $this;
  292. }
  293. /**
  294. * Get parent gird instance.
  295. *
  296. * @return Grid
  297. */
  298. public function grid()
  299. {
  300. return $this->grid;
  301. }
  302. /**
  303. * Get filter of Grid.
  304. *
  305. * @return Filter
  306. */
  307. public function filter()
  308. {
  309. return $this->grid->filter();
  310. }
  311. /**
  312. * Get constraints.
  313. *
  314. * @return array|bool
  315. */
  316. public function getConstraints()
  317. {
  318. return $this->constraints;
  319. }
  320. /**
  321. * @param array $constraints
  322. *
  323. * @return $this
  324. */
  325. public function setConstraints(array $constraints)
  326. {
  327. $this->constraints = $constraints;
  328. return $this;
  329. }
  330. /**
  331. * Build.
  332. *
  333. * @return Collection
  334. */
  335. public function buildData()
  336. {
  337. if (is_null($this->data)) {
  338. $this->setData($this->fetch());
  339. }
  340. return $this->data;
  341. }
  342. /**
  343. * @param Collection|callable|array|AbstractPaginator $data
  344. *
  345. * @return $this
  346. */
  347. public function setData($data)
  348. {
  349. if (is_callable($data)) {
  350. $this->builder = $data;
  351. return $this;
  352. }
  353. if ($data instanceof AbstractPaginator) {
  354. $this->setPaginator($data);
  355. $data = $data->getCollection();
  356. } elseif ($data instanceof Collection) {
  357. } elseif ($data instanceof Arrayable || is_array($data)) {
  358. $data = collect($data);
  359. }
  360. if ($data instanceof Collection) {
  361. $this->data = $data;
  362. } else {
  363. $this->data = collect();
  364. }
  365. $this->stdObjToArray($this->data);
  366. return $this;
  367. }
  368. /**
  369. * Add conditions to grid model.
  370. *
  371. * @param array $conditions
  372. *
  373. * @return $this
  374. */
  375. public function addConditions(array $conditions)
  376. {
  377. foreach ($conditions as $condition) {
  378. call_user_func_array([$this, key($condition)], current($condition));
  379. }
  380. return $this;
  381. }
  382. /**
  383. * @throws \Exception
  384. *
  385. * @return Collection|array
  386. */
  387. protected function fetch()
  388. {
  389. if ($this->paginator) {
  390. return $this->paginator->getCollection();
  391. }
  392. if ($this->builder && is_callable($this->builder)) {
  393. $results = call_user_func($this->builder, $this);
  394. } else {
  395. $results = $this->repository->get($this);
  396. }
  397. if (is_array($results) || $results instanceof Collection) {
  398. return $results;
  399. }
  400. if ($results instanceof AbstractPaginator) {
  401. $this->setPaginator($results);
  402. return $results->getCollection();
  403. }
  404. throw new AdminException('Grid query error');
  405. }
  406. /**
  407. * @param AbstractPaginator $paginator
  408. *
  409. * @return void
  410. */
  411. protected function setPaginator(AbstractPaginator $paginator)
  412. {
  413. $this->paginator = $paginator;
  414. $paginator->setPageName($this->getPageName());
  415. }
  416. /**
  417. * @param Collection $collection
  418. *
  419. * @return Collection
  420. */
  421. protected function stdObjToArray(Collection $collection)
  422. {
  423. return $collection->transform(function ($item) {
  424. if ($item instanceof \stdClass) {
  425. return (array) $item;
  426. }
  427. return $item;
  428. });
  429. }
  430. /**
  431. * Get current page.
  432. *
  433. * @return int|null
  434. */
  435. public function getCurrentPage()
  436. {
  437. if (! $this->usePaginate) {
  438. return;
  439. }
  440. return $this->currentPage ?: ($this->currentPage = ($this->request->get($this->getPageName()) ?: 1));
  441. }
  442. /**
  443. * @param int $currentPage
  444. */
  445. public function setCurrentPage(int $currentPage)
  446. {
  447. $this->currentPage = $currentPage;
  448. return $this;
  449. }
  450. /**
  451. * Get items number of per page.
  452. *
  453. * @return int|null
  454. */
  455. public function getPerPage()
  456. {
  457. if (! $this->usePaginate) {
  458. return;
  459. }
  460. return $this->request->get($this->getPerPageName()) ?: $this->perPage;
  461. }
  462. /**
  463. * Find query by method name.
  464. *
  465. * @param $method
  466. *
  467. * @return Collection
  468. */
  469. public function findQueryByMethod($method)
  470. {
  471. return $this->queries->where('method', $method);
  472. }
  473. /**
  474. * @param string|callable $method
  475. *
  476. * @return $this
  477. */
  478. public function filterQueryBy($method)
  479. {
  480. $this->queries = $this->queries->filter(function ($query, $k) use ($method) {
  481. if (
  482. (is_string($method) && $query['method'] === $method)
  483. || (is_array($method) && in_array($query['method'], $method, true))
  484. ) {
  485. return false;
  486. }
  487. if (is_callable($method)) {
  488. return call_user_func($method, $query, $k);
  489. }
  490. return true;
  491. });
  492. return $this;
  493. }
  494. /**
  495. * Get the grid sort.
  496. *
  497. * @return array exp: ['name', 'desc']
  498. */
  499. public function getSort()
  500. {
  501. if (empty($this->sort)) {
  502. $this->sort = $this->request->get($this->getSortName());
  503. }
  504. if (empty($this->sort['column']) || empty($this->sort['type'])) {
  505. return [null, null, null];
  506. }
  507. return [$this->sort['column'], $this->sort['type'], $this->sort['cast'] ?? null];
  508. }
  509. /**
  510. * @param string|array $method
  511. *
  512. * @return void
  513. */
  514. public function rejectQuery($method)
  515. {
  516. $this->queries = $this->queries->reject(function ($query) use ($method) {
  517. if (is_callable($method)) {
  518. return call_user_func($method, $query);
  519. }
  520. return in_array($query['method'], (array) $method, true);
  521. });
  522. }
  523. /**
  524. * Reset orderBy query.
  525. *
  526. * @return void
  527. */
  528. public function resetOrderBy()
  529. {
  530. $this->rejectQuery(['orderBy', 'orderByDesc']);
  531. }
  532. /**
  533. * @param string $method
  534. * @param array $arguments
  535. *
  536. * @return $this
  537. */
  538. public function __call($method, $arguments)
  539. {
  540. return $this->addQuery($method, $arguments);
  541. }
  542. /**
  543. * @param string $method
  544. * @param array $arguments
  545. *
  546. * @return $this
  547. */
  548. public function addQuery(string $method, array $arguments = [])
  549. {
  550. $this->queries->push([
  551. 'method' => $method,
  552. 'arguments' => $arguments,
  553. ]);
  554. return $this;
  555. }
  556. public function getSortQueries()
  557. {
  558. return $this->findQueryByMethod('orderBy')
  559. ->merge($this->findQueryByMethod('orderByDesc'))
  560. ->merge($this->findQueryByMethod('latest'))
  561. ->merge($this->findQueryByMethod('oldest'));
  562. }
  563. public function getSortDescMethods()
  564. {
  565. return ['orderByDesc', 'latest'];
  566. }
  567. /**
  568. * Set the relationships that should be eager loaded.
  569. *
  570. * @param mixed $relations
  571. *
  572. * @return $this|Model
  573. */
  574. public function with($relations)
  575. {
  576. if (is_array($relations)) {
  577. if (Arr::isAssoc($relations)) {
  578. $relations = array_keys($relations);
  579. }
  580. $this->eagerLoads = array_merge($this->eagerLoads, $relations);
  581. }
  582. if (is_string($relations)) {
  583. if (Str::contains($relations, '.')) {
  584. $relations = explode('.', $relations)[0];
  585. }
  586. if (Str::contains($relations, ':')) {
  587. $relations = explode(':', $relations)[0];
  588. }
  589. if (in_array($relations, $this->eagerLoads)) {
  590. return $this;
  591. }
  592. $this->eagerLoads[] = $relations;
  593. }
  594. return $this->addQuery('with', (array) $relations);
  595. }
  596. /**
  597. * @return void
  598. */
  599. public function reset()
  600. {
  601. $this->data = null;
  602. $this->model = null;
  603. $this->initQueries();
  604. }
  605. }