Completed
Push — master ( 7eb3b6...d299b5 )
by Anton
11s
created

Grid   D

Complexity

Total Complexity 84

Size/Duplication

Total Lines 835
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 92.86%

Importance

Changes 0
Metric Value
dl 0
loc 835
ccs 195
cts 210
cp 0.9286
rs 4.4444
c 0
b 0
f 0
wmc 84
lcom 1
cbo 6

46 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 3
init() 0 1 ?
A setAdapter() 0 4 1
A getAdapter() 0 7 2
A getUid() 0 4 1
A getPrefix() 0 4 1
A setModule() 0 4 1
A getModule() 0 4 1
A setController() 0 4 1
A getController() 0 4 1
C processRequest() 0 48 7
A processSource() 0 14 3
A getData() 0 7 2
A getSettings() 0 10 1
A setParams() 0 4 1
C getParams() 0 44 8
A getUrl() 0 12 1
A addAllowOrder() 0 4 1
A setAllowOrders() 0 7 2
A getAllowOrders() 0 4 1
A checkOrderColumn() 0 4 1
A checkOrderName() 0 4 2
A addOrder() 0 12 3
A addOrders() 0 6 2
A setOrder() 0 5 1
A setOrders() 0 5 1
B getOrders() 0 15 5
A addAllowFilter() 0 4 1
A setAllowFilters() 0 7 2
A getAllowFilters() 0 4 1
A checkFilterColumn() 0 10 3
A checkFilterName() 0 4 1
A addFilter() 0 13 4
A getFilter() 0 8 2
A getFilters() 0 4 1
A addAlias() 0 4 1
A reverseAlias() 0 4 2
A applyAlias() 0 4 1
A setPage() 0 7 2
A getPage() 0 4 1
A setLimit() 0 7 2
A getLimit() 0 4 1
A setDefaultLimit() 0 9 2
A getDefaultLimit() 0 4 1
A setDefaultOrder() 0 6 1
A getDefaultOrder() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Grid often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Grid, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link https://github.com/bluzphp/framework
7
 */
8
9
/**
10
 * @namespace
11
 */
12
namespace Bluz\Grid;
13
14
use Bluz\Common\Helper;
15
use Bluz\Common\Options;
16
use Bluz\Proxy\Request;
17
use Bluz\Proxy\Router;
18
19
/**
20
 * Grid
21
 *
22
 * @package  Bluz\Grid
23
 * @author   Anton Shevchuk
24
 * @link     https://github.com/bluzphp/framework/wiki/Grid
25
 *
26
 * @method string filter($column, $filter, $value, $reset = true)
27
 * @method string first()
28
 * @method string last()
29
 * @method string limit($limit = 25)
30
 * @method string next()
31
 * @method string order($column, $order = null, $defaultOrder = Grid::ORDER_ASC, $reset = true)
32
 * @method string page($page = 1)
33
 * @method string pages()
34
 * @method string prev()
35
 * @method string reset()
36
 * @method string total()
37
 */
