Completed
Push — master ( 7004fd...26e409 )
by Anton
14s queued 10s
created

Grid::getDefaultLimit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link      https://github.com/bluzphp/framework
7
 */
8
9
declare(strict_types=1);
10
11
namespace Bluz\Grid;
12
13
use Bluz\Common\Helper;
14
use Bluz\Common\Options;
15
use Bluz\Proxy\Request;
16
use Bluz\Proxy\Router;
17
18
/**
19
 * Grid
20
 *
21
 * @package  Bluz\Grid
22
 * @author   Anton Shevchuk
23
 * @link     https://github.com/bluzphp/framework/wiki/Grid
24
 *
25
 * @method string filter($column, $filter, $value, $reset = true)
26
 * @method string first()
27
 * @method string last()
28
 * @method string limit($limit = 25)
29
 * @method string next()
30
 * @method string order($column, $order = null, $defaultOrder = Grid::ORDER_ASC, $reset = true)
31
 * @method string page($page = 1)
32
 * @method string pages()
33
 * @method string prev()
34
 * @method string reset()
35
 * @method string total()
36
 */
37
abstract class Grid
38
{
39
    use Options;
40
    use Helper;
41
42
    public const ORDER_ASC = 'asc';
43
    public const ORDER_DESC = 'desc';
44
45
    public const FILTER_LIKE = 'like'; // like
46
    public const FILTER_ENUM = 'enum'; // one from .., .., ..
47
48
    public const FILTER_EQ = 'eq'; // equal to ..
49
    public const FILTER_NE = 'ne'; // not equal to ..
50
    public const FILTER_GT = 'gt'; // greater than ..
51
    public const FILTER_GE = 'ge'; // greater than .. or equal
52
    public const FILTER_LT = 'lt'; // less than ..
53
    public const FILTER_LE = 'le'; // less than .. or equal
54
55
    /**
56
     * @var Source\AbstractSource instance of Source
57
     */
58
    protected $adapter;
59
60
    /**
61
     * @var Data instance of Data
62
     */
63
    protected $data;
64
65
    /**
66
     * @var string unique identification of grid
67
     */
68
    protected $uid;
69
70
    /**
71
     * @var string unique prefix of grid
72
     */
73
    protected $prefix = '';
74
75
    /**
76
     * @var string location of Grid
77
     */
78
    protected $module;
79
80
    /**
81
     * @var string location of Grid
82
     */
83
    protected $controller;
84
85
    /**
86
     * @var array custom array params
87
     */
88
    protected $params = [];
89
90
    /**
91
     * @var integer start from first page
92
     */
93
    protected $page = 1;
94
95
    /**
96
     * @var integer limit per page
97
     */
98
    protected $limit = 25;
99
100
    /**
101
     * @var integer default value of page limit
102
     * @see Grid::$limit
103
     */
104
    protected $defaultLimit = 25;
105
106
    /**
107
     * List of orders
108
     *
109
     * Example
110
     *     'first' => 'ASC',
111
     *     'last' => 'ASC'
112
     *
113
     * @var array
114
     */
115
    protected $orders = [];
116
117
    /**
118
     * @var array default order
119
     * @see Grid::$orders
120
     */
121
    protected $defaultOrder = [];
122
123
    /**
124
     * @var array list of allow orders
125
     * @see Grid::$orders
126
     */
127
    protected $allowOrders = [];
128
129
    /**
130
     * @var array list of filters
131
     */
132
    protected $filters = [];
133
134
    /**
135
     * List of allow filters
136
     *
137
     * Example
138
     *     ['id', 'status' => ['active', 'disable']]
139
     *
140
     * @var array
141
     * @see Grid::$filters
142
     */
143
    protected $allowFilters = [];
144
145
    /**
146
     * List of allow filter names
147
     *
148
     * @var array
149
     * @see Grid::$filters
150
     */
151
    protected $allowFilterNames = [
152
        self::FILTER_LIKE,
153
        self::FILTER_ENUM,
154
        self::FILTER_EQ,
155
        self::FILTER_NE,
156
        self::FILTER_GT,
157
        self::FILTER_GE,
158
        self::FILTER_LT,
159
        self::FILTER_LE
160
    ];
161
162
    /**
163
     * List of aliases for columns in DB
164
     *
165
     * @var array
166
     */
167
    protected $aliases = [];
168
169
    /**
170
     * Grid constructor
171
     *
172
     * @param array $options
173
     *
174
     * @throws \Bluz\Common\Exception\CommonException
175
     */
176 32
    public function __construct($options = null)
177
    {
178
        // initial default helper path
179 32
        $this->addHelperPath(__DIR__ . '/Helper/');
180
181 32
        if ($options) {
182
            $this->setOptions($options);
183
        }
184
185 32
        if ($this->getUid()) {
186 32
            $this->prefix = $this->getUid() . '-';
187
        }
188
189 32
        $this->init();
190
191 32
        $this->processRequest();
192 32
    }
193
194
    /**
195
     * Initialize Grid
196
     *
197
     * @return void
198
     */
199
    abstract public function init(): void;
200
201
    /**
202
     * Set source adapter
203
     *
204
     * @param Source\AbstractSource $adapter
205
     *
206
     * @return void
207
     */
208 32
    public function setAdapter(Source\AbstractSource $adapter): void
209
    {
210 32
        $this->adapter = $adapter;
211 32
    }
212
213
    /**
214
     * Get source adapter
215
     *
216
     * @return Source\AbstractSource
217
     * @throws GridException
218
     */
219 15
    public function getAdapter(): Source\AbstractSource
220
    {
221 15
        if (null === $this->adapter) {
222
            throw new GridException('Grid adapter is not initialized');
223
        }
224 15
        return $this->adapter;
225
    }
226
227
    /**
228
     * Get unique Grid Id
229
     *
230
     * @return string
231
     */
232 32
    public function getUid(): string
233
    {
234 32
        return $this->uid;
235
    }
236
237
    /**
238
     * Get prefix
239
     *
240
     * @return string
241
     */
242 1
    public function getPrefix(): string
243
    {
244 1
        return $this->prefix;
245
    }
246
247
    /**
248
     * Set module
249
     *
250
     * @param string $module
251
     *
252
     * @return void
253
     */
254 1
    public function setModule(string $module): void
255
    {
256 1
        $this->module = $module;
257 1
    }
258
259
    /**
260
     * Get module
261
     *
262
     * @return string
263
     */
264 13
    public function getModule(): ?string
265
    {
266 13
        return $this->module;
267
    }
268
269
    /**
270
     * Set controller
271
     *
272
     * @param  string $controller
273
     *
274
     * @return void
275
     */
276 1
    public function setController(string $controller): void
277
    {
278 1
        $this->controller = $controller;
279 1
    }
280
281
    /**
282
     * Get controller
283
     *
284
     * @return string
285
     */
286 13
    public function getController(): ?string
287
    {
288 13
        return $this->controller;
289
    }
290
291
    /**
292
     * Process request
293
     *
294
     * Example of request url
295
     * - http://domain.com/pages/grid/
296
     * - http://domain.com/pages/grid/page/2/
297
     * - http://domain.com/pages/grid/page/2/order-alias/desc/
298
     * - http://domain.com/pages/grid/page/2/order-created/desc/order-alias/asc/
299
     *
300
     * with prefix for support more than one grid on page
301
     * - http://domain.com/users/grid/users-page/2/users-order-created/desc/
302
     * - http://domain.com/users/grid/users-page/2/users-filter-status/active/
303
     *
304
     * hash support
305
     * - http://domain.com/pages/grid/#/page/2/order-created/desc/order-alias/asc/
306
     *
307
     * @return void
308
     * @throws GridException
309
     */
310 32
    public function processRequest(): void
311
    {
312 32
        $this->module = Request::getModule();
313 32
        $this->controller = Request::getController();
314
315 32
        $page = (int)Request::getParam($this->prefix . 'page', 1);
316 32
        $this->setPage($page);
317
318 32
        $limit = (int)Request::getParam($this->prefix . 'limit', $this->limit);
319 32
        $this->setLimit($limit);
320
321 32
        foreach ($this->allowOrders as $column) {
322 32
            $alias = $this->applyAlias($column);
323 32
            $order = Request::getParam($this->prefix . 'order-' . $alias);
324 32
            if (null !== $order) {
325 32
                $this->addOrder($column, $order);
326
            }
327
        }
328 32
        foreach ($this->allowFilters as $column) {
329 32
            $alias = $this->applyAlias($column);
330 32
            $filter = Request::getParam($this->prefix . 'filter-' . $alias);
331
332 32
            if (null !== $filter) {
333 1
                $filter = trim($filter, ' _-');
334 1
                if (strpos($filter, '-')) {
335
                    /**
336
                     * Example of filters
337
                     * - http://domain.com/users/grid/users-filter-roleId/gt-2      - roleId greater than 2
338
                     * - http://domain.com/users/grid/users-filter-roleId/gt-1_lt-4 - 1 < roleId < 4
339
                     * - http://domain.com/users/grid/users-filter-login/eq-admin   - login == admin
340
                     * - http://domain.com/users/grid/users-filter-login/like-adm   - login LIKE `adm`
341
                     * - http://domain.com/users/grid/users-filter-login/like-od-   - login LIKE `od-`
342
                     */
343 1
                    $filters = explode('_', $filter);
344 1
                    foreach ($filters as $rawFilter) {
345 1
                        [$filterName, $filterValue] = explode('-', $rawFilter, 2);
0 ignored issues
show
Bug introduced by
The variable $filterName does not exist. Did you mean $filter?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $filterValue does not exist. Did you mean $filter?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
346 1
                        $this->addFilter($column, $filterName, $filterValue);
0 ignored issues
show
Bug introduced by
The variable $filterName does not exist. Did you mean $filter?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $filterValue does not exist. Did you mean $filter?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
347
                    }
348
                } else {
349
                    /**
350
                     * Example of filters
351
                     * - http://domain.com/users/grid/users-filter-roleId/2
352
                     * - http://domain.com/users/grid/users-filter-login/admin
353
                     */
354 32
                    $this->addFilter($column, self::FILTER_EQ, $filter);
355
                }
356
            }
357
        }
358 32
    }
359
360
    /**
361
     * Process source
362
     *
363
     * @return void
364
     * @throws GridException
365
     */
366 15
    public function processSource(): void
367
    {
368 15
        if (null === $this->adapter) {
369
            throw new GridException('Grid Adapter is not initiated, please update method `init()` and try again');
370
        }
371
372
        try {
373 15
            $this->data = $this->getAdapter()->process(
374 15
                $this->getPage(),
375 15
                $this->getLimit(),
376 15
                $this->getFilters(),
377 15
                $this->getOrders()
378
            );
379
        } catch (\Exception $e) {
380
            throw new GridException('Grid Adapter can\'t process request: '. $e->getMessage());
381
        }
382 15
    }
383
384
    /**
385
     * Get data
386
     *
387
     * @return Data
388
     * @throws \Bluz\Grid\GridException
389
     */
390 15
    public function getData(): Data
391
    {
392 15
        if (!$this->data) {
393 15
            $this->processSource();
394
        }
395 15
        return $this->data;
396
    }
397
398
    /**
399
     * Setup params
400
     *
401
     * @param  $params
402
     *
403
     * @return void
404
     */
405
    public function setParams($params): void
406
    {
407
        $this->params = $params;
408
    }
409
410
    /**
411
     * Return params prepared for url builder
412
     *
413
     * @param  array $rewrite
414
     *
415
     * @return array
416
     */
417 13
    public function getParams(array $rewrite = []): array
418
    {
419 13
        $params = $this->params;
420
421
        // change page to first for each new grid (with new filters or orders, or other stuff)
422 13
        $page = $rewrite['page'] ?? 1;
423
424 13
        if ($page > 1) {
425 4
            $params[$this->prefix . 'page'] = $page;
426
        }
427
428
        // change limit
429 13
        $limit = $rewrite['limit'] ?? $this->getLimit();
430
431 13
        if ($limit !== $this->defaultLimit) {
432 1
            $params[$this->prefix . 'limit'] = $limit;
433
        }
434
435
        // change orders
436 13
        $orders = $rewrite['orders'] ?? $this->getOrders();
437
438 13
        foreach ($orders as $column => $order) {
439 3
            $column = $this->applyAlias($column);
440 3
            $params[$this->prefix . 'order-' . $column] = $order;
441
        }
442
443
        // change filters
444 13
        $filters = $rewrite['filters'] ?? $this->getFilters();
445
446 13
        foreach ($filters as $column => $columnFilters) {
447
            /** @var array $columnFilters */
448 3
            $column = $this->applyAlias($column);
449 3
            if (\count($columnFilters) === 1 && isset($columnFilters[self::FILTER_EQ])) {
450 2
                $params[$this->prefix . 'filter-' . $column] = $columnFilters[self::FILTER_EQ];
451 2
                continue;
452
            }
453
454 2
            $columnFilter = [];
455 2
            foreach ($columnFilters as $filterName => $filterValue) {
456 2
                $columnFilter[] = $filterName . '-' . $filterValue;
457
            }
458 2
            $params[$this->prefix . 'filter-' . $column] = implode('_', $columnFilter);
459
        }
460 13
        return $params;
461
    }
462
463
    /**
464
     * Get Url
465
     *
466
     * @param  array $params
467
     *
468
     * @return string
469
     */
470 13
    public function getUrl($params): string
471
    {
472
        // prepare params
473 13
        $params = $this->getParams($params);
474
475
        // retrieve URL
476 13
        return Router::getUrl(
477 13
            $this->getModule(),
478 13
            $this->getController(),
479 13
            $params
480
        );
481
    }
482
483
    /**
484
     * Add column name for allow order
485
     *
486
     * @param string $column
487
     *
488
     * @return void
489
     */
490 32
    public function addAllowOrder($column): void
491
    {
492 32
        $this->allowOrders[] = $column;
493 32
    }
494
495
    /**
496
     * Set allow orders
497
     *
498
     * @param  string[] $orders
499
     *
500
     * @return void
501
     */
502 32
    public function setAllowOrders(array $orders = []): void
503
    {
504 32
        $this->allowOrders = [];
505 32
        foreach ($orders as $column) {
506 32
            $this->addAllowOrder($column);
507
        }
508 32
    }
509
510
    /**
511
     * Get allow orders
512
     *
513
     * @return array
514
     */
515 3
    public function getAllowOrders(): array
516
    {
517 3
        return $this->allowOrders;
518
    }
519
520
    /**
521
     * Check order column
522
     *
523
     * @param  string $column
524
     *
525
     * @return bool
526
     */
527 3
    protected function checkOrderColumn($column): bool
528
    {
529 3
        return \in_array($column, $this->getAllowOrders(), true);
530
    }
531
532
    /**
533
     * Check order name
534
     *
535
     * @param  string $order
536
     *
537
     * @return bool
538
     */
539 2
    protected function checkOrderName($order): bool
540
    {
541 2
        return ($order === self::ORDER_ASC || $order === self::ORDER_DESC);
542
    }
543
544
    /**
545
     * Add order rule
546
     *
547
     * @param  string $column
548
     * @param  string $order
549
     *
550
     * @return void
551
     * @throws GridException
552
     */
553 3
    public function addOrder($column, $order = self::ORDER_ASC): void
554
    {
555 3
        if (!$this->checkOrderColumn($column)) {
556
            throw new GridException("Order for column `$column` is not allowed");
557
        }
558
559 3
        if (!$this->checkOrderName($order)) {
560
            throw new GridException("Order name for column `$column` is incorrect");
561
        }
562
563 3
        $this->orders[$column] = $order;
564 3
    }
565
566
    /**
567
     * Add order rules
568
     *
569
     * @param  array $orders
570
     *
571
     * @return void
572
     * @throws GridException
573
     */
574 1
    public function addOrders(array $orders): void
575
    {
576 1
        foreach ($orders as $column => $order) {
577 1
            $this->addOrder($column, $order);
578
        }
579 1
    }
580
581
    /**
582
     * Set order
583
     *
584
     * @param  string $column
585
     * @param  string $order ASC or DESC
586
     *
587
     * @return void
588
     * @throws GridException
589
     */
590 1
    public function setOrder($column, $order = self::ORDER_ASC): void
591
    {
592 1
        $this->orders = [];
593 1
        $this->addOrder($column, $order);
594 1
    }
595
596
    /**
597
     * Set orders
598
     *
599
     * @param  array $orders
600
     *
601
     * @return void
602
     * @throws GridException
603
     */
604 1
    public function setOrders(array $orders): void
605
    {
606 1
        $this->orders = [];
607 1
        $this->addOrders($orders);
608 1
    }
609
610
    /**
611
     * Get orders
612
     *
613
     * @return array
614
     */
615 25
    public function getOrders(): array
616
    {
617 25
        if (empty($this->orders)) {
618 22
            return $this->getDefaultOrder();
619
        }
620
621 3
        return $this->orders;
622
    }
623
624
    /**
625
     * Add column name to allow filter it
626
     *
627
     * @param string $column
628
     *
629
     * @return void
630
     */
631 32
    public function addAllowFilter($column): void
632
    {
633 32
        $this->allowFilters[] = $column;
634 32
    }
635
636
    /**
637
     * Set allowed filters
638
     *
639
     * @param  string[] $filters
640
     *
641
     * @return void
642
     */
643 32
    public function setAllowFilters(array $filters = []): void
644
    {
645 32
        $this->allowFilters = [];
646 32
        foreach ($filters as $column) {
647 32
            $this->addAllowFilter($column);
648
        }
649 32
    }
650
651
    /**
652
     * Get allow filters
653
     *
654
     * @return array
655
     */
656 12
    public function getAllowFilters(): array
657
    {
658 12
        return $this->allowFilters;
659
    }
660
661
    /**
662
     * Check filter column
663
     *
664
     * @param  string $column
665
     *
666
     * @return bool
667
     */
668 12
    protected function checkFilterColumn($column): bool
669
    {
670 12
        return array_key_exists($column, $this->getAllowFilters()) ||
671 12
            \in_array($column, $this->getAllowFilters(), false);
672
    }
673
674
    /**
675
     * Check filter
676
     *
677
     * @param  string $filter
678
     *
679
     * @return bool
680
     */
681 12
    protected function checkFilterName($filter): bool
682
    {
683 12
        return \in_array($filter, $this->allowFilterNames, false);
684
    }
685
686
    /**
687
     * Add filter
688
     *
689
     * @param  string $column name
690
     * @param  string $filter
691
     * @param  string $value
692
     *
693
     * @return void
694
     * @throws GridException
695
     */
696 10
    public function addFilter($column, $filter, $value): void
697
    {
698 10
        if (!$this->checkFilterColumn($column)) {
699 1
            throw new GridException("Filter for column `$column` is not allowed");
700
        }
701 9
        if (!$this->checkFilterName($filter)) {
702 1
            throw new GridException('Filter name is incorrect');
703
        }
704 8
        if (!isset($this->filters[$column])) {
705 8
            $this->filters[$column] = [];
706
        }
707 8
        $this->filters[$column][$filter] = $value;
708 8
    }
709
710
711
    /**
712
     * Get filter
713
     *
714
     * @param  string $column
715
     * @param  string $filter
716
     *
717
     * @return mixed
718
     */
719
    public function getFilter($column, $filter = null)
720
    {
721
        if (null === $filter) {
722
            return $this->filters[$column] ?? null;
723
        }
724
        return $this->filters[$column][$filter] ?? null;
725
    }
726
727
    /**
728
     * Get filters
729
     *
730
     * @return array
731
     */
732 21
    public function getFilters(): array
733
    {
734 21
        return $this->filters;
735
    }
736
737
    /**
738
     * Add alias for column name
739
     *
740
     * @param  string $column
741
     * @param  string $alias
742
     *
743
     * @return void
744
     */
745 30
    public function addAlias($column, $alias): void
746
    {
747 30
        $this->aliases[$column] = $alias;
748 30
    }
749
750
    /**
751
     * Get column name by alias
752
     *
753
     * @param  string $alias
754
     *
755
     * @return string
756
     */
757
    protected function reverseAlias($alias): string
758
    {
759
        return array_search($alias, $this->aliases, true) ?: $alias;
760
    }
761
762
    /**
763
     * Get alias by column name
764
     *
765
     * @param  string $column
766
     *
767
     * @return string
768
     */
769 32
    public function applyAlias($column): string
770
    {
771 32
        return $this->aliases[$column] ?? $column;
772
    }
773
774
    /**
775
     * Set page
776
     *
777
     * @param  integer $page
778
     *
779
     * @return void
780
     * @throws GridException
781
     */
782 32
    public function setPage(int $page = 1): void
783
    {
784 32
        if ($page < 1) {
785 1
            throw new GridException('Wrong page number, should be greater than zero');
786
        }
787 32
        $this->page = $page;
788 32
    }
789
790
    /**
791
     * Get page
792
     *
793
     * @return integer
794
     */
795 17
    public function getPage(): int
796
    {
797 17
        return $this->page;
798
    }
799
800
    /**
801
     * Set limit per page
802
     *
803
     * @param  integer $limit
804
     *
805
     * @return void
806
     * @throws GridException
807
     */
808 32
    public function setLimit(int $limit): void
809
    {
810 32
        if ($limit < 1) {
811 1
            throw new GridException('Wrong limit value, should be greater than zero');
812
        }
813 32
        $this->limit = $limit;
814 32
    }
815
816
    /**
817
     * Get limit per page
818
     *
819
     * @return integer
820
     */
821 24
    public function getLimit(): int
822
    {
823 24
        return $this->limit;
824
    }
825
826
    /**
827
     * Set default limit
828
     *
829
     * @param  integer $limit
830
     *
831
     * @return void
832
     * @throws GridException
833
     */
834 32
    public function setDefaultLimit(int $limit): void
835
    {
836 32
        if ($limit < 1) {
837 1
            throw new GridException('Wrong default limit value, should be greater than zero');
838
        }
839 32
        $this->setLimit($limit);
840
841 32
        $this->defaultLimit = $limit;
842 32
    }
843
844
    /**
845
     * Get default limit
846
     *
847
     * @return integer
848
     */
849 1
    public function getDefaultLimit(): int
850
    {
851 1
        return $this->defaultLimit;
852
    }
853
854
    /**
855
     * Set default order
856
     *
857
     * @param  string $column
858
     * @param  string $order ASC or DESC
859
     *
860
     * @return void
861
     */
862 3
    public function setDefaultOrder($column, $order = self::ORDER_ASC): void
863
    {
864 3
        $this->defaultOrder = [$column => $order];
865 3
    }
866
867
    /**
868
     * Get default order
869
     *
870
     * @return array
871
     */
872 22
    public function getDefaultOrder(): ?array
873
    {
874 22
        return $this->defaultOrder;
875
    }
876
}
877