Pager::getTotalRecordsByRecordCount()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 8
ccs 5
cts 6
cp 0.8333
rs 10
cc 2
nc 2
nop 0
crap 2.0185
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