Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
40.00% |
66 / 165 |
|
61.90% |
13 / 21 |
CRAP | |
0.00% |
0 / 1 |
DataMapperFactory | |
40.00% |
66 / 165 |
|
61.90% |
13 / 21 |
1006.90 | |
0.00% |
0 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
__clone | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
db | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
reader | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
get | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRaw | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRandom | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAll | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
writer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
create | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
updater | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
update | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
remover | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
delete | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isNullModel | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
createNullModel | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
createBaseModel | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
getObjectId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setObjectId | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
getColumnByMember | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
find | |
27.42% |
34 / 124 |
|
0.00% |
0 / 1 |
531.53 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\DataStorage\Database\Mapper |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\DataStorage\Database\Mapper; |
16 | |
17 | use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; |
18 | use phpOMS\DataStorage\Database\Query\Builder; |
19 | use phpOMS\DataStorage\Database\Query\OrderType; |
20 | use phpOMS\DataStorage\Database\Query\Where; |
21 | |
22 | /** |
23 | * Mapper factory. |
24 | * |
25 | * @package phpOMS\DataStorage\Database\Mapper |
26 | * @license OMS License 2.0 |
27 | * @link https://jingga.app |
28 | * @since 1.0.0 |
29 | * |
30 | * @template T |
31 | */ |
32 | class DataMapperFactory |
33 | { |
34 | /** |
35 | * Datetime format of the database datetime |
36 | * |
37 | * This is only for the datetime stored in the database not the generated query. |
38 | * For the query check the datetime in Grammar:$datetimeFormat |
39 | * |
40 | * @var string |
41 | * @since 1.0.0 |
42 | */ |
43 | public static string $datetimeFormat = 'Y-m-d H:i:s'; |
44 | |
45 | /** |
46 | * Primary field name. |
47 | * |
48 | * @var string |
49 | * @since 1.0.0 |
50 | */ |
51 | public const PRIMARYFIELD = ''; |
52 | |
53 | /** |
54 | * Autoincrement primary field. |
55 | * |
56 | * @var bool |
57 | * @since 1.0.0 |
58 | */ |
59 | public const AUTOINCREMENT = true; |
60 | |
61 | /** |
62 | * Primary field name. |
63 | * |
64 | * @var string |
65 | * @since 1.0.0 |
66 | */ |
67 | public const CREATED_AT = ''; |
68 | |
69 | /** |
70 | * Columns. |
71 | * |
72 | * @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}> |
73 | * @since 1.0.0 |
74 | */ |
75 | public const COLUMNS = []; |
76 | |
77 | /** |
78 | * Has many relation. |
79 | * |
80 | * @var array<string, array> |
81 | * @since 1.0.0 |
82 | */ |
83 | public const HAS_MANY = []; |
84 | |
85 | /** |
86 | * Relations. |
87 | * |
88 | * Relation is defined in current mapper |
89 | * |
90 | * @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}> |
91 | * @since 1.0.0 |
92 | */ |
93 | public const OWNS_ONE = []; |
94 | |
95 | /** |
96 | * Belongs to. |
97 | * |
98 | * @var array<string, array{mapper:class-string, external:string, column?:string, by?:string}> |
99 | * @since 1.0.0 |
100 | */ |
101 | public const BELONGS_TO = []; |
102 | |
103 | /** |
104 | * Table. |
105 | * |
106 | * @var string |
107 | * @since 1.0.0 |
108 | */ |
109 | public const TABLE = ''; |
110 | |
111 | /** |
112 | * Parent column. |
113 | * |
114 | * @var class-string |
115 | * @since 1.0.0 |
116 | */ |
117 | public const PARENT = ''; |
118 | |
119 | /** |
120 | * Model to use by the mapper. |
121 | * |
122 | * @var class-string<T> |
123 | * @since 1.0.0 |
124 | */ |
125 | public const MODEL = ''; |
126 | |
127 | /** |
128 | * Model factory to use by the mapper. |
129 | * |
130 | * @var class-string |
131 | * @since 1.0.0 |
132 | */ |
133 | public const FACTORY = ''; |
134 | |
135 | /** |
136 | * Database connection. |
137 | * |
138 | * @var ConnectionAbstract |
139 | * @since 1.0.0 |
140 | */ |
141 | protected static ConnectionAbstract $db; |
142 | |
143 | /** |
144 | * Constructor. |
145 | * |
146 | * @since 1.0.0 |
147 | * @codeCoverageIgnore |
148 | */ |
149 | final private function __construct() |
150 | { |
151 | } |
152 | |
153 | /** |
154 | * Clone. |
155 | * |
156 | * @return void |
157 | * |
158 | * @since 1.0.0 |
159 | * @codeCoverageIgnore |
160 | */ |
161 | private function __clone() |
162 | { |
163 | } |
164 | |
165 | /** |
166 | * Set default database connection |
167 | * |
168 | * @param ConnectionAbstract $db Database connection |
169 | * |
170 | * @return class-string<self> |
171 | * |
172 | * @since 1.0.0 |
173 | */ |
174 | public static function db(ConnectionAbstract $db) : string |
175 | { |
176 | self::$db = $db; |
177 | |
178 | return static::class; |
179 | } |
180 | |
181 | /** |
182 | * Create read mapper |
183 | * |
184 | * @param ConnectionAbstract $db Database connection |
185 | * |
186 | * @return ReadMapper |
187 | * |
188 | * @since 1.0.0 |
189 | */ |
190 | public static function reader(ConnectionAbstract $db = null) : ReadMapper |
191 | { |
192 | return new ReadMapper(new static(), $db ?? self::$db); |
193 | } |
194 | |
195 | /** |
196 | * Create read mapper |
197 | * |
198 | * @param ConnectionAbstract $db Database connection |
199 | * |
200 | * @return ReadMapper<T> |
201 | * |
202 | * @since 1.0.0 |
203 | */ |
204 | public static function get(ConnectionAbstract $db = null) : ReadMapper |
205 | { |
206 | /** @var ReadMapper<T> $reader */ |
207 | $reader = new ReadMapper(new static(), $db ?? self::$db); |
208 | |
209 | return $reader->get(); |
210 | } |
211 | |
212 | /** |
213 | * Create read mapper |
214 | * |
215 | * @param ConnectionAbstract $db Database connection |
216 | * |
217 | * @return ReadMapper |
218 | * |
219 | * @since 1.0.0 |
220 | */ |
221 | public static function getRaw(ConnectionAbstract $db = null) : ReadMapper |
222 | { |
223 | /** @var ReadMapper<T> $reader */ |
224 | $reader = new ReadMapper(new static(), $db ?? self::$db); |
225 | |
226 | return $reader->getRaw(); |
227 | } |
228 | |
229 | /** |
230 | * Create read mapper |
231 | * |
232 | * @param ConnectionAbstract $db Database connection |
233 | * |
234 | * @return ReadMapper |
235 | * |
236 | * @since 1.0.0 |
237 | */ |
238 | public static function getRandom(ConnectionAbstract $db = null) : ReadMapper |
239 | { |
240 | return (new ReadMapper(new static(), $db ?? self::$db))->getRandom(); |
241 | } |
242 | |
243 | /** |
244 | * Create read mapper |
245 | * |
246 | * @param ConnectionAbstract $db Database connection |
247 | * |
248 | * @return ReadMapper |
249 | * |
250 | * @since 1.0.0 |
251 | */ |
252 | public static function count(ConnectionAbstract $db = null) : ReadMapper |
253 | { |
254 | return (new ReadMapper(new static(), $db ?? self::$db))->count(); |
255 | } |
256 | |
257 | /** |
258 | * Create read mapper |
259 | * |
260 | * @param ConnectionAbstract $db Database connection |
261 | * |
262 | * @return Builder |
263 | * |
264 | * @since 1.0.0 |
265 | */ |
266 | public static function getQuery(ConnectionAbstract $db = null) : Builder |
267 | { |
268 | return (new ReadMapper(new static(), $db ?? self::$db))->getQuery(); |
269 | } |
270 | |
271 | /** |
272 | * Create read mapper |
273 | * |
274 | * @param ConnectionAbstract $db Database connection |
275 | * |
276 | * @return ReadMapper |
277 | * |
278 | * @since 1.0.0 |
279 | */ |
280 | public static function getAll(ConnectionAbstract $db = null) : ReadMapper |
281 | { |
282 | /** @var ReadMapper<T> $reader */ |
283 | $reader = new ReadMapper(new static(), $db ?? self::$db); |
284 | |
285 | return $reader->getAll(); |
286 | } |
287 | |
288 | /** |
289 | * Create write mapper |
290 | * |
291 | * @param ConnectionAbstract $db Database connection |
292 | * |
293 | * @return WriteMapper |
294 | * |
295 | * @since 1.0.0 |
296 | */ |
297 | public static function writer(ConnectionAbstract $db = null) : WriteMapper |
298 | { |
299 | return new WriteMapper(new static(), $db ?? self::$db); |
300 | } |
301 | |
302 | /** |
303 | * Create write mapper |
304 | * |
305 | * @param ConnectionAbstract $db Database connection |
306 | * |
307 | * @return WriteMapper |
308 | * |
309 | * @since 1.0.0 |
310 | */ |
311 | public static function create(ConnectionAbstract $db = null) : WriteMapper |
312 | { |
313 | return (new WriteMapper(new static(), $db ?? self::$db))->create(); |
314 | } |
315 | |
316 | /** |
317 | * Create update mapper |
318 | * |
319 | * @param ConnectionAbstract $db Database connection |
320 | * |
321 | * @return UpdateMapper |
322 | * |
323 | * @since 1.0.0 |
324 | */ |
325 | public static function updater(ConnectionAbstract $db = null) : UpdateMapper |
326 | { |
327 | return new UpdateMapper(new static(), $db ?? self::$db); |
328 | } |
329 | |
330 | /** |
331 | * Create update mapper |
332 | * |
333 | * @param ConnectionAbstract $db Database connection |
334 | * |
335 | * @return UpdateMapper |
336 | * |
337 | * @since 1.0.0 |
338 | */ |
339 | public static function update(ConnectionAbstract $db = null) : UpdateMapper |
340 | { |
341 | return (new UpdateMapper(new static(), $db ?? self::$db))->update(); |
342 | } |
343 | |
344 | /** |
345 | * Create delete mapper |
346 | * |
347 | * @param ConnectionAbstract $db Database connection |
348 | * |
349 | * @return DeleteMapper |
350 | * |
351 | * @since 1.0.0 |
352 | */ |
353 | public static function remover(ConnectionAbstract $db = null) : DeleteMapper |
354 | { |
355 | return new DeleteMapper(new static(), $db ?? self::$db); |
356 | } |
357 | |
358 | /** |
359 | * Create delete mapper |
360 | * |
361 | * @param ConnectionAbstract $db Database connection |
362 | * |
363 | * @return DeleteMapper |
364 | * |
365 | * @since 1.0.0 |
366 | */ |
367 | public static function delete(ConnectionAbstract $db = null) : DeleteMapper |
368 | { |
369 | return (new DeleteMapper(new static(), $db ?? self::$db))->delete(); |
370 | } |
371 | |
372 | /** |
373 | * Test if object is null object |
374 | * |
375 | * @param mixed $obj Object to check |
376 | * |
377 | * @return bool |
378 | * |
379 | * @since 1.0.0 |
380 | */ |
381 | public static function isNullModel(mixed $obj) : bool |
382 | { |
383 | return \is_object($obj) && \strpos(\get_class($obj), '\Null') !== false; |
384 | } |
385 | |
386 | /** |
387 | * Creates the current null object |
388 | * |
389 | * @param mixed $id Model id |
390 | * |
391 | * @return mixed |
392 | * |
393 | * @since 1.0.0 |
394 | */ |
395 | public static function createNullModel(mixed $id = null) : mixed |
396 | { |
397 | $class = empty(static::MODEL) ? \substr(static::class, 0, -6) : static::MODEL; |
398 | $parts = \explode('\\', $class); |
399 | $name = $parts[$c = (\count($parts) - 1)]; |
400 | $parts[$c] = 'Null' . $name; |
401 | $class = \implode('\\', $parts); |
402 | |
403 | return $id !== null ? new $class($id) : new $class(); |
404 | } |
405 | |
406 | /** |
407 | * Create the empty base model |
408 | * |
409 | * @param null|array $data Data to use for initialization |
410 | * |
411 | * @return object |
412 | * |
413 | * @since 1.0.0 |
414 | */ |
415 | public static function createBaseModel(array $data = null) : object |
416 | { |
417 | if (empty(static::FACTORY)) { |
418 | $class = empty(static::MODEL) ? \substr(static::class, 0, -6) : static::MODEL; |
419 | |
420 | return new $class(); |
421 | } |
422 | |
423 | return static::FACTORY::createWith($data); |
424 | } |
425 | |
426 | /** |
427 | * Get id of object |
428 | * |
429 | * @param object $obj Model to create |
430 | * @param string $member Member name for the id, if it is not the primary key |
431 | * |
432 | * @return mixed |
433 | * |
434 | * @since 1.0.0 |
435 | */ |
436 | public static function getObjectId(object $obj, string $member = null) : mixed |
437 | { |
438 | $propertyName = $member ?? static::COLUMNS[static::PRIMARYFIELD]['internal']; |
439 | |
440 | return $obj->{$propertyName}; |
441 | } |
442 | |
443 | /** |
444 | * Set id to model |
445 | * |
446 | * @param \ReflectionClass $refClass Reflection class |
447 | * @param object $obj Object to create |
448 | * @param mixed $objId Id to set |
449 | * |
450 | * @return void |
451 | * |
452 | * @since 1.0.0 |
453 | */ |
454 | public static function setObjectId(\ReflectionClass $refClass, object $obj, mixed $objId) : void |
455 | { |
456 | $propertyName = static::COLUMNS[static::PRIMARYFIELD]['internal']; |
457 | $refProp = $refClass->getProperty($propertyName); |
458 | |
459 | \settype($objId, static::COLUMNS[static::PRIMARYFIELD]['type']); |
460 | if (!$refProp->isPublic()) { |
461 | $refProp->setValue($obj, $objId); |
462 | } else { |
463 | $obj->{$propertyName} = $objId; |
464 | } |
465 | } |
466 | |
467 | /** |
468 | * Find database column name by member name |
469 | * |
470 | * @param string $name member name |
471 | * |
472 | * @return null|string |
473 | * |
474 | * @since 1.0.0 |
475 | */ |
476 | public static function getColumnByMember(string $name) : ?string |
477 | { |
478 | foreach (static::COLUMNS as $cName => $column) { |
479 | if ($column['internal'] === $name) { |
480 | return $cName; |
481 | } |
482 | } |
483 | |
484 | return null; |
485 | } |
486 | |
487 | /** |
488 | * Find data. |
489 | * |
490 | * @param string $search Search string |
491 | * @param DataMapperAbstract $mapper Mapper to populate |
492 | * @param int $id Pivot element id |
493 | * @param string $secondaryId secondary id which becomes necessary for sorted results |
494 | * @param string $type Page type (p = get previous elements, n = get next elements) |
495 | * @param int $pageLimit Limit result set |
496 | * @param string $sortBy Model member name to sort by |
497 | * @param string $sortOrder Sort order |
498 | * @param array $searchFields Fields to search in. ([] = all) @todo: maybe change to all which have autocomplete = true defined? |
499 | * @param array $filters Additional search filters applied ['type', 'value1', 'logic1', 'value2', 'logic2'] |
500 | * |
501 | * @return array{hasPrevious:bool, hasNext:bool, data:object[]} |
502 | * |
503 | * @since 1.0.0 |
504 | */ |
505 | public static function find( |
506 | string $search = null, |
507 | DataMapperAbstract $mapper = null, |
508 | int $id = 0, |
509 | string $secondaryId = '', |
510 | string $type = null, |
511 | int $pageLimit = 25, |
512 | string $sortBy = null, |
513 | string $sortOrder = OrderType::DESC, |
514 | array $searchFields = [], |
515 | array $filters = [] |
516 | ) : array { |
517 | $mapper ??= static::getAll(); |
518 | $sortOrder = \strtoupper($sortOrder); |
519 | |
520 | $data = []; |
521 | |
522 | $type = $id === 0 ? null : $type; |
523 | $hasPrevious = false; |
524 | $hasNext = false; |
525 | |
526 | $primarySortField = static::COLUMNS[static::PRIMARYFIELD]['internal']; |
527 | |
528 | $sortBy = empty($sortBy) || static::getColumnByMember($sortBy) === null ? $primarySortField : $sortBy; |
529 | |
530 | $sortById = $sortBy === $primarySortField; |
531 | $secondaryId = $sortById ? $id : $secondaryId; |
532 | |
533 | foreach ($filters as $key => $filter) { |
534 | $mapper->where($key, '%' . $filter['value1'] . '%', $filter['logic1'] ?? 'like'); |
535 | |
536 | if (!empty($filter['value2'])) { |
537 | $mapper->where($key, '%' . $filter['value2'] . '%', $filter['logic2'] ?? 'like'); |
538 | } |
539 | } |
540 | |
541 | if (!empty($search)) { |
542 | $where = new Where(static::$db); |
543 | $counter = 0; |
544 | |
545 | if (empty($searchFields)) { |
546 | foreach (static::COLUMNS as $column) { |
547 | $searchFields[] = $column['internal']; |
548 | } |
549 | } |
550 | |
551 | foreach ($searchFields as $searchField) { |
552 | if (($column = static::getColumnByMember($searchField)) === null) { |
553 | continue; |
554 | } |
555 | |
556 | $where->where($column, 'like', '%' . $search . '%', 'OR'); |
557 | ++$counter; |
558 | } |
559 | |
560 | if ($counter > 0) { |
561 | $mapper->where('', $where); |
562 | } |
563 | } |
564 | |
565 | // @todo: how to handle columns which are NOT members (columns which are manipulated) |
566 | // Maybe pass callback array which can handle these cases? |
567 | |
568 | if ($type === 'p') { |
569 | $cloned = clone $mapper; |
570 | $mapper->sort( |
571 | $sortBy, |
572 | $sortOrder === OrderType::DESC ? OrderType::ASC : OrderType::DESC |
573 | ) |
574 | ->where($sortBy, $secondaryId, $sortOrder === OrderType::DESC ? '>=' : '<=') |
575 | ->limit($pageLimit + 2); |
576 | |
577 | if (!$sortById) { |
578 | $where = new Where(static::$db); |
579 | $where->where(static::PRIMARYFIELD, '>=', $id) |
580 | ->orWhere( |
581 | static::getColumnByMember($sortBy), |
582 | $sortOrder === OrderType::DESC ? '>' : '<', |
583 | $secondaryId |
584 | ); |
585 | |
586 | $mapper->where('', $where) |
587 | ->sort($primarySortField, OrderType::ASC); |
588 | } |
589 | |
590 | $data = $mapper->execute(); |
591 | |
592 | if (($count = \count($data)) < 2) { |
593 | $cloned->sort($sortBy, $sortOrder) |
594 | ->limit($pageLimit + 1); |
595 | |
596 | if (!$sortById) { |
597 | $where = new Where(static::$db); |
598 | $where->where(static::PRIMARYFIELD, '<=', $id) |
599 | ->orWhere( |
600 | static::getColumnByMember($sortBy), |
601 | $sortOrder === OrderType::DESC ? '<' : '>', |
602 | $secondaryId |
603 | ); |
604 | |
605 | $cloned->where('', $where) |
606 | ->sort($primarySortField, OrderType::DESC); |
607 | } |
608 | |
609 | $data = $cloned->execute(); |
610 | |
611 | $hasNext = $count > $pageLimit; |
612 | if ($hasNext) { |
613 | \array_pop($data); |
614 | --$count; |
615 | } |
616 | } else { |
617 | if (\reset($data)->getId() === $id) { |
618 | \array_shift($data); |
619 | $hasNext = true; |
620 | --$count; |
621 | } |
622 | |
623 | if ($count > $pageLimit) { |
624 | if (!$hasNext) { // @todo: can be maybe removed? |
625 | \array_pop($data); |
626 | $hasNext = true; |
627 | --$count; |
628 | } |
629 | |
630 | if ($count > $pageLimit) { |
631 | $hasPrevious = true; |
632 | \array_pop($data); |
633 | } |
634 | } |
635 | |
636 | $data = \array_reverse($data); |
637 | } |
638 | } elseif ($type === 'n') { |
639 | $mapper = $mapper->sort($sortBy, $sortOrder) |
640 | ->where($sortBy, $secondaryId, $sortOrder === OrderType::DESC ? '<=' : '>=') |
641 | ->limit($pageLimit + 2); |
642 | |
643 | if (!$sortById) { |
644 | $where = new Where(static::$db); |
645 | $where->where(static::PRIMARYFIELD, '<=', $id) |
646 | ->orWhere( |
647 | static::getColumnByMember($sortBy), |
648 | $sortOrder === OrderType::DESC ? '<' : '>', |
649 | $secondaryId |
650 | ); |
651 | |
652 | $mapper = $mapper |
653 | ->where('', $where) |
654 | ->sort($primarySortField, OrderType::DESC); |
655 | } |
656 | |
657 | $data = $mapper->execute(); |
658 | $count = \count($data); |
659 | |
660 | if ($count < 1) { |
661 | return [ |
662 | 'hasPrevious' => false, |
663 | 'hasNext' => false, |
664 | 'data' => [], |
665 | ]; |
666 | } |
667 | |
668 | if (\reset($data)->getId() === $id) { |
669 | \array_shift($data); |
670 | $hasPrevious = true; |
671 | --$count; |
672 | } |
673 | |
674 | if ($count > $pageLimit) { |
675 | \array_pop($data); |
676 | $hasNext = true; |
677 | --$count; |
678 | } |
679 | |
680 | if ($count > $pageLimit) { |
681 | \array_pop($data); |
682 | --$count; |
683 | } |
684 | } else { |
685 | $mapper->sort($sortBy, $sortOrder) |
686 | ->limit($pageLimit + 1); |
687 | |
688 | if (!$sortById) { |
689 | $mapper->sort($primarySortField, OrderType::DESC); |
690 | } |
691 | |
692 | $data = $mapper->execute(); |
693 | |
694 | $hasNext = ($count = \count($data)) > $pageLimit; |
695 | if ($hasNext) { |
696 | \array_pop($data); |
697 | } |
698 | } |
699 | |
700 | return [ |
701 | 'hasPrevious' => $hasPrevious, |
702 | 'hasNext' => $hasNext, |
703 | 'data' => $data, |
704 | ]; |
705 | } |
706 | } |