38
abstract class Grid
39
{
40
    use Options;
41
    use Helper;
42
43
    const ORDER_ASC = 'asc';
44
    const ORDER_DESC = 'desc';
45
46
    const FILTER_LIKE = 'like'; // like
47
    const FILTER_ENUM = 'enum'; // one from .., .., ..
48
49
    const FILTER_EQ = 'eq'; // equal to ..
50
    const FILTER_NE = 'ne'; // not equal to ..
51
    const FILTER_GT = 'gt'; // greater than ..
52
    const FILTER_GE = 'ge'; // greater than .. or equal
53
    const FILTER_LT = 'lt'; // less than ..
54
    const FILTER_LE = 'le'; // less than .. or equal
55
56
    /**
57
     * @var Source\AbstractSource instance of Source
58
     */
59
    protected $adapter;
60
61
    /**
62
     * @var Data instance of Data
63
     */
64
    protected $data;
65
66
    /**
67
     * @var string unique identification of grid
68
     */
69
    protected $uid;
70
71
    /**
72
     * @var string unique prefix of grid
73
     */
74
    protected $prefix;
75
76
    /**
77
     * @var string location of Grid
78
     */
79
    protected $module;
80
81
    /**
82
     * @var string location of Grid
83
     */
84
    protected $controller;
85
86
    /**
87
     * @var array custom array params
88
     */
89
    protected $params = [];
90
91
    /**
92
     * @var integer start from first page
93
     */
94
    protected $page = 1;
95
96
    /**
97
     * @var integer limit per page
98
     */
99
    protected $limit = 25;
100
101
    /**
102
     * @var integer default value of page limit
103
     * @see Grid::$limit
104
     */
105
    protected $defaultLimit = 25;
106
107
    /**
108
     * List of orders
109
     *
110
     * Example
111
     *     'first' => 'ASC',
112
     *     'last' => 'ASC'
113
     *
114
     * @var array
115
     */
116
    protected $orders = [];
117
118
    /**
119
     * @var array default order
120
     * @see Grid::$orders
121
     */
122
    protected $defaultOrder;
123
124
    /**
125
     * @var array list of allow orders
126
     * @see Grid::$orders
127
     */
128
    protected $allowOrders = [];
129
130
    /**
131
     * @var array list of filters
132
     */
133
    protected $filters = [];
134
135
    /**
136
     * List of allow filters
137
     *
138
     * Example
139
     *     ['id', 'status' => ['active', 'disable']]
140
     *
141
     * @var array
142
     * @see Grid::$filters
143
     */
144
    protected $allowFilters = [];
145
146
    /**
147
     * List of allow filter names
148
     *
149
     * @var array
150
     * @see Grid::$filters
151
     */
152
    protected $allowFilterNames = [
153
        self::FILTER_LIKE,
154
        self::FILTER_ENUM,
155
        self::FILTER_EQ,
156
        self::FILTER_NE,
157
        self::FILTER_GT,
158
        self::FILTER_GE,
159
        self::FILTER_LT,
160
        self::FILTER_LE
161
    ];
162
163
    /**
164
     * List of aliases for columns in DB
165
     *
166
     * @var array
167
     */
168
    protected $aliases = [];
169
170
    /**
171
     * Grid constructor
172
     *
173
     * @param array $options
174
     */
175 34
    public function __construct($options = null)
176
    {
177 34
        if ($options) {
178
            $this->setOptions($options);
179
        }
180
181 34
        if ($this->getUid()) {
182 34
            $this->prefix = $this->getUid() . '-';
183
        } else {
184
            $this->prefix = '';
185
        }
186
187 34
        $this->init();
188
189 34
        $this->processRequest();
190
191
        // initial default helper path
192 34
        $this->addHelperPath(dirname(__FILE__) . '/Helper/');
193 34
    }
194
195
    /**
196
     * Initialize Grid
197
     *
198
     * @return Grid
199
     */
200
    abstract public function init();
201
202
    /**
203
     * Set source adapter
204
     *
205
     * @param Source\AbstractSource $adapter
206
     * @return void
207
     */
208 34
    public function setAdapter(Source\AbstractSource $adapter)
209
    {
210 34
        $this->adapter = $adapter;
211 34
    }
212
213
    /**
214
     * Get source adapter
215
     *
216
     * @return Source\AbstractSource
217
     * @throws GridException
218
     */
219 15
    public function getAdapter()
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 34
    public function getUid()
233
    {
234 34
        return $this->uid;
235
    }
236
237
    /**
238
     * Get prefix
239
     *
240
     * @return string
241
     */
242 1
    public function getPrefix()
243
    {
244 1
        return $this->prefix;
245
    }
246
247
    /**
248
     * Set module
249
     *
250
     * @param string $module
251
     * @return void
252
     */
253 1
    public function setModule($module)
254
    {
255 1
        $this->module = $module;
256 1
    }
257
258
    /**
259
     * Get module
260
     *
261
     * @return string
262
     */
263 13
    public function getModule()
264
    {
265 13
        return $this->module;
266
    }
267
268
    /**
269
     * Set controller
270
     *
271
     * @param  string $controller
272
     * @return void
273
     */
274 1
    public function setController($controller)
275
    {
276 1
        $this->controller = $controller;
277 1
    }
278
279
    /**
280
     * Get controller
281
     *
282
     * @return string
283
     */
284 13
    public function getController()
285
    {
286 13
        return $this->controller;
287
    }
288
289
    /**
290
     * Process request
291
     *
292
     * Example of request url
293
     * - http://domain.com/pages/grid/
294
     * - http://domain.com/pages/grid/page/2/
295
     * - http://domain.com/pages/grid/page/2/order-alias/desc/
296
     * - http://domain.com/pages/grid/page/2/order-created/desc/order-alias/asc/
297
     *
298
     * with prefix for support more than one grid on page
299
     * - http://domain.com/users/grid/users-page/2/users-order-created/desc/
300
     * - http://domain.com/users/grid/users-page/2/users-filter-status/active/
301
     *
302
     * hash support
303
     * - http://domain.com/pages/grid/#/page/2/order-created/desc/order-alias/asc/
304
     *
305
     * @return Grid
306
     */
307 34
    public function processRequest()
308
    {
309 34
        $this->module = Request::getModule();
310 34
        $this->controller = Request::getController();
311
312 34
        $page = Request::getParam($this->prefix . 'page', 1);
313 34
        $this->setPage($page);
314
315 34
        $limit = Request::getParam($this->prefix . 'limit', $this->limit);
316 34
        $this->setLimit($limit);
317
318 34
        foreach ($this->allowOrders as $column) {
319 34
            $alias = $this->applyAlias($column);
320 34
            $order = Request::getParam($this->prefix . 'order-' . $alias);
321 34
            if (!is_null($order)) {
322 34
                $this->addOrder($column, $order);
323
            }
324
        }
325 34
        foreach ($this->allowFilters as $column) {
326 34
            $alias = $this->applyAlias($column);
327 34
            $filter = Request::getParam($this->prefix . 'filter-' . $alias);
328
329 34
            if (!is_null($filter)) {
330 1
                $filter = trim($filter, ' -');
331 1
                if (strpos($filter, '-')) {
332
                    /**
333
                     * Example of filters
334
                     * - http://domain.com/users/grid/users-filter-roleId/gt-2      - roleId greater than 2
335
                     * - http://domain.com/users/grid/users-filter-roleId/gt-1-lt-4 - 1 < roleId < 4
336
                     * - http://domain.com/users/grid/users-filter-login/eq-admin   - login == admin
337
                     */
338 1
                    while ($filter) {
339 1
                        list($filterName, $filterValue, $filter) = array_pad(explode('-', $filter, 3), 3, null);
340 1
                        $this->addFilter($column, $filterName, $filterValue);
341
                    }
342
                } else {
343
                    /**
344
                     * Example of filters
345
                     * - http://domain.com/users/grid/users-filter-roleId/2
346
                     * - http://domain.com/users/grid/users-filter-login/admin
347
                     */
348 34
                    $this->addFilter($column, self::FILTER_EQ, $filter);
349
                }
350
            }
351
        }
352
353 34
        return $this;
354
    }
355
356
    /**
357
     * Process source
358
     *
359
     * @return self
360
     * @throws GridException
361
     */
362 15
    public function processSource()
363
    {
364 15
        if (null === $this->adapter) {
365
            throw new GridException("Grid Adapter is not initiated, please update method `init()` and try again");
366
        }
367
368
        try {
369 15
            $this->data = $this->getAdapter()->process($this->getSettings());
370
        } catch (\Exception $e) {
371
            throw new GridException("Grid Adapter can't process request: {$e->getMessage()}");
372
        }
373
374 15
        return $this;
375
    }
376
377
    /**
378
     * Get data
379
     *
380
     * @return Data
381
     */
382 15
    public function getData()
383
    {
384 15
        if (!$this->data) {
385 15
            $this->processSource();
386
        }
387 15
        return $this->data;
388
    }
389
390
    /**
391
     * Get settings
392
     *
393
     * @return array
394
     */
395 15
    public function getSettings()
396
    {
397
        $settings = [
398 15
            'page' => $this->getPage(),
399 15
            'limit' => $this->getLimit(),
400 15
            'orders' => $this->getOrders(),
401 15
            'filters' => $this->getFilters()
402
        ];
403 15
        return $settings;
404
    }
405
406
    /**
407
     * Setup params
408
     *
409
     * @param  $params
410
     * @return void
411
     */
412
    public function setParams($params)
413
    {
414
        $this->params = $params;
415
    }
416
417
    /**
418
     * Return params prepared for url builder
419
     *
420
     * @param  array $rewrite
421
     * @return array
422
     */
423 13
    public function getParams(array $rewrite = [])
424
    {
425 13
        $params = $this->params;
426
427
        // change page to first for each new grid (with new filters or orders, or other stuff)
428 13
        $page = $rewrite['page'] ?? 1;
429
430 13
        if ($page > 1) {
431 4
            $params[$this->prefix . 'page'] = $page;
432
        }
433
434
        // change limit
435 13
        $limit = $rewrite['limit'] ?? $this->getLimit();
436
437 13
        if ($limit != $this->defaultLimit) {
438 1
            $params[$this->prefix . 'limit'] = $limit;
439
        }
440
441
        // change orders
442 13
        $orders = $rewrite['orders'] ?? $this->getOrders();
443
444 13
        foreach ($orders as $column => $order) {
445 3
            $column = $this->applyAlias($column);
446 3
            $params[$this->prefix . 'order-' . $column] = $order;
447
        }
448
449
        // change filters
450 13
        $filters = $rewrite['filters'] ?? $this->getFilters();
451
452 13
        foreach ($filters as $column => $columnFilters) {
453 3
            $column = $this->applyAlias($column);
454 3
            if (sizeof($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] = join('-', $columnFilter);
464
        }
465 13
        return $params;
466
    }
467
468
    /**
469
     * Get Url
470
     *
471
     * @param  array $params
472
     * @return string
473
     */
474 13
    public function getUrl($params)
475
    {
476
        // prepare params
477 13
        $params = $this->getParams($params);
478
479
        // retrieve URL
480 13
        return Router::getUrl(
481 13
            $this->getModule(),
482 13
            $this->getController(),
483
            $params
484
        );
485
    }
486
487
    /**
488
     * Add column name for allow order
489
     *
490
     * @param string $column
491
     * @return void
492
     */
493 34
    public function addAllowOrder($column)
494
    {
495 34
        $this->allowOrders[] = $column;
496 34
    }
497
498
    /**
499
     * Set allow orders
500
     *
501
     * @param  string[] $orders
502
     * @return void
503
     */
504 34
    public function setAllowOrders(array $orders = [])
505
    {
506 34
        $this->allowOrders = [];
507 34
        foreach ($orders as $column) {
508 34
            $this->addAllowOrder($column);
509
        }
510 34
    }
511
512
    /**
513
     * Get allow orders
514
     *
515
     * @return array
516
     */
517 8
    public function getAllowOrders()
518
    {
519 8
        return $this->allowOrders;
520
    }
521
522
    /**
523
     * Check order column
524
     *
525
     * @param  string $column
526
     * @return bool
527
     */
528 8
    protected function checkOrderColumn($column)
529
    {
530 8
        return in_array($column, $this->getAllowOrders());
531
    }
532
533
    /**
534
     * Check order name
535
     *
536
     * @param  string $order
537
     * @return bool
538
     */
539 6
    protected function checkOrderName($order)
540
    {
541 6
        return ($order == Grid::ORDER_ASC || $order == Grid::ORDER_DESC);
542
    }
543
544
    /**
545
     * Add order rule
546
     *
547
     * @param  string $column
548
     * @param  string $order
549
     * @return void
550
     * @throws GridException
551
     */
552 8
    public function addOrder($column, $order = Grid::ORDER_ASC)
553
    {
554 8
        if (!$this->checkOrderColumn($column)) {
555 1
            throw new GridException("Order for column `$column` is not allowed");
556
        }
557
558 7
        if (!$this->checkOrderName($order)) {
559 1
            throw new GridException("Order name for column `$column` is incorrect");
560
        }
561
562 6
        $this->orders[$column] = $order;
563 6
    }
564
565
    /**
566
     * Add order rules
567
     *
568
     * @param  array $orders
569
     * @return void
570
     */
571 1
    public function addOrders(array $orders)
572
    {
573 1
        foreach ($orders as $column => $order) {
574 1
            $this->addOrder($column, $order);
575
        }
576 1
    }
577
578
    /**
579
     * Set order
580
     *
581
     * @param  string $column
582
     * @param  string $order  ASC or DESC
583
     * @return void
584
     */
585 6
    public function setOrder($column, $order = Grid::ORDER_ASC)
586
    {
587 6
        $this->orders = [];
588 6
        $this->addOrder($column, $order);
589 4
    }
590
591
    /**
592
     * Set orders
593
     *
594
     * @param  array $orders
595
     * @return void
596
     */
597 1
    public function setOrders(array $orders)
598
    {
599 1
        $this->orders = [];
600 1
        $this->addOrders($orders);
601 1
    }
602
603
    /**
604
     * Get orders
605
     *
606
     * @return array
607
     */
608 25
    public function getOrders()
609
    {
610 25
        $default = $this->getDefaultOrder();
611
612
        // remove default order when another one is set
613 25
        if (is_array($default)
614 25
            && sizeof($this->orders)
615 25
            && isset($this->orders[key($default)])
616 25
            && $this->orders[key($default)] == reset($default)
617
        ) {
618 3
            unset($this->orders[key($default)]);
619
        }
620
621 25
        return $this->orders;
622
    }
623
624
    /**
625
     * Add column name to allow filter it
626
     *
627
     * @param string $column
628
     * @return void
629
     */
630 34
    public function addAllowFilter($column)
631
    {
632 34
        $this->allowFilters[] = $column;
633 34
    }
634
635
    /**
636
     * Set allowed filters
637
     *
638
     * @param  string[] $filters
639
     * @return void
640
     */
641 34
    public function setAllowFilters(array $filters = [])
642
    {
643 34
        $this->allowFilters = [];
644 34
        foreach ($filters as $column) {
645 34
            $this->addAllowFilter($column);
646
        }
647 34
    }
648
649
    /**
650
     * Get allow filters
651
     *
652
     * @return array
653
     */
654 12
    public function getAllowFilters()
655
    {
656 12
        return $this->allowFilters;
657
    }
658
659
    /**
660
     * Check filter column
661
     *
662
     * @param  string $column
663
     * @return bool
664
     */
665 12
    protected function checkFilterColumn($column)
666
    {
667 12
        if (in_array($column, $this->getAllowFilters()) ||
668 12
            array_key_exists($column, $this->getAllowFilters())
669
        ) {
670 10
            return true;
671
        } else {
672 2
            return false;
673
        }
674
    }
675
676
    /**
677
     * Check filter
678
     *
679
     * @param  string $filter
680
     * @return bool
681
     */
682 12
    protected function checkFilterName($filter)
683
    {
684 12
        return in_array($filter, $this->allowFilterNames);
685
    }
686
687
    /**
688
     * Add filter
689
     *
690
     * @param  string $column name
691
     * @param  string $filter
692
     * @param  string $value
693
     * @return void
694
     * @throws GridException
695
     */
696 10
    public function addFilter($column, $filter, $value)
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
     * @return mixed
717
     */
718
    public function getFilter($column, $filter = null)
719
    {
720
        if (is_null($filter)) {
721
            return $this->filters[$column] ?? null;
722
        } else {
723
            return $this->filters[$column][$filter] ?? null;
724
        }
725
    }
726
727
    /**
728
     * Get filters
729
     *
730
     * @return array
731
     */
732 23
    public function getFilters()
733
    {
734 23
        return $this->filters;
735
    }
736
737
    /**
738
     * Add alias for column name
739
     *
740
     * @param  string $column
741
     * @param  string $alias
742
     * @return void
743
     */
744 32
    public function addAlias($column, $alias)
745
    {
746 32
        $this->aliases[$column] = $alias;
747 32
    }
748
749
    /**
750
     * Get column name by alias
751
     *
752
     * @param  string $alias
753
     * @return string
754
     */
755
    protected function reverseAlias($alias)
756
    {
757
        return array_search($alias, $this->aliases) ?: $alias;
758
    }
759
760
    /**
761
     * Get alias by column name
762
     *
763
     * @param  string $column
764
     * @return string
765
     */
766 34
    protected function applyAlias($column)
767
    {
768 34
        return $this->aliases[$column] ?? $column;
769
    }
770
771
    /**
772
     * Set page
773
     *
774
     * @param  integer $page
775
     * @return void
776
     * @throws GridException
777
     */
778 34
    public function setPage($page = 1)
779
    {
780 34
        if ($page < 1) {
781 1
            throw new GridException('Wrong page number, should be greater than zero');
782
        }
783 34
        $this->page = (int)$page;
784 34
    }
785
786
    /**
787
     * Get page
788
     *
789
     * @return integer
790
     */
791 17
    public function getPage()
792
    {
793 17
        return $this->page;
794
    }
795
796
    /**
797
     * Set limit per page
798
     *
799
     * @param  integer $limit
800
     * @return void
801
     * @throws GridException
802
     */
803 34
    public function setLimit($limit)
804
    {
805 34
        if ($limit < 1) {
806 1
            throw new GridException('Wrong limit value, should be greater than zero');
807
        }
808 34
        $this->limit = (int)$limit;
809 34
    }
810
811
    /**
812
     * Get limit per page
813
     *
814
     * @return integer
815
     */
816 24
    public function getLimit()
817
    {
818 24
        return $this->limit;
819
    }
820
821
    /**
822
     * Set default limit
823
     *
824
     * @param  integer $limit
825
     * @return void
826
     * @throws GridException
827
     */
828 34
    public function setDefaultLimit($limit)
829
    {
830 34
        if ($limit < 1) {
831 1
            throw new GridException('Wrong default limit value, should be greater than zero');
832
        }
833 34
        $this->setLimit($limit);
834
835 34
        $this->defaultLimit = (int)$limit;
836 34
    }
837
838
    /**
839
     * Get default limit
840
     *
841
     * @return integer
842
     */
843 1
    public function getDefaultLimit()
844
    {
845 1
        return $this->defaultLimit;
846
    }
847
848
    /**
849
     * Set default order
850
     *
851
     * @param  string $column
852
     * @param  string $order ASC or DESC
853
     * @return void
854
     * @throws GridException
855
     */
856 5
    public function setDefaultOrder($column, $order = Grid::ORDER_ASC)
857
    {
858 5
        $this->setOrder($column, $order);
859
860 3
        $this->defaultOrder = [$column => $order];
861 3
    }
862
863
    /**
864
     * Get default order
865
     *
866
     * @return array
867
     */
868 24
    public function getDefaultOrder()
869
    {
870 24
        return $this->defaultOrder;
871
    }
872
}
873