AbstractPaginator.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Paginator;
  12. use ArrayAccess;
  13. use ArrayIterator;
  14. use Closure;
  15. use Hyperf\Collection\Arr;
  16. use Hyperf\Collection\Collection;
  17. use Hyperf\Contract\PaginatorInterface;
  18. use Hyperf\Stringable\Str;
  19. use Hyperf\Support\Traits\ForwardsCalls;
  20. use Stringable;
  21. abstract class AbstractPaginator implements PaginatorInterface, ArrayAccess, Stringable
  22. {
  23. use ForwardsCalls;
  24. /**
  25. * The number of links to display on each side of current page link.
  26. */
  27. public int $onEachSide = 3;
  28. /**
  29. * The paginator options.
  30. */
  31. protected array $options = [];
  32. /**
  33. * All the items being paginated.
  34. */
  35. protected Collection $items;
  36. /**
  37. * The number of items to be shown per page.
  38. */
  39. protected int $perPage;
  40. /**
  41. * The current page being "viewed".
  42. */
  43. protected int $currentPage;
  44. /**
  45. * The base path to assign to all URLs.
  46. */
  47. protected string $path = '/';
  48. /**
  49. * The query parameters to add to all URLs.
  50. */
  51. protected array $query = [];
  52. /**
  53. * The URL fragment to add to all URLs.
  54. */
  55. protected ?string $fragment = null;
  56. /**
  57. * The query string variable used to store the page.
  58. */
  59. protected string $pageName = 'page';
  60. /**
  61. * The current path resolver callback.
  62. */
  63. protected static ?Closure $currentPathResolver = null;
  64. /**
  65. * The current page resolver callback.
  66. */
  67. protected static ?Closure $currentPageResolver = null;
  68. /**
  69. * The query string resolver callback.
  70. */
  71. protected static ?Closure $queryStringResolver = null;
  72. /**
  73. * Make dynamic calls into the collection.
  74. */
  75. public function __call(string $method, array $parameters)
  76. {
  77. return $this->forwardCallTo($this->getCollection(), $method, $parameters);
  78. }
  79. /**
  80. * Render the contents of the paginator when casting to string.
  81. */
  82. public function __toString(): string
  83. {
  84. return $this->render();
  85. }
  86. /**
  87. * Get the URL for the previous page.
  88. */
  89. public function previousPageUrl(): ?string
  90. {
  91. if ($this->currentPage() > 1) {
  92. return $this->url($this->currentPage() - 1);
  93. }
  94. return null;
  95. }
  96. /**
  97. * Create a range of pagination URLs.
  98. */
  99. public function getUrlRange(int $start, int $end): array
  100. {
  101. return Collection::range($start, $end)
  102. ->mapWithKeys(fn ($page) => [$page => $this->url($page)])
  103. ->all();
  104. }
  105. /**
  106. * Get the URL for a given page number.
  107. */
  108. public function url(int $page): string
  109. {
  110. if ($page <= 0) {
  111. $page = 1;
  112. }
  113. // If we have any extra query string key / value pairs that need to be added
  114. // onto the URL, we will put them in query string form and then attach it
  115. // to the URL. This allows for extra information like sortings storage.
  116. $parameters = [$this->pageName => $page];
  117. if (count($this->query) > 0) {
  118. $parameters = array_merge($this->query, $parameters);
  119. }
  120. return $this->path . (Str::contains($this->path, '?') ? '&' : '?') . Arr::query($parameters) . $this->buildFragment();
  121. }
  122. /**
  123. * Get / set the URL fragment to be appended to URLs.
  124. *
  125. * @return null|$this|string
  126. */
  127. public function fragment(?string $fragment = null)
  128. {
  129. if (is_null($fragment)) {
  130. return $this->fragment;
  131. }
  132. $this->fragment = $fragment;
  133. return $this;
  134. }
  135. /**
  136. * Add a set of query string values to the paginator.
  137. *
  138. * @param null|array|string $key
  139. */
  140. public function appends($key, null|array|string $value = null): static
  141. {
  142. if (is_null($key)) {
  143. return $this;
  144. }
  145. if (is_array($key)) {
  146. return $this->appendArray($key);
  147. }
  148. return $this->addQuery($key, $value);
  149. }
  150. /**
  151. * Load a set of relationships onto the mixed relationship collection.
  152. */
  153. public function loadMorph(string $relation, array $relations): static
  154. {
  155. $collection = $this->getCollection();
  156. if (method_exists($collection, 'loadMorph')) {
  157. $collection->loadMorph($relation, $relations);
  158. }
  159. return $this;
  160. }
  161. /**
  162. * Get the slice of items being paginated.
  163. */
  164. public function items(): array
  165. {
  166. return $this->items->all();
  167. }
  168. /**
  169. * Get the number of the first item in the slice.
  170. */
  171. public function firstItem(): ?int
  172. {
  173. return count($this->items) > 0 ? ($this->currentPage - 1) * $this->perPage + 1 : null;
  174. }
  175. /**
  176. * Get the number of the last item in the slice.
  177. */
  178. public function lastItem(): ?int
  179. {
  180. return count($this->items) > 0 ? $this->firstItem() + $this->count() - 1 : null;
  181. }
  182. /**
  183. * Get the number of items shown per page.
  184. */
  185. public function perPage(): int
  186. {
  187. return $this->perPage;
  188. }
  189. /**
  190. * Determine if there are enough items to split into multiple pages.
  191. */
  192. public function hasPages(): bool
  193. {
  194. return $this->currentPage() != 1 || $this->hasMorePages();
  195. }
  196. /**
  197. * Determine if the paginator is on the first page.
  198. */
  199. public function onFirstPage(): bool
  200. {
  201. return $this->currentPage() <= 1;
  202. }
  203. /**
  204. * Get the current page.
  205. */
  206. public function currentPage(): int
  207. {
  208. return $this->currentPage;
  209. }
  210. /**
  211. * Get the query string variable used to store the page.
  212. */
  213. public function getPageName(): string
  214. {
  215. return $this->pageName;
  216. }
  217. /**
  218. * Set the query string variable used to store the page.
  219. */
  220. public function setPageName(string $name): static
  221. {
  222. $this->pageName = $name;
  223. return $this;
  224. }
  225. /**
  226. * Set the base path to assign to all URLs.
  227. */
  228. public function withPath(string $path): static
  229. {
  230. return $this->setPath($path);
  231. }
  232. /**
  233. * Set the base path to assign to all URLs.
  234. */
  235. public function setPath(string $path): static
  236. {
  237. $this->path = $path;
  238. return $this;
  239. }
  240. /**
  241. * Set the number of links to display on each side of current page link.
  242. */
  243. public function onEachSide(int $count): static
  244. {
  245. $this->onEachSide = $count;
  246. return $this;
  247. }
  248. /**
  249. * Resolve the current request path or return the default value.
  250. */
  251. public static function resolveCurrentPath(string $default = '/'): string
  252. {
  253. if (isset(static::$currentPathResolver)) {
  254. return call_user_func(static::$currentPathResolver);
  255. }
  256. return $default;
  257. }
  258. /**
  259. * Set the current request path resolver callback.
  260. */
  261. public static function currentPathResolver(Closure $resolver): void
  262. {
  263. static::$currentPathResolver = $resolver;
  264. }
  265. /**
  266. * Resolve the current page or return the default value.
  267. */
  268. public static function resolveCurrentPage(string $pageName = 'page', int $default = 1): int
  269. {
  270. if (isset(static::$currentPageResolver)) {
  271. return call_user_func(static::$currentPageResolver, $pageName);
  272. }
  273. return $default;
  274. }
  275. /**
  276. * Set the current page resolver callback.
  277. */
  278. public static function currentPageResolver(Closure $resolver): void
  279. {
  280. static::$currentPageResolver = $resolver;
  281. }
  282. /**
  283. * Get an iterator for the items.
  284. */
  285. public function getIterator(): ArrayIterator
  286. {
  287. return $this->items->getIterator();
  288. }
  289. /**
  290. * Determine if the list of items is empty.
  291. */
  292. public function isEmpty(): bool
  293. {
  294. return $this->items->isEmpty();
  295. }
  296. /**
  297. * Determine if the list of items is not empty.
  298. */
  299. public function isNotEmpty(): bool
  300. {
  301. return $this->items->isNotEmpty();
  302. }
  303. /**
  304. * Get the number of items for the current page.
  305. */
  306. public function count(): int
  307. {
  308. return $this->items->count();
  309. }
  310. /**
  311. * Get the paginator's underlying collection.
  312. */
  313. public function getCollection(): Collection
  314. {
  315. return $this->items;
  316. }
  317. /**
  318. * Set the paginator's underlying collection.
  319. */
  320. public function setCollection(Collection $collection): static
  321. {
  322. $this->items = $collection;
  323. return $this;
  324. }
  325. /**
  326. * Get the paginator options.
  327. */
  328. public function getOptions(): array
  329. {
  330. return $this->options;
  331. }
  332. public function offsetExists(mixed $offset): bool
  333. {
  334. return $this->items->has($offset);
  335. }
  336. public function offsetGet(mixed $offset): mixed
  337. {
  338. return $this->items->get($offset);
  339. }
  340. public function offsetSet(mixed $offset, mixed $value): void
  341. {
  342. $this->items->put($offset, $value);
  343. }
  344. /**
  345. * Unset the item at the given key.
  346. */
  347. public function offsetUnset(mixed $offset): void
  348. {
  349. $this->items->forget($offset);
  350. }
  351. /**
  352. * Add all current query string values to the paginator.
  353. */
  354. public function withQueryString(): static
  355. {
  356. if (isset(static::$queryStringResolver)) {
  357. return $this->appends(call_user_func(static::$queryStringResolver));
  358. }
  359. return $this;
  360. }
  361. /**
  362. * Resolve the query string or return the default value.
  363. */
  364. public static function resolveQueryString(null|array|string $default = null): string
  365. {
  366. if (isset(static::$queryStringResolver)) {
  367. return (static::$queryStringResolver)();
  368. }
  369. return $default;
  370. }
  371. /**
  372. * Set with query string resolver callback.
  373. */
  374. public static function queryStringResolver(Closure $resolver): void
  375. {
  376. static::$queryStringResolver = $resolver;
  377. }
  378. /**
  379. * Determine if the given value is a valid page number.
  380. */
  381. protected function isValidPageNumber(int $page): bool
  382. {
  383. return $page >= 1 && filter_var($page, FILTER_VALIDATE_INT) !== false;
  384. }
  385. /**
  386. * Add an array of query string values.
  387. */
  388. protected function appendArray(array $keys): static
  389. {
  390. foreach ($keys as $key => $value) {
  391. $this->addQuery($key, $value);
  392. }
  393. return $this;
  394. }
  395. /**
  396. * Add a query string value to the paginator.
  397. */
  398. protected function addQuery(string $key, array|string $value): static
  399. {
  400. if ($key !== $this->pageName) {
  401. $this->query[$key] = $value;
  402. }
  403. return $this;
  404. }
  405. /**
  406. * Build the full fragment portion of a URL.
  407. */
  408. protected function buildFragment(): string
  409. {
  410. return $this->fragment ? '#' . $this->fragment : '';
  411. }
  412. }