-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPicoPageData.php
More file actions
581 lines (514 loc) · 16.1 KB
/
PicoPageData.php
File metadata and controls
581 lines (514 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
<?php
namespace MagicObject\Database;
use MagicObject\Exceptions\FindOptionException;
use MagicObject\MagicObject;
use PDO;
use PDOStatement;
use stdClass;
/**
* Class representing paginated data for database queries.
*
* The `PicoPageData` class encapsulates the results of a database query along with pagination details,
* execution timing, and other metadata. It provides methods to manage and retrieve paginated results
* effectively, allowing for easy integration into applications that require data manipulation and display.
*
* ## Key Features
* - Encapsulates query results in a paginated format.
* - Supports execution time tracking for performance monitoring.
* - Provides easy access to pagination controls and metadata.
* - Facilitates fetching and processing of data with subquery mapping.
*
* @author Kamshory
* @package MagicObject\Database
* @link https://github.com/Planetbiru/MagicObject
*/
class PicoPageData // NOSONAR
{
const RESULT = 'result';
const PAGEABLE = 'pageable';
/**
* Result data from the query.
*
* @var MagicObject[]
*/
private $result = array();
/**
* Pageable object that defines pagination settings.
*
* @var PicoPageable
*/
private $pageable;
/**
* Total number of matching results from the query.
*
* @var int
*/
private $totalResult = 0;
/**
* Total number of pages based on pagination settings.
*
* @var int
*/
private $totalPage = 0;
/**
* Current page number in the pagination context.
*
* @var int
*/
private $pageNumber = 1;
/**
* Number of results per page.
*
* @var int
*/
private $pageSize = 0;
/**
* Offset for retrieving data in the current pagination context.
*
* @var int
*/
private $dataOffset = 0;
/**
* Start time of the query execution.
*
* @var float
*/
private $startTime = 0.0;
/**
* End time of the query execution.
*
* @var float
*/
private $endTime = 0.0;
/**
* Total execution time for the query in seconds.
*
* @var float
*/
private $executionTime = 0.0;
/**
* Array holding pagination details for display.
*
* @var array
*/
private $pagination = array();
/**
* PDO statement associated with the query execution.
*
* @var PDOStatement
*/
private $stmt = null;
/**
* Class name of the entity being managed.
*
* @var string
*/
private $className;
/**
* Mapping information for subqueries.
*
* @var array
*/
private $subqueryMap;
/**
* Flag indicating whether the result was derived from a count query.
*
* @var bool
*/
private $byCountResult = false;
/**
* Entity associated with the results.
*
* @var MagicObject
*/
private $entity;
/**
* Flags for controlling find options in the query.
*
* @var int
*/
private $findOption = 0;
/**
* Constructor for the PicoPageData class.
*
* Initializes a new instance of the class with the specified parameters.
*
* @param MagicObject[]|null $result Array of MagicObject instances or null.
* @param float $startTime Timestamp when the query was initiated.
* @param int $totalResult Total count of results, defaults to 0.
* @param PicoPageable|null $pageable Pageable object for pagination settings.
* @param PDOStatement|null $stmt PDO statement associated with the query.
* @param MagicObject|null $entity Entity associated with the query results.
* @param array|null $subqueryMap Mapping for subquery results.
*/
public function __construct(
$result = null,
$startTime = null,
$totalResult = 0,
$pageable = null,
$stmt = null,
$entity = null,
$subqueryMap = null
) {
// Set the start time
if (isset($startTime)) {
$this->startTime = $startTime;
} else {
$this->startTime = time();
}
// Initialize result data
$this->result = $result === null ? [] : $result;
// Calculate total results
$this->totalResult = $totalResult === 0 ? $this->countData($this->result) : $totalResult;
$this->byCountResult = $totalResult === 0;
// Handle pageable settings
if ($pageable instanceof PicoPageable) {
$this->pageable = $pageable;
$this->calculateContent();
} else {
$this->initializeDefaultPagination($this->totalResult);
}
// Set execution timing
$this->endTime = microtime(true);
$this->executionTime = $this->endTime - $this->startTime;
// Store additional parameters
$this->stmt = $stmt;
$this->entity = $entity;
$this->className = $entity !== null ? get_class($entity) : null;
$this->subqueryMap = $subqueryMap !== null ? $subqueryMap : array();
}
/**
* Count the number of items in the result set.
*
* @param array $result Result set to count.
* @return int Count of items in the result.
*/
private function countData($result)
{
return is_array($result) ? count($result) : 0;
}
/**
* Calculate pagination content based on the pageable settings.
*
* @return self Returns the current instance for method chaining.
*/
public function calculateContent()
{
// Extract pagination details
$this->pageNumber = $this->pageable->getPage()->getPageNumber();
$this->pageSize = $this->pageable->getPage()->getPageSize();
$this->totalPage = (int) ceil($this->totalResult / $this->pageSize);
$this->dataOffset = ($this->pageNumber - 1) * $this->pageSize;
$this->generatePagination(3);
return $this;
}
/**
* Initialize default pagination settings.
*
* This method is called when no pageable object is provided.
*
* @param int $countResult Total count of results.
*/
private function initializeDefaultPagination($countResult)
{
$this->pageNumber = 1;
$this->totalPage = 1;
$this->pageSize = $countResult;
$this->dataOffset = 0;
}
/**
* Generate pagination details for display.
*
* This method constructs an array of pagination controls based on the current page number and total pages.
*
* @param int $pageRange Number of pages to show before and after the current page.
* @return self Returns the current instance for method chaining.
*/
public function generatePagination($pageRange = 3)
{
$pageRange = max(1, $pageRange);
$curPage = $this->pageNumber;
$totalPage = $this->totalPage;
$minPage = max(1, $curPage - $pageRange);
$maxPage = $this->byCountResult ? $totalPage : min($curPage + $pageRange, $totalPage);
$this->pagination = array();
for ($i = $minPage; $i <= $maxPage; $i++) {
$this->pagination[] = array('page' => $i, 'selected' => $i === $curPage);
}
return $this;
}
/**
* Get result data from the query.
*
* @return MagicObject[] Array of MagicObject instances.
*/
public function getResult()
{
return $this->result;
}
/**
* Get the result as an array of stdClass objects or plain values.
*
* This method converts the internal result set, which may consist of MagicObject instances,
* into a plain PHP array. Each MagicObject is recursively converted to a stdClass object
* or a scalar value.
*
* @return array The result set as a plain PHP array.
*/
public function getResultAsArray()
{
return $this->magicObjectToArray($this->result);
}
/**
* Recursively converts a MagicObject, an object, or an array of them into a plain PHP array or stdClass object.
*
* This method is useful for serialization or for passing the data to components
* that expect standard PHP types instead of MagicObject instances.
*
* @param mixed $data The data to convert. Can be a MagicObject, an array, an object, or a scalar value.
* @return mixed The converted data as a plain PHP array, stdClass object, or scalar value.
*/
protected function magicObjectToArray($data) // NOSONAR
{
// Null or scalar
if (is_null($data) || is_scalar($data)) {
return $data;
}
// Array
if (is_array($data)) {
$out = array();
foreach ($data as $k => $v) {
$out[$k] = $this->magicObjectToArray($v);
}
return $out;
}
// Object
if (is_object($data)) {
// Prefer toArray() if exists
if (method_exists($data, 'toArray')) {
return $this->magicObjectToArray($data->toArray());
}
// Public properties first
$vars = get_object_vars($data);
// Fallback: reflection (protected/private)
if (empty($vars)) {
$reflect = new \ReflectionClass($data);
$vars = array();
foreach ($reflect->getProperties() as $prop) {
// Skip non-public if we cannot safely access
if (!$prop->isPublic()) {
// setAccessible may not exist or may be restricted
if (!method_exists($prop, 'setAccessible')) {
continue;
}
try {
$prop->setAccessible(true);
} catch (\Exception $e) {
continue;
}
}
$vars[$prop->getName()] = $prop->getValue($data);
}
}
$obj = new \stdClass();
foreach ($vars as $k => $v) {
$obj->$k = $this->magicObjectToArray($v);
}
return $obj;
}
return $data;
}
/**
* Get the current page number in the pagination context.
*
* @return int Current page number.
*/
public function getPageNumber()
{
return $this->pageNumber;
}
/**
* Get the total number of pages based on pagination settings.
*
* @return int Total page count.
*/
public function getTotalPage()
{
return $this->totalPage;
}
/**
* Get the size of each page (number of results per page).
*
* @return int Page size.
*/
public function getPageSize()
{
return $this->pageSize;
}
/**
* Convert the object to a JSON string representation for debugging.
*
* This method is intended for debugging purposes only and provides
* a JSON representation of the object's state.
*
* @return string The JSON representation of the object.
*/
public function __toString()
{
$obj = new stdClass;
$exposedProps = array(
"pageable",
"totalResult",
"totalPage",
"pageNumber",
"pageSize",
"dataOffset",
"startTime",
"endTime",
"executionTime",
"pagination"
);
foreach ($exposedProps as $key) {
if (property_exists($this, $key)) {
$obj->{$key} = $this->{$key};
}
}
$obj->findOption = array(
"FIND_OPTION_NO_COUNT_DATA" => $this->findOption & MagicObject::FIND_OPTION_NO_COUNT_DATA,
"FIND_OPTION_NO_FETCH_DATA" => $this->findOption & MagicObject::FIND_OPTION_NO_FETCH_DATA,
);
return json_encode($obj, JSON_PRETTY_PRINT);
}
/**
* Get the execution time of the query in seconds.
*
* @return float Execution time.
*/
public function getExecutionTime()
{
return $this->executionTime;
}
/**
* Get the pagination details for the current query.
*
* @return array Pagination details.
*/
public function getPagination()
{
return $this->pagination;
}
/**
* Get the pagination control object for managing page navigation.
*
* @param string $parameterName Parameter name for the page.
* @param string|null $path Optional link path.
* @return PicoPageControl Pagination control object.
*/
public function getPageControl($parameterName = 'page', $path = null)
{
return new PicoPageControl($this, $parameterName, $path);
}
/**
* Get the total result count from the query.
*
* @return int Total result count.
*/
public function getTotalResult()
{
return $this->totalResult;
}
/**
* Get the pageable object associated with this data.
*
* @return PicoPageable|null Pageable object or null if not set.
*/
public function getPageable()
{
return $this->pageable;
}
/**
* Get the data offset for the current pagination context.
*
* @return int Data offset.
*/
public function getDataOffset()
{
return $this->dataOffset;
}
/**
* Get the PDO statement associated with the query.
*
* @return PDOStatement PDO statement.
* @throws FindOptionException if the statement is null.
*/
public function getPDOStatement()
{
if ($this->stmt === null) {
throw new FindOptionException("Statement is null. See MagicObject::FIND_OPTION_NO_FETCH_DATA option.");
}
return $this->stmt;
}
/**
* Fetch the next row from the result set.
*
* @return MagicObject|mixed Next row data as a MagicObject or false on failure.
* @throws FindOptionException if the statement is null.
*/
public function fetch()
{
if ($this->stmt === null) {
throw new FindOptionException("Statement is null. See MagicObject::FIND_OPTION_NO_FETCH_DATA option.");
}
$result = $this->stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT);
return $result !== false ? $this->applySubqueryResult($result) : false;
}
/**
* Apply subquery results to the row data.
*
* This method processes the row data and integrates results from subqueries as defined by the mapping.
*
* @param array $row Data row from the query result.
* @return MagicObject Processed MagicObject instance containing the merged data.
*/
public function applySubqueryResult($row)
{
$data = $row;
if (!empty($this->subqueryMap) && is_array($this->subqueryMap)) {
foreach ($this->subqueryMap as $info) {
$objectName = $info['objectName'];
$objectNameSub = $info['objectName'];
$primaryKeyValue = isset($info['columnName']) && isset($row[$info['columnName']]) ? $row[$info['columnName']] : null;
$propertyValue = isset($row[$objectNameSub]) ? $row[$objectNameSub] : null;
$data[$objectName] = ($propertyValue !== null)
? (new MagicObject())->set($info['primaryKey'], $primaryKeyValue)->set($info['propertyName'], $propertyValue)
: new MagicObject();
}
} else {
$persist = new PicoDatabasePersistence($this->entity->currentDatabase(), $this->entity);
$info = $this->entity->tableInfo();
$data = $persist->fixDataType($row, $info);
$data = $persist->join($data, $row, $info);
}
return new $this->className($data);
}
/**
* Get find option flags indicating query behavior.
*
* @return int Find option flags.
*/
public function getFindOption()
{
return $this->findOption;
}
/**
* Set find option flags to control query behavior.
*
* @param int $findOption Flags indicating the desired query options.
* @return self Returns the current instance for method chaining.
*/
public function setFindOption($findOption)
{
$this->findOption = $findOption;
return $this;
}
}