PaginatorComponent.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php
  2. declare(strict_types = 1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 2.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Controller\Component;
  17. use Cake\Controller\Component;
  18. use Cake\Controller\ComponentRegistry;
  19. use Cake\Datasource\Exception\PageOutOfBoundsException;
  20. use Cake\Datasource\Paginator;
  21. use Cake\Datasource\ResultSetInterface;
  22. use Cake\Http\Exception\NotFoundException;
  23. use InvalidArgumentException;
  24. /**
  25. * This component is used to handle automatic model data pagination. The primary way to use this
  26. * component is to call the paginate() method. There is a convenience wrapper on Controller as well.
  27. *
  28. * ### Configuring pagination
  29. *
  30. * You configure pagination when calling paginate(). See that method for more details.
  31. *
  32. * @link https://book.cakephp.org/3.0/en/controllers/components/pagination.html
  33. */
  34. class PaginatorComponent extends Component
  35. {
  36. /**
  37. * Default pagination settings.
  38. *
  39. * When calling paginate() these settings will be merged with the configuration
  40. * you provide.
  41. *
  42. * - `maxLimit` - The maximum limit users can choose to view. Defaults to 100
  43. * - `limit` - The initial number of items per page. Defaults to 20.
  44. * - `page` - The starting page, defaults to 1.
  45. * - `whitelist` - A list of parameters users are allowed to set using request
  46. * parameters. Modifying this list will allow users to have more influence
  47. * over pagination, be careful with what you permit.
  48. *
  49. * @var array
  50. */
  51. protected $_defaultConfig = [
  52. 'page' => 1,
  53. 'limit' => 20,
  54. 'maxLimit' => 100,
  55. 'whitelist' => ['limit', 'sort', 'page', 'direction']
  56. ];
  57. /**
  58. * Datasource paginator instance.
  59. *
  60. * @var \Cake\Datasource\Paginator
  61. */
  62. protected $_paginator;
  63. /**
  64. * {@inheritDoc}
  65. */
  66. public function __construct(ComponentRegistry $registry, array $config = [])
  67. {
  68. if (isset($config['paginator'])) {
  69. if (!$config['paginator'] instanceof Paginator) {
  70. throw new InvalidArgumentException('Paginator must be an instance of ' . Paginator::class);
  71. }
  72. $this->_paginator = $config['paginator'];
  73. unset($config['paginator']);
  74. } else {
  75. $this->_paginator = new Paginator();
  76. }
  77. parent::__construct($registry, $config);
  78. }
  79. /**
  80. * Events supported by this component.
  81. *
  82. * @return array
  83. */
  84. public function implementedEvents(): array
  85. {
  86. return [];
  87. }
  88. /**
  89. * Handles automatic pagination of model records.
  90. *
  91. * ### Configuring pagination
  92. *
  93. * When calling `paginate()` you can use the $settings parameter to pass in pagination settings.
  94. * These settings are used to build the queries made and control other pagination settings.
  95. *
  96. * If your settings contain a key with the current table's alias. The data inside that key will be used.
  97. * Otherwise the top level configuration will be used.
  98. *
  99. * ```
  100. * $settings = [
  101. * 'limit' => 20,
  102. * 'maxLimit' => 100
  103. * ];
  104. * $results = $paginator->paginate($table, $settings);
  105. * ```
  106. *
  107. * The above settings will be used to paginate any Table. You can configure Table specific settings by
  108. * keying the settings with the Table alias.
  109. *
  110. * ```
  111. * $settings = [
  112. * 'Articles' => [
  113. * 'limit' => 20,
  114. * 'maxLimit' => 100
  115. * ],
  116. * 'Comments' => [ ... ]
  117. * ];
  118. * $results = $paginator->paginate($table, $settings);
  119. * ```
  120. *
  121. * This would allow you to have different pagination settings for `Articles` and `Comments` tables.
  122. *
  123. * ### Controlling sort fields
  124. *
  125. * By default CakePHP will automatically allow sorting on any column on the table object being
  126. * paginated. Often times you will want to allow sorting on either associated columns or calculated
  127. * fields. In these cases you will need to define a whitelist of all the columns you wish to allow
  128. * sorting on. You can define the whitelist in the `$settings` parameter:
  129. *
  130. * ```
  131. * $settings = [
  132. * 'Articles' => [
  133. * 'finder' => 'custom',
  134. * 'sortWhitelist' => ['title', 'author_id', 'comment_count'],
  135. * ]
  136. * ];
  137. * ```
  138. *
  139. * Passing an empty array as whitelist disallows sorting altogether.
  140. *
  141. * ### Paginating with custom finders
  142. *
  143. * You can paginate with any find type defined on your table using the `finder` option.
  144. *
  145. * ```
  146. * $settings = [
  147. * 'Articles' => [
  148. * 'finder' => 'popular'
  149. * ]
  150. * ];
  151. * $results = $paginator->paginate($table, $settings);
  152. * ```
  153. *
  154. * Would paginate using the `find('popular')` method.
  155. *
  156. * You can also pass an already created instance of a query to this method:
  157. *
  158. * ```
  159. * $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
  160. * return $q->where(['name' => 'CakePHP'])
  161. * });
  162. * $results = $paginator->paginate($query);
  163. * ```
  164. *
  165. * ### Scoping Request parameters
  166. *
  167. * By using request parameter scopes you can paginate multiple queries in the same controller action:
  168. *
  169. * ```
  170. * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
  171. * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
  172. * ```
  173. *
  174. * Each of the above queries will use different query string parameter sets
  175. * for pagination data. An example URL paginating both results would be:
  176. *
  177. * ```
  178. * /dashboard?articles[page]=1&tags[page]=2
  179. * ```
  180. *
  181. * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate.
  182. * @param array $settings The settings/configuration used for pagination.
  183. * @return \Cake\Datasource\ResultSetInterface Query results
  184. * @throws \Cake\Http\Exception\NotFoundException
  185. */
  186. public function paginate($object, array $settings = []): ResultSetInterface
  187. {
  188. $request = $this->_registry->getController()->getRequest();
  189. try {
  190. $results = $this->_paginator->paginate(
  191. $object,
  192. $request->getQueryParams(),
  193. $settings
  194. );
  195. $this->_setPagingParams();
  196. } catch (PageOutOfBoundsException $e) {
  197. $this->_setPagingParams();
  198. throw new NotFoundException(null, null, $e);
  199. }
  200. return $results;
  201. }
  202. /**
  203. * Merges the various options that Pagination uses.
  204. * Pulls settings together from the following places:
  205. *
  206. * - General pagination settings
  207. * - Model specific settings.
  208. * - Request parameters
  209. *
  210. * The result of this method is the aggregate of all the option sets combined together. You can change
  211. * config value `whitelist` to modify which options/values can be set using request parameters.
  212. *
  213. * @param string $alias Model alias being paginated, if the general settings has a key with this value
  214. * that key's settings will be used for pagination instead of the general ones.
  215. * @param array $settings The settings to merge with the request data.
  216. * @return array Array of merged options.
  217. */
  218. public function mergeOptions(string $alias, array $settings): array
  219. {
  220. $request = $this->_registry->getController()->getRequest();
  221. return $this->_paginator->mergeOptions(
  222. $request->getQueryParams(),
  223. $this->_paginator->getDefaults($alias, $settings)
  224. );
  225. }
  226. /**
  227. * Set paginator instance.
  228. *
  229. * @param \Cake\Datasource\Paginator $paginator Paginator instance.
  230. * @return self
  231. */
  232. public function setPaginator(Paginator $paginator): self
  233. {
  234. $this->_paginator = $paginator;
  235. return $this;
  236. }
  237. /**
  238. * Get paginator instance.
  239. *
  240. * @return \Cake\Datasource\Paginator
  241. */
  242. public function getPaginator(): Paginator
  243. {
  244. return $this->_paginator;
  245. }
  246. /**
  247. * Set paging params to request instance.
  248. *
  249. * @return void
  250. */
  251. protected function _setPagingParams(): void
  252. {
  253. $controller = $this->getController();
  254. $request = $controller->getRequest();
  255. $paging = $this->_paginator->getPagingParams() + (array)$request->getParam('paging', []);
  256. $controller->setRequest($request->withParam('paging', $paging));
  257. }
  258. /**
  259. * Proxy setting config options to Paginator.
  260. *
  261. * @param string|array $key The key to set, or a complete array of configs.
  262. * @param mixed|null $value The value to set.
  263. * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
  264. * @return $this
  265. */
  266. public function setConfig($key, $value = null, $merge = true)
  267. {
  268. $this->_paginator->setConfig($key, $value, $merge);
  269. return $this;
  270. }
  271. /**
  272. * Proxy getting config options to Paginator.
  273. *
  274. * @param string|null $key The key to get or null for the whole config.
  275. * @param mixed $default The return value when the key does not exist.
  276. * @return mixed Config value being read.
  277. */
  278. public function getConfig(?string $key = null, $default = null)
  279. {
  280. return $this->_paginator->getConfig($key, $default);
  281. }
  282. /**
  283. * Proxy setting config options to Paginator.
  284. *
  285. * @param string|array $key The key to set, or a complete array of configs.
  286. * @param mixed|null $value The value to set.
  287. * @return $this
  288. */
  289. public function configShallow($key, $value = null)
  290. {
  291. $this->_paginator->configShallow($key, $value = null);
  292. return $this;
  293. }
  294. /**
  295. * Proxy method calls to Paginator.
  296. *
  297. * @param string $method Method name.
  298. * @param array $args Method arguments.
  299. * @return mixed
  300. */
  301. public function __call(string $method, array $args)
  302. {
  303. return call_user_func_array([$this->_paginator, $method], $args);
  304. }
  305. }