Passed
Push — develop ( c767f6...9879fb )
by Anton
11:49 queued 09:29
created

Grid::setDefaultOrder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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