1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace EngineWorks\DBAL; |
6
|
|
|
|
7
|
|
|
use EngineWorks\DBAL\Exceptions\QueryException; |
8
|
|
|
use InvalidArgumentException; |
9
|
|
|
use LogicException; |
10
|
|
|
use RuntimeException; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Pagination |
14
|
|
|
* @package EngineWorks\DBAL |
15
|
|
|
*/ |
16
|
|
|
class Pager |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* This count method is used when another query to retrieve the total records is provided |
20
|
|
|
*/ |
21
|
|
|
public const COUNT_METHOD_QUERY = 0; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* This count method is used to create a select count(*) with the data query as subquery |
25
|
|
|
*/ |
26
|
|
|
public const COUNT_METHOD_SELECT = 1; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* This count method is used to retrieve the total records by |
30
|
|
|
*/ |
31
|
|
|
public const COUNT_METHOD_RECORDCOUNT = 2; |
32
|
|
|
|
33
|
|
|
/** @var DBAL */ |
34
|
|
|
private $dbal; |
35
|
|
|
|
36
|
|
|
/** @var Recordset|null */ |
37
|
|
|
private $recordset; |
38
|
|
|
|
39
|
|
|
/** @var string SQL to query the data */ |
40
|
|
|
private $queryData; |
41
|
|
|
|
42
|
|
|
/** @var string SQL to query the count */ |
43
|
|
|
private $queryCount; |
44
|
|
|
|
45
|
|
|
/** @var int */ |
46
|
|
|
private $pageSize; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* One of COUNT_METHOD_QUERY, COUNT_METHOD_SELECT, COUNT_METHOD_RECORDCOUNT |
50
|
|
|
* This is set when the object is created, |
51
|
|
|
* Using its setter for COUNT_METHOD_SELECT, COUNT_METHOD_RECORDCOUNT |
52
|
|
|
* Using setQueryCount for COUNT_METHOD_QUERY |
53
|
|
|
* |
54
|
|
|
* @var int |
55
|
|
|
*/ |
56
|
|
|
private $countMethod; |
57
|
|
|
|
58
|
|
|
/** @var int number of the current page */ |
59
|
|
|
private $page; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* If NULL then the value needs to be read from database |
63
|
|
|
* |
64
|
|
|
* @var int|null |
65
|
|
|
*/ |
66
|
|
|
private $totalRecords = null; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Instantiate a pager object |
70
|
|
|
* If the queryCount is not set then it will set the method COUNT_METHOD_SELECT |
71
|
|
|
* that will query a count(*) using $queryData as a subquery |
72
|
|
|
* |
73
|
|
|
* @param DBAL $dbal |
74
|
|
|
* @param string $queryData The sql sentence to retrieve the data, do not use any LIMIT here |
75
|
|
|
* @param string $queryCount The sql sentence to retrieve the count of the data |
76
|
|
|
* @param int $pageSize The page size |
77
|
|
|
*/ |
78
|
9 |
|
public function __construct(DBAL $dbal, string $queryData, string $queryCount = '', int $pageSize = 20) |
79
|
|
|
{ |
80
|
9 |
|
$this->dbal = $dbal; |
81
|
9 |
|
$this->queryData = $queryData; |
82
|
9 |
|
if ('' !== $queryCount) { |
83
|
|
|
// this method also calls $this->setCountMethod |
84
|
1 |
|
$this->setQueryCount($queryCount); |
85
|
|
|
} else { |
86
|
|
|
// set a non-null value, otherwise setCountMethod will fail |
87
|
8 |
|
$this->countMethod = -1; |
88
|
8 |
|
$this->setCountMethod(self::COUNT_METHOD_SELECT); |
89
|
|
|
} |
90
|
9 |
|
$this->setPageSize($pageSize); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* perform the query to get a limited result |
95
|
|
|
* @param int $requestedPage |
96
|
|
|
* @return bool |
97
|
|
|
*/ |
98
|
7 |
|
public function queryPage(int $requestedPage): bool |
99
|
|
|
{ |
100
|
|
|
// clear |
101
|
7 |
|
$this->page = 0; |
102
|
7 |
|
$this->totalRecords = null; |
103
|
7 |
|
$this->recordset = null; |
104
|
|
|
// request |
105
|
7 |
|
$page = min($this->getTotalPages(), max(1, $requestedPage)); |
106
|
3 |
|
$query = $this->dbal->sqlLimit($this->getQueryData(), $page, $this->getPageSize()); |
107
|
3 |
|
$recordset = $this->dbal->queryRecordset($query); |
108
|
3 |
|
if (false === $recordset) { |
109
|
|
|
return false; |
110
|
|
|
} |
111
|
3 |
|
$this->page = $page; |
112
|
3 |
|
$this->recordset = $recordset; |
113
|
3 |
|
return true; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* perform the query to get all the records (without paging) |
118
|
|
|
* @return bool |
119
|
|
|
*/ |
120
|
|
|
public function queryAll(): bool |
121
|
|
|
{ |
122
|
|
|
$this->setPageSize($this->getTotalCount()); |
123
|
|
|
return $this->queryPage(1); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* The current page number |
128
|
|
|
* @return int |
129
|
|
|
*/ |
130
|
3 |
|
public function getPage(): int |
131
|
|
|
{ |
132
|
3 |
|
return $this->page; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* The current recordset object |
137
|
|
|
* @return Recordset |
138
|
|
|
*/ |
139
|
3 |
|
public function getRecordset(): Recordset |
140
|
|
|
{ |
141
|
3 |
|
if (! $this->recordset instanceof Recordset) { |
142
|
|
|
throw new RuntimeException('The pager does not have a current page'); |
143
|
|
|
} |
144
|
3 |
|
return $this->recordset; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* The SQL to query the data |
149
|
|
|
* @return string |
150
|
|
|
*/ |
151
|
3 |
|
public function getQueryData(): string |
152
|
|
|
{ |
153
|
3 |
|
return $this->queryData; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* The SQL to query the count of records |
158
|
|
|
* @return string |
159
|
|
|
*/ |
160
|
1 |
|
public function getQueryCount(): string |
161
|
|
|
{ |
162
|
1 |
|
return $this->queryCount; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Set the SQL to query the count of records |
167
|
|
|
* This set the countMethod to COUNT_METHOD_QUERY |
168
|
|
|
* @param string $query |
169
|
|
|
*/ |
170
|
1 |
|
protected function setQueryCount(string $query): void |
171
|
|
|
{ |
172
|
1 |
|
if ('' === $query) { |
173
|
|
|
throw new InvalidArgumentException('setQueryCount require a valid string argument'); |
174
|
|
|
} |
175
|
1 |
|
$this->queryCount = $query; |
176
|
1 |
|
$this->countMethod = self::COUNT_METHOD_QUERY; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Get the page size |
181
|
|
|
* @return int |
182
|
|
|
*/ |
183
|
4 |
|
public function getPageSize(): int |
184
|
|
|
{ |
185
|
4 |
|
return $this->pageSize; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Get the total count based on the count method |
190
|
|
|
* @return int |
191
|
|
|
*/ |
192
|
7 |
|
public function getTotalCount(): int |
193
|
|
|
{ |
194
|
7 |
|
if (null === $this->totalRecords) { |
195
|
7 |
|
if (self::COUNT_METHOD_QUERY === $this->getCountMethod()) { |
196
|
1 |
|
$this->totalRecords = $this->getTotalRecordsByQueryCount(); |
197
|
6 |
|
} elseif (self::COUNT_METHOD_SELECT === $this->getCountMethod()) { |
198
|
5 |
|
$this->totalRecords = $this->getTotalRecordsBySelectCount(); |
199
|
1 |
|
} elseif (self::COUNT_METHOD_RECORDCOUNT === $this->getCountMethod()) { |
200
|
1 |
|
$this->totalRecords = $this->getTotalRecordsByRecordCount(); |
201
|
|
|
} else { |
202
|
|
|
throw new LogicException('Cannot get a method to obtain the total count'); |
203
|
|
|
} |
204
|
|
|
} |
205
|
3 |
|
return $this->totalRecords; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* The count method, ne of COUNT_METHOD_QUERY, COUNT_METHOD_SELECT, COUNT_METHOD_RECORDCOUNT |
210
|
|
|
* @return int |
211
|
|
|
*/ |
212
|
8 |
|
public function getCountMethod(): int |
213
|
|
|
{ |
214
|
8 |
|
return $this->countMethod; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Change the count method, the only possible values are |
219
|
|
|
* COUNT_METHOD_SELECT and COUNT_METHOD_RECORDCOUNT |
220
|
|
|
* Return the previous count method set |
221
|
|
|
* @param int $method |
222
|
|
|
* @return int |
223
|
|
|
*/ |
224
|
8 |
|
public function setCountMethod(int $method): int |
225
|
|
|
{ |
226
|
8 |
|
if (! in_array($method, [self::COUNT_METHOD_SELECT, self::COUNT_METHOD_RECORDCOUNT])) { |
227
|
1 |
|
throw new InvalidArgumentException('Invalid count method'); |
228
|
|
|
} |
229
|
8 |
|
$previous = $this->countMethod; |
230
|
8 |
|
$this->countMethod = $method; |
231
|
8 |
|
return $previous; |
232
|
|
|
} |
233
|
|
|
|
234
|
1 |
|
protected function getTotalRecordsByRecordCount(): int |
235
|
|
|
{ |
236
|
1 |
|
$query = $this->getQueryData(); |
237
|
1 |
|
$result = $this->dbal->queryResult($query); |
238
|
1 |
|
if (false === $result) { |
239
|
|
|
throw new RuntimeException("Unable to query the record count by getting all the results: $query"); |
240
|
|
|
} |
241
|
1 |
|
return $result->resultCount(); |
242
|
|
|
} |
243
|
|
|
|
244
|
5 |
|
protected function getTotalRecordsBySelectCount(): int |
245
|
|
|
{ |
246
|
5 |
|
$query = 'SELECT COUNT(*)' |
247
|
5 |
|
. ' FROM (' . rtrim($this->queryData, "; \t\n\r\0\x0B") . ')' |
248
|
5 |
|
. ' AS ' . $this->dbal->sqlTable('subquerycount') |
249
|
5 |
|
. ';'; |
250
|
5 |
|
$value = (int) $this->dbal->queryOne($query, -1); |
251
|
5 |
|
if (-1 === $value) { |
252
|
4 |
|
throw new QueryException('Unable to query the record count using a subquery', $query); |
253
|
|
|
} |
254
|
1 |
|
return $value; |
255
|
|
|
} |
256
|
|
|
|
257
|
1 |
|
protected function getTotalRecordsByQueryCount(): int |
258
|
|
|
{ |
259
|
1 |
|
$query = $this->getQueryCount(); |
260
|
1 |
|
$value = (int) $this->dbal->queryOne($query, -1); |
261
|
1 |
|
if (-1 === $value) { |
262
|
|
|
throw new QueryException('Unable to query the record count using a query', $query); |
263
|
|
|
} |
264
|
1 |
|
return $value; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Number of total pages (min: 1, max: total count / page size) |
269
|
|
|
* @return int |
270
|
|
|
*/ |
271
|
7 |
|
public function getTotalPages(): int |
272
|
|
|
{ |
273
|
7 |
|
return max(1, intval(ceil($this->getTotalCount() / $this->getPageSize()))); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Set the page size, this is fixes to a minimum value of 1 |
278
|
|
|
* @param int $pageSize |
279
|
|
|
*/ |
280
|
9 |
|
public function setPageSize(int $pageSize): void |
281
|
|
|
{ |
282
|
9 |
|
$this->pageSize = max(1, $pageSize); |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|