Completed
Pull Request — master (#373)
by Anton
04:30
created

Grid   D

Complexity

Total Complexity 91

Size/Duplication

Total Lines 779
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 87.04%

Importance

Changes 0
Metric Value
dl 0
loc 779
ccs 188
cts 216
cp 0.8704
rs 4.4444
c 0
b 0
f 0
wmc 91
lcom 1
cbo 6

41 Methods

Rating   Name   Duplication   Size   Complexity  
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
A getData() 0 7 2
A getSettings() 0 9 1
A setParams() 0 4 1
A __construct() 0 19 3
C processRequest() 0 44 8
A processSource() 0 14 3
C getParams() 0 45 11
A getUrl() 0 12 1
A setAllowOrders() 0 4 1
A getAllowOrders() 0 4 1
A addOrder() 0 15 4
A addOrders() 0 6 2
A setOrder() 0 5 1
A setOrders() 0 5 1
B getOrders() 0 15 5
A setAllowFilters() 0 4 1
A getAllowFilters() 0 4 1
B checkFilter() 0 16 10
B addFilter() 0 21 5
A getFilter() 0 16 4
A getFilters() 0 4 1
A addAlias() 0 4 1
A setAliases() 0 4 1
A applyAlias() 0 4 2
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
    const FILTER_NUM = 'num'; // ==, !=, >, >=, <, <=
49
50
    const FILTER_EQ = 'eq'; // equal to ..
51
    const FILTER_NE = 'ne'; // not equal to ..
52
    const FILTER_GT = 'gt'; // greater than ..
53
    const FILTER_GE = 'ge'; // greater than .. or equal
54
    const FILTER_LT = 'lt'; // less than ..
55
    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 aliases for columns in DB
149
     *
150
     * @var array
151
     */
152
    protected $aliases = [];
153
154
    /**
155
     * Grid constructor
156
     *
157
     * @param array $options
158
     */
159 33
    public function __construct($options = null)
160
    {
161 33
        if ($options) {
162
            $this->setOptions($options);
163
        }
164
165 33
        if ($this->uid) {
166 33
            $this->prefix = $this->getUid() . '-';
167
        } else {
168
            $this->prefix = '';
169
        }
170
171 33
        $this->init();
172
173 33
        $this->processRequest();
174
175
        // initial default helper path
176 33
        $this->addHelperPath(dirname(__FILE__) . '/Helper/');
177 33
    }
178
179
    /**
180
     * Initialize Grid
181
     *
182
     * @return Grid
183
     */
184
    abstract public function init();
185
186
    /**
187
     * Set source adapter
188
     *
189
     * @param Source\AbstractSource $adapter
190
     * @return void
191
     */
192 33
    public function setAdapter(Source\AbstractSource $adapter)
193
    {
194 33
        $this->adapter = $adapter;
195 33
    }
196
197
    /**
198
     * Get source adapter
199
     *
200
     * @return Source\AbstractSource
201
     * @throws GridException
202
     */
203 15
    public function getAdapter()
204
    {
205 15
        if (null == $this->adapter) {
206
            throw new GridException('Grid adapter is not initialized');
207
        }
208 15
        return $this->adapter;
209
    }
210
211
    /**
212
     * Get unique Grid Id
213
     *
214
     * @return string
215
     */
216 33
    public function getUid()
217
    {
218 33
        return $this->uid;
219
    }
220
221
    /**
222
     * Get prefix
223
     *
224
     * @return string
225
     */
226 1
    public function getPrefix()
227
    {
228 1
        return $this->prefix;
229
    }
230
231
    /**
232
     * Set module
233
     *
234
     * @param string $module
235
     * @return void
236
     */
237 1
    public function setModule($module)
238
    {
239 1
        $this->module = $module;
240 1
    }
241
242
    /**
243
     * Get module
244
     *
245
     * @return string
246
     */
247 11
    public function getModule()
248
    {
249 11
        return $this->module;
250
    }
251
252
    /**
253
     * Set controller
254
     *
255
     * @param  string $controller
256
     * @return void
257
     */
258 1
    public function setController($controller)
259
    {
260 1
        $this->controller = $controller;
261 1
    }
262
263
    /**
264
     * Get controller
265
     *
266
     * @return string
267
     */
268 11
    public function getController()
269
    {
270 11
        return $this->controller;
271
    }
272
273
    /**
274
     * Process request
275
     *
276
     * Example of request url
277
     * - http://domain.com/pages/grid/
278
     * - http://domain.com/pages/grid/page/2/
279
     * - http://domain.com/pages/grid/page/2/order-alias/desc/
280
     * - http://domain.com/pages/grid/page/2/order-created/desc/order-alias/asc/
281
     *
282
     * with prefix for support more than one grid on page
283
     * - http://domain.com/users/grid/users-page/2/users-order-created/desc/
284
     * - http://domain.com/users/grid/users-page/2/users-filter-status/active/
285
     *
286
     * hash support
287
     * - http://domain.com/pages/grid/#/page/2/order-created/desc/order-alias/asc/
288
     *
289
     * @return Grid
290
     */
291 33
    public function processRequest()
292
    {
293 33
        $this->module = Request::getModule();
294 33
        $this->controller = Request::getController();
295
296 33
        $page = Request::getParam($this->prefix . 'page', 1);
297 33
        $this->setPage($page);
298
299 33
        $limit = Request::getParam($this->prefix . 'limit', $this->limit);
300 33
        $this->setLimit($limit);
301
302 33
        foreach ($this->allowOrders as $column) {
303 33
            $order = Request::getParam($this->prefix . 'order-' . $column);
304 33
            if ($order) {
305 33
                $this->addOrder($column, $order);
306
            }
307
        }
308 33
        foreach ($this->allowFilters as $column) {
309 33
            $filter = Request::getParam($this->prefix . 'filter-' . $column);
310
311 33
            if ($filter) {
312 1
                if (strpos($filter, '-')) {
313 1
                    $filter = trim($filter, ' -');
314
315 1
                    while ($pos = strpos($filter, '-')) {
316 1
                        $filterType = substr($filter, 0, $pos);
317 1
                        $filter = substr($filter, $pos + 1);
318
319 1
                        if (strpos($filter, '-')) {
320
                            $filterValue = substr($filter, 0, strpos($filter, '-'));
321
                            $filter = substr($filter, strpos($filter, '-') + 1);
322
                        } else {
323 1
                            $filterValue = $filter;
324
                        }
325
326 1
                        $this->addFilter($column, $filterType, $filterValue);
327
                    }
328
                } else {
329 33
                    $this->addFilter($column, self::FILTER_EQ, $filter);
330
                }
331
            }
332
        }
333 33
        return $this;
334
    }
335
336
    /**
337
     * Process source
338
     *
339
     * @return self
340
     * @throws GridException
341
     */
342 15
    public function processSource()
343
    {
344 15
        if (null === $this->adapter) {
345
            throw new GridException("Grid Adapter is not initiated, please update method init() and try again");
346
        }
347
348
        try {
349 15
            $this->data = $this->getAdapter()->process($this->getSettings());
350
        } catch (\Exception $e) {
351
            throw new GridException("Grid Adapter can't process request: ". $e->getMessage());
352
        }
353
354 15
        return $this;
355
    }
356
357
    /**
358
     * Get data
359
     *
360
     * @return Data
361
     */
362 15
    public function getData()
363
    {
364 15
        if (!$this->data) {
365 15
            $this->processSource();
366
        }
367 15
        return $this->data;
368
    }
369
370
    /**
371
     * Get settings
372
     *
373
     * @return array
374
     */
375 15
    public function getSettings()
376
    {
377 15
        $settings = [];
378 15
        $settings['page'] = $this->getPage();
379 15
        $settings['limit'] = $this->getLimit();
380 15
        $settings['orders'] = $this->getOrders();
381 15
        $settings['filters'] = $this->getFilters();
382 15
        return $settings;
383
    }
384
385
    /**
386
     * Setup params
387
     *
388
     * @param  $params
389
     * @return void
390
     */
391
    public function setParams($params)
392
    {
393
        $this->params = $params;
394
    }
395
396
    /**
397
     * Return params prepared for url builder
398
     *
399
     * @param  array $rewrite
400
     * @return array
401
     */
402 11
    public function getParams(array $rewrite = [])
403
    {
404 11
        $params = $this->params;
405
406
        // change page
407 11
        if (isset($rewrite['page']) && $rewrite['page'] > 1) {
408 4
            $params[$this->prefix . 'page'] = $rewrite['page'];
409
        }
410
411
        // change limit
412 11
        if (isset($rewrite['limit'])) {
413 1
            if ($rewrite['limit'] != $this->defaultLimit) {
414 1
                $params[$this->prefix . 'limit'] = ($rewrite['limit'] != $this->limit)
415 1
                    ? $rewrite['limit'] : $this->limit;
416
            }
417
        } else {
418 10
            if ($this->limit != $this->defaultLimit) {
419
                $params[$this->prefix . 'limit'] = $this->limit;
420
            }
421
        }
422
423
        // change orders
424 11
        $orders = $rewrite['orders'] ?? $this->getOrders();
425 2
426
        foreach ($orders as $column => $order) {
427 9
            $params[$this->prefix . 'order-' . $column] = $order;
428
        }
429
430 11
        // change filters
431 2
        $filters = $rewrite['filters'] ?? $this->getFilters();
432
433
        foreach ($filters as $column => $columnFilters) {
434
            $columnFilter = [];
435 11
            foreach ($columnFilters as $filterName => $filterValue) {
436 1
                if ($filterName == self::FILTER_EQ) {
437
                    $columnFilter[] = $filterValue;
438 10
                } else {
439
                    $columnFilter[] = $filterName . '-' . $filterValue;
440 11
                }
441 1
            }
442 1
            $params[$this->prefix . 'filter-' . $column] = join('-', $columnFilter);
443 1
        }
444
445
        return $params;
446 1
    }
447
448
    /**
449 1
     * Get Url
450
     *
451
     * @param  array $params
452 11
     * @return string
453
     */
454
    public function getUrl($params)
455
    {
456
        // prepare params
457
        $params = $this->getParams($params);
458
459
        // retrieve URL
460
        return Router::getUrl(
461 11
            $this->getModule(),
462
            $this->getController(),
463
            $params
464 11
        );
465
    }
466
467 11
    /**
468 11
     * Set allow orders
469 11
     *
470
     * @param  string[] $orders
471
     * @return void
472
     */
473
    public function setAllowOrders(array $orders = [])
474
    {
475
        $this->allowOrders = $orders;
476
    }
477
478
    /**
479
     * Get allow orders
480 33
     *
481
     * @return array
482 33
     */
483 33
    public function getAllowOrders()
484
    {
485
        return $this->allowOrders;
486
    }
487
488
    /**
489
     * Add order rule
490 2
     *
491
     * @param  string $column
492 2
     * @param  string $order
493
     * @return void
494
     * @throws GridException
495
     */
496
    public function addOrder($column, $order = Grid::ORDER_ASC)
497
    {
498
        if (!in_array($column, $this->allowOrders)) {
499
            throw new GridException('Wrong column order');
500
        }
501
502
        if (strtolower($order) != Grid::ORDER_ASC
503 8
            && strtolower($order) != Grid::ORDER_DESC
504
        ) {
505 8
            throw new GridException('Order for column "' . $column . '" is incorrect');
506 1
        }
507
        $column = $this->applyAlias($column);
508
509 7
        $this->orders[$column] = $order;
510 7
    }
511
512 1
    /**
513
     * Add order rules
514 6
     *
515
     * @param  array $orders
516 6
     * @return void
517 6
     */
518
    public function addOrders(array $orders)
519
    {
520
        foreach ($orders as $column => $order) {
521
            $this->addOrder($column, $order);
522
        }
523
    }
524
525 1
    /**
526
     * Set order
527 1
     *
528 1
     * @param  string $column
529
     * @param  string $order  ASC or DESC
530 1
     * @return void
531
     */
532
    public function setOrder($column, $order = Grid::ORDER_ASC)
533
    {
534
        $this->orders = [];
535
        $this->addOrder($column, $order);
536
    }
537
538
    /**
539 6
     * Set orders
540
     *
541 6
     * @param  array $orders
542 6
     * @return void
543 4
     */
544
    public function setOrders(array $orders)
545
    {
546
        $this->orders = [];
547
        $this->addOrders($orders);
548
    }
549
550
    /**
551 1
     * Get orders
552
     *
553 1
     * @return array
554 1
     */
555 1
    public function getOrders()
556
    {
557
        $default = $this->getDefaultOrder();
558
559
        // remove default order when another one is set
560
        if (is_array($default)
561
            && count($this->orders) > 1
562 24
            && isset($this->orders[key($default)])
563
            && $this->orders[key($default)] == reset($default)
564 24
        ) {
565
            unset($this->orders[key($default)]);
566
        }
567 24
568 24
        return $this->orders;
569 24
    }
570 24
571
    /**
572
     * Set allowed filters
573
     *
574
     * @param  string[] $filters
575 24
     * @return void
576
     */
577
    public function setAllowFilters(array $filters = [])
578
    {
579
        $this->allowFilters = $filters;
580
    }
581
582
    /**
583
     * Get allow filters
584 33
     *
585
     * @return array
586 33
     */
587 33
    public function getAllowFilters()
588
    {
589
        return $this->allowFilters;
590
    }
591
592
    /**
593
     * Check filter
594 3
     *
595
     * @param  string $filter
596 3
     * @return bool
597
     */
598
    public function checkFilter($filter)
599
    {
600
        if ($filter == self::FILTER_EQ ||
601
            $filter == self::FILTER_NE ||
602
            $filter == self::FILTER_GT ||
603
            $filter == self::FILTER_GE ||
604
            $filter == self::FILTER_LT ||
605 10
            $filter == self::FILTER_LE ||
606
            $filter == self::FILTER_NUM ||
607 10
            $filter == self::FILTER_ENUM ||
608 9
            $filter == self::FILTER_LIKE
609 6
        ) {
610 6
            return true;
611 6
        }
612 6
        return false;
613 5
    }
614 5
615 10
    /**
616
     * Add filter
617 8
     *
618
     * @param  string $column
619 2
     * @param  string $filter
620
     * @param  string $value
621
     * @return void
622
     * @throws GridException
623
     */
624
    public function addFilter($column, $filter, $value)
625
    {
626
        if (!in_array($column, $this->allowFilters) &&
627
            !array_key_exists($column, $this->allowFilters)
628
        ) {
629
            throw new GridException('Wrong column name for filter');
630
        }
631 10
632
        $filter = strtolower($filter);
633 10
634 10
        if (!$this->checkFilter($filter)) {
635
            throw new GridException('Wrong filter name');
636 1
        }
637
638
        $column = $this->applyAlias($column);
639 9
        
640
        if (!isset($this->filters[$column])) {
641 9
            $this->filters[$column] = [];
642 1
        }
643
        $this->filters[$column][$filter] = $value;
644
    }
645 8
646
647 8
    /**
648 8
     * Get filter
649
     *
650 8
     * @param  string $column
651 8
     * @param  string $filter
652
     * @return mixed
653
     */
654
    public function getFilter($column, $filter = null)
655
    {
656
        if (isset($this->filters[$column])) {
657
            if ($filter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filter of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
658
                if (isset($this->filters[$column][$filter])) {
659
                    return $this->filters[$column][$filter];
660
                } else {
661
                    return null;
662
                }
663
            } else {
664
                return $this->filters[$column];
665
            }
666
        } else {
667
            return null;
668
        }
669
    }
670
671
    /**
672
     * Get filters
673
     *
674
     * @return array
675
     */
676
    public function getFilters()
677
    {
678
        return $this->filters;
679
    }
680
681
    /**
682
     * Add alias
683 23
     *
684
     * @param  string $key
685 23
     * @param  string $value
686
     * @return void
687
     */
688
    public function addAlias($key, $value)
689
    {
690
        $this->aliases[$key] = $value;
691
    }
692
693
    /**
694
     * Set aliases
695
     *
696
     * @param array $aliases
697
     * @return void
698
     */
699
    public function setAliases($aliases)
700
    {
701
        $this->aliases = $aliases;
702
    }
703
704
    /**
705
     * Apply Alias
706
     *
707
     * @param  string $key
708
     * @return string
709
     */
710
    protected function applyAlias($key)
711
    {
712
        return isset($this->aliases[$key])?$this->aliases[$key]:$key;
713
    }
714
715
    /**
716
     * Set page
717 11
     *
718
     * @param  integer $page
719 11
     * @return void
720
     * @throws GridException
721
     */
722
    public function setPage($page = 1)
723
    {
724
        if ($page < 1) {
725
            throw new GridException('Wrong page number, should be greater than zero');
726
        }
727
        $this->page = (int)$page;
728
    }
729 33
730
    /**
731 33
     * Get page
732 1
     *
733
     * @return integer
734 33
     */
735 33
    public function getPage()
736
    {
737
        return $this->page;
738
    }
739
740
    /**
741
     * Set limit per page
742 17
     *
743
     * @param  integer $limit
744 17
     * @return void
745
     * @throws GridException
746
     */
747
    public function setLimit($limit)
748
    {
749
        if ($limit < 1) {
750
            throw new GridException('Wrong limit value, should be greater than zero');
751
        }
752
        $this->limit = (int)$limit;
753
    }
754 33
755
    /**
756 33
     * Get limit per page
757 1
     *
758
     * @return integer
759 33
     */
760 33
    public function getLimit()
761
    {
762
        return $this->limit;
763
    }
764
765
    /**
766
     * Set default limit
767 16
     *
768
     * @param  integer $limit
769 16
     * @return void
770
     * @throws GridException
771
     */
772
    public function setDefaultLimit($limit)
773
    {
774
        if ($limit < 1) {
775
            throw new GridException('Wrong default limit value, should be greater than zero');
776
        }
777
        $this->setLimit($limit);
778
779 33
        $this->defaultLimit = (int)$limit;
780
    }
781 33
782 1
    /**
783
     * Get default limit
784 33
     *
785
     * @return integer
786 33
     */
787 33
    public function getDefaultLimit()
788
    {
789
        return $this->defaultLimit;
790
    }
791
792
    /**
793
     * Set default order
794 1
     *
795
     * @param  string $column
796 1
     * @param  string $order ASC or DESC
797
     * @return void
798
     * @throws GridException
799
     */
800
    public function setDefaultOrder($column, $order = Grid::ORDER_ASC)
801
    {
802
        $this->setOrder($column, $order);
803
804
        $this->defaultOrder = [$column => $order];
805
    }
806
807 5
    /**
808
     * Get default order
809 5
     *
810
     * @return array
811 3
     */
812 3
    public function getDefaultOrder()
813
    {
814
        return $this->defaultOrder;
815
    }
816
}
817