Completed
Pull Request — master (#258)
by
unknown
10:36
created

Grid   F

Complexity

Total Complexity 153

Size/Duplication

Total Lines 972
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 22

Test Coverage

Coverage 92.29%

Importance

Changes 24
Bugs 3 Features 4
Metric Value
wmc 153
c 24
b 3
f 4
lcom 1
cbo 22
dl 0
loc 972
ccs 323
cts 350
cp 0.9229
rs 1.263

57 Methods

Rating   Name   Duplication   Size   Complexity  
A setCustomization() 0 4 1
A getDefaultFilter() 0 4 1
A getDefaultSort() 0 4 1
A getPerPageList() 0 4 1
A getPrimaryKey() 0 4 1
A getRememberState() 0 4 1
A getRowCallback() 0 4 1
A getActualFilter() 0 5 4
A getModel() 0 4 1
A setModel() 0 8 3
A setDefaultPerPage() 0 12 2
A setDefaultFilter() 0 5 1
A setPerPageList() 0 10 3
A setTranslator() 0 5 1
A setFilterRenderType() 0 10 2
A setPaginator() 0 5 1
A setPrimaryKey() 0 5 1
A setRememberState() 0 9 1
A setRowCallback() 0 5 1
A setClientSideOptions() 0 5 1
A setStrictMode() 0 5 1
A getCount() 0 8 2
A getDefaultPerPage() 0 8 2
A getPerPage() 0 6 2
C getData() 0 37 11
A getTranslator() 0 8 2
B getRememberSession() 0 13 5
A getTablePrototype() 0 9 2
B getFilterRenderType() 0 21 7
A getPaginator() 0 10 2
A setDefaultSort() 0 15 3
A setTemplateFile() 0 9 1
C getProperty() 0 30 8
A getPropertyAccessor() 0 8 2
A getRowPrototype() 0 17 4
A getClientSideOptions() 0 4 1
A isStrictMode() 0 4 1
A getCustomization() 0 8 2
B loadState() 0 12 6
A saveState() 0 5 2
A handleRefresh() 0 4 1
A handlePage() 0 4 1
A handleSort() 0 5 1
B handleFilter() 0 20 9
A handleReset() 0 15 2
A handlePerPage() 0 10 2
A reload() 0 9 2
A createTemplate() 0 8 1
B render() 0 30 6
A saveRememberState() 0 10 3
A applyFiltering() 0 5 1
C __getConditions() 0 27 7
C applySorting() 0 39 13
A applyPaging() 0 13 3
A createComponentForm() 0 18 1
A getItemsForCountSelect() 0 4 1
A __triggerUserNotice() 0 8 4

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
/**
4
 * This file is part of the Grido (http://grido.bugyik.cz)
5
 *
6
 * Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz)
7
 *
8
 * For the full copyright and license information, please view
9
 * the file LICENSE.md that was distributed with this source code.
10
 */
11
12
namespace Grido;
13
14
use Grido\Exception;
15
use Grido\Components\Paginator;
16
use Grido\Components\Columns\Column;
17
use Grido\Components\Filters\Filter;
18
use Grido\Components\Actions\Action;
19
20
use Symfony\Component\PropertyAccess\PropertyAccessor;
21
22
/**
23
 * Grido - DataGrid for Nette Framework.
24
 *
25
 * @package     Grido
26
 * @author      Petr Bugyík
27
 *
28
 * @property-read int $count
29 1
 * @property-read mixed $data
30
 * @property-read \Nette\Utils\Html $tablePrototype
31
 * @property-read PropertyAccessor $propertyAccessor
32
 * @property-read Customization $customization
33
 * @property-write string $templateFile
34
 * @property bool $rememberState
35
 * @property array $defaultPerPage
36
 * @property array $defaultFilter
37
 * @property array $defaultSort
38
 * @property array $perPageList
39
 * @property \Nette\Localization\ITranslator $translator
40 1
 * @property Paginator $paginator
41
 * @property string $primaryKey
42 1
 * @property string $filterRenderType
43
 * @property DataSources\IDataSource $model
44 1
 * @property callback $rowCallback
45
 * @property bool $strictMode
46 1
 * @method void onRegistered(Grid $grid)
47
 * @method void onRender(Grid $grid)
48
 * @method void onFetchData(Grid $grid)
49
 */
50 1
class Grid extends Components\Container
51 1
{
52
    /***** DEFAULTS ****/
53
    const BUTTONS = 'buttons';
54
55
    const CLIENT_SIDE_OPTIONS = 'grido-options';
56
57
    /** @var int @persistent */
58
    public $page = 1;
59
60
    /** @var int @persistent */
61
    public $perPage;
62
63
    /** @var array @persistent */
64
    public $sort = [];
65
66 1
    /** @var array @persistent */
67
    public $filter = [];
68
69
    /** @var array event on all grid's components registered */
70
    public $onRegistered;
71
72
    /** @var array event on render */
73
    public $onRender;
74
75
    /** @var array event for modifying data */
76
    public $onFetchData;
77
78
    /** @var callback returns tr html element; function($row, Html $tr) */
79
    protected $rowCallback;
80
81
    /** @var \Nette\Utils\Html */
82
    protected $tablePrototype;
83
84
    /** @var bool */
85
    protected $rememberState = FALSE;
86
87
    /** @var string */
88
    protected $rememberStateSectionName;
89
90
    /** @var string */
91
    protected $primaryKey = 'id';
92
93
    /** @var string */
94
    protected $filterRenderType;
95
96
    /** @var array */
97
    protected $perPageList = [10, 20, 30, 50, 100];
98
99
    /** @var int */
100
    protected $defaultPerPage = 20;
101
102
    /** @var array */
103
    protected $defaultFilter = [];
104
105
    /** @var array */
106
    protected $defaultSort = [];
107
108
    /** @var DataSources\IDataSource */
109
    protected $model;
110
111
    /** @var int total count of items */
112
    protected $count;
113
114
    /** @var mixed */
115
    protected $data;
116
117
    /** @var Paginator */
118
    protected $paginator;
119 1
120
    /** @var \Nette\Localization\ITranslator */
121
    protected $translator;
122
123
    /** @var PropertyAccessor */
124
    protected $propertyAccessor;
125
126
    /** @var bool */
127
    protected $strictMode = TRUE;
128
129
    /** @var array */
130
    protected $options = [
131
        self::CLIENT_SIDE_OPTIONS => []
132
    ];
133
134
    /** @var Customization */
135 1
    protected $customization;
136
137
    /**
138
     * Sets a model that implements the interface Grido\DataSources\IDataSource or data-source object.
139
     * @param mixed $model
140
     * @param bool $forceWrapper
141 1
     * @throws Exception
142
     * @return Grid
143
     */
144
    public function setModel($model, $forceWrapper = FALSE)
145
    {
146 1
        $this->model = $model instanceof DataSources\IDataSource && $forceWrapper === FALSE
147 1
            ? $model
148 1
            : new DataSources\Model($model);
149
150 1
        return $this;
151
    }
152
153 1
    /**
154
     * Sets the default number of items per page.
155
     * @param int $perPage
156
     * @return Grid
157
     */
158
    public function setDefaultPerPage($perPage)
159
    {
160 1
        $perPage = (int) $perPage;
161 1
        $this->defaultPerPage = $perPage;
162
163 1
        if (!in_array($perPage, $this->perPageList)) {
164 1
            $this->perPageList[] = $perPage;
165 1
            sort($this->perPageList);
166 1
        }
167
168 1
        return $this;
169
    }
170
171
    /**
172
     * Sets default filtering.
173
     * @param array $filter
174
     * @return Grid
175
     */
176
    public function setDefaultFilter(array $filter)
177
    {
178 1
        $this->defaultFilter = array_merge($this->defaultFilter, $filter);
179 1
        return $this;
180
    }
181
182
    /**
183
     * Sets default sorting.
184
     * @param array $sort
185
     * @return Grid
186
     * @throws Exception
187 1
     */
188
    public function setDefaultSort(array $sort)
189
    {
190 1
        static $replace = ['asc' => Column::ORDER_ASC, 'desc' => Column::ORDER_DESC];
191
192 1
        foreach ($sort as $column => $dir) {
193 1
            $dir = strtr(strtolower($dir), $replace);
194 1
            if (!in_array($dir, $replace)) {
195 1
                throw new Exception("Dir '$dir' for column '$column' is not allowed.");
196
            }
197
198 1
            $this->defaultSort[$column] = $dir;
199 1
        }
200
201 1
        return $this;
202
    }
203
204
    /**
205
     * Sets items to per-page select.
206
     * @param array $perPageList
207
     * @return Grid
208 1
     */
209
    public function setPerPageList(array $perPageList)
210
    {
211 1
        $this->perPageList = $perPageList;
212
213 1
        if ($this->hasFilters(FALSE) || $this->hasOperation(FALSE)) {
214 1
            $this['form']['count']->setItems($this->getItemsForCountSelect());
215 1
        }
216
217 1
        return $this;
218
    }
219
220
    /**
221
     * Sets translator.
222
     * @param \Nette\Localization\ITranslator $translator
223
     * @return Grid
224
     */
225
    public function setTranslator(\Nette\Localization\ITranslator $translator)
226
    {
227 1
        $this->translator = $translator;
228 1
        return $this;
229
    }
230
231
    /**
232
     * Sets type of filter rendering.
233
     * Defaults inner (Filter::RENDER_INNER) if column does not exist then outer filter (Filter::RENDER_OUTER).
234
     * @param string $type
235 1
     * @throws Exception
236
     * @return Grid
237
     */
238
    public function setFilterRenderType($type)
239
    {
240 1
        $type = strtolower($type);
241 1
        if (!in_array($type, [Filter::RENDER_INNER, Filter::RENDER_OUTER])) {
242 1
            throw new Exception('Type must be Filter::RENDER_INNER or Filter::RENDER_OUTER.');
243
        }
244
245 1
        $this->filterRenderType = $type;
246 1
        return $this;
247
    }
248
249
    /**
250
     * Sets custom paginator.
251
     * @param Paginator $paginator
252
     * @return Grid
253
     */
254
    public function setPaginator(Paginator $paginator)
255
    {
256 1
        $this->paginator = $paginator;
257 1
        return $this;
258
    }
259
260
    /**
261
     * Sets grid primary key.
262
     * Defaults is "id".
263
     * @param string $key
264
     * @return Grid
265
     */
266
    public function setPrimaryKey($key)
267
    {
268 1
        $this->primaryKey = $key;
269 1
        return $this;
270
    }
271
272
    /**
273
     * Sets file name of custom template.
274
     * @param string $file
275
     * @return Grid
276
     */
277
    public function setTemplateFile($file)
278
    {
279 1
        $this->onRender[] = function() use ($file) {
280 1
            $this->template->gridoTemplate = $this->getTemplate()->getFile();
0 ignored issues
show
Bug introduced by
Accessing gridoTemplate on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
281 1
            $this->getTemplate()->setFile($file);
282 1
        };
283 1
284 1
        return $this;
285
    }
286
287
    /**
288
     * Sets saving state to session.
289
     * @param bool $state
290
     * @param string $sectionName
291
     * @return Grid
292
     */
293
    public function setRememberState($state = TRUE, $sectionName = NULL)
294
    {
295 1
        $this->getPresenter(); //component must be attached to presenter
296 1
        $this->getRememberSession(TRUE); //start session if not
297 1
        $this->rememberState = (bool) $state;
298 1
        $this->rememberStateSectionName = $sectionName;
299
300 1
        return $this;
301
    }
302
303
    /**
304
     * Sets callback for customizing tr html object.
305
     * Callback returns tr html element; function($row, Html $tr).
306
     * @param $callback
307
     * @return Grid
308
     */
309
    public function setRowCallback($callback)
310
    {
311 1
        $this->rowCallback = $callback;
312 1
        return $this;
313
    }
314
315
    /**
316
     * Sets client-side options.
317
     * @param array $options
318
     * @return Grid
319
     */
320
    public function setClientSideOptions(array $options)
321
    {
322 1
        $this->options[self::CLIENT_SIDE_OPTIONS] = $options;
323 1
        return $this;
324
    }
325
326
    /**
327
     * Determines whether any user error will cause a notice.
328
     * @param bool $mode
329
     * @return \Grido\Grid
330
     */
331
    public function setStrictMode($mode)
332
    {
333 1
        $this->strictMode = (bool) $mode;
334 1
        return $this;
335
    }
336
337
    /**
338
     * @param \Grido\Customization $customization
339
     */
340
    public function setCustomization(Customization $customization)
341
    {
342 1
        $this->customization = $customization;
343 1
    }
344
345
    /**********************************************************************************************/
346
347
    /**
348
     * Returns total count of data.
349
     * @return int
350
     */
351
    public function getCount()
352
    {
353 1
        if ($this->count === NULL) {
354 1
            $this->count = $this->getModel()->getCount();
355 1
        }
356
357 1
        return $this->count;
358
    }
359
360
    /**
361
     * Returns default per page.
362
     * @return int
363
     */
364
    public function getDefaultPerPage()
365
    {
366 1
        if (!in_array($this->defaultPerPage, $this->perPageList)) {
367 1
            $this->defaultPerPage = $this->perPageList[0];
368 1
        }
369
370 1
        return $this->defaultPerPage;
371
    }
372
373
    /**
374
     * Returns default filter.
375
     * @return array
376
     */
377
    public function getDefaultFilter()
378
    {
379 1
        return $this->defaultFilter;
380
    }
381
382
    /**
383
     * Returns default sort.
384
     * @return array
385
     */
386
    public function getDefaultSort()
387
    {
388 1
        return $this->defaultSort;
389
    }
390
391
    /**
392
     * Returns list of possible items per page.
393
     * @return array
394
     */
395
    public function getPerPageList()
396 1
    {
397 1
        return $this->perPageList;
398
    }
399
400
    /**
401
     * Returns primary key.
402
     * @return string
403
     */
404
    public function getPrimaryKey()
405
    {
406 1
        return $this->primaryKey;
407
    }
408
409
    /**
410
     * Returns remember state.
411
     * @return bool
412
     */
413
    public function getRememberState()
414
    {
415 1
        return $this->rememberState;
416
    }
417
418
    /**
419
     * Returns row callback.
420
     * @return callback
421
     */
422
    public function getRowCallback()
423
    {
424 1
        return $this->rowCallback;
425
    }
426
427
    /**
428
     * Returns items per page.
429
     * @return int
430
     */
431
    public function getPerPage()
432
    {
433 1
        return $this->perPage === NULL
434 1
            ? $this->getDefaultPerPage()
435 1
            : $this->perPage;
436
    }
437
438
    /**
439
     * Returns actual filter values.
440
     * @param string $key
441
     * @return mixed
442
     */
443
    public function getActualFilter($key = NULL)
444
    {
445 1
        $filter = $this->filter ? $this->filter : $this->defaultFilter;
446 1
        return $key !== NULL && isset($filter[$key]) ? $filter[$key] : $filter;
447
    }
448
449
    /**
450
     * Returns fetched data.
451
     * @param bool $applyPaging
452
     * @param bool $useCache
453
     * @param bool $fetch
454
     * @throws Exception
455
     * @return array|DataSources\IDataSource|\Nette\Database\Table\Selection
456
     */
457
    public function getData($applyPaging = TRUE, $useCache = TRUE, $fetch = TRUE)
458
    {
459 1
        if ($this->getModel() === NULL) {
460 1
            throw new Exception('Model cannot be empty, please use method $grid->setModel().');
461
        }
462
463 1
        $data = $this->data;
464 1
        if ($data === NULL || $useCache === FALSE) {
465 1
            $this->applyFiltering();
466 1
            $this->applySorting();
467
468 1
            if ($applyPaging) {
469 1
                $this->applyPaging();
470 1
            }
471
472 1
            if ($fetch === FALSE) {
473 1
                return $this->getModel();
474
            }
475
476 1
            $data = $this->getModel()->getData();
477
478 1
            if ($useCache === TRUE) {
479 1
                $this->data = $data;
480 1
            }
481
482 1
            if ($applyPaging && !empty($data) && !in_array($this->page, range(1, $this->getPaginator()->pageCount))) {
483 1
                $this->__triggerUserNotice("Page is out of range.");
484 1
                $this->page = 1;
485 1
            }
486
487 1
            if (!empty($this->onFetchData)) {
488
                $this->onFetchData($this);
489
            }
490 1
        }
491
492 1
        return $data;
493
    }
494
495
    /**
496
     * Returns translator.
497
     * @return Translations\FileTranslator
498
     */
499
    public function getTranslator()
500
    {
501 1
        if ($this->translator === NULL) {
502 1
            $this->setTranslator(new Translations\FileTranslator);
503 1
        }
504
505 1
        return $this->translator;
506
    }
507
508
    /**
509
     * Returns remember session for set expiration, etc.
510
     * @param bool $forceStart - if TRUE, session will be started if not
511
     * @return \Nette\Http\SessionSection|NULL
512
     */
513
    public function getRememberSession($forceStart = FALSE)
514
    {
515 1
        $presenter = $this->getPresenter();
516 1
        $session = $presenter->getSession();
517
518 1
        if (!$session->isStarted() && $forceStart) {
519 1
            $session->start();
520 1
        }
521
522 1
        return $session->isStarted()
523 1
            ? ($session->getSection($this->rememberStateSectionName ?: ($presenter->name . ':' . $this->getUniqueId())))
0 ignored issues
show
Bug introduced by
Accessing name on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
524 1
            : NULL;
525
    }
526
527
    /**
528
     * Returns table html element of grid.
529
     * @return \Nette\Utils\Html
530
     */
531
    public function getTablePrototype()
532
    {
533 1
        if ($this->tablePrototype === NULL) {
534 1
            $this->tablePrototype = \Nette\Utils\Html::el('table');
535 1
            $this->tablePrototype->id($this->getName());
536 1
        }
537
538 1
        return $this->tablePrototype;
539
    }
540
541
    /**
542
     * @return string
543
     * @internal
544
     */
545
    public function getFilterRenderType()
546
    {
547 1
        if ($this->filterRenderType !== NULL) {
548 1
            return $this->filterRenderType;
549
        }
550
551 1
        $this->filterRenderType = Filter::RENDER_OUTER;
552 1
        if ($this->hasColumns() && $this->hasFilters() && $this->hasActions()) {
553 1
            $this->filterRenderType = Filter::RENDER_INNER;
554
555 1
            $filters = $this[Filter::ID]->getComponents();
556 1
            foreach ($filters as $filter) {
557 1
                if (!$this[Column::ID]->getComponent($filter->name, FALSE)) {
558 1
                    $this->filterRenderType = Filter::RENDER_OUTER;
559 1
                    break;
560
                }
561 1
            }
562 1
        }
563
564 1
        return $this->filterRenderType;
565
    }
566
567
    /**
568
     * @return DataSources\IDataSource
569
     */
570
    public function getModel()
571
    {
572 1
        return $this->model;
573
    }
574
575
    /**
576
     * @return Paginator
577
     * @internal
578
     */
579
    public function getPaginator()
580
    {
581 1
        if ($this->paginator === NULL) {
582 1
            $this->paginator = new Paginator;
583 1
            $this->paginator->setItemsPerPage($this->getPerPage())
584 1
                ->setGrid($this);
585 1
        }
586
587 1
        return $this->paginator;
588
    }
589
590
    /**
591
     * A simple wrapper around symfony/property-access with Nette Database dot notation support.
592
     * @param array|object $object
593
     * @param string $name
594
     * @return mixed
595
     * @internal
596
     */
597
    public function getProperty($object, $name)
598
    {
599 1
        if ($object instanceof \Nette\Database\Table\IRow && \Nette\Utils\Strings::contains($name, '.')) {
600 1
            $parts = explode('.', $name);
601 1
            foreach ($parts as $item) {
602 1
                if (is_object($object)) {
603 1
                    $object = $object->$item;
604 1
                }
605 1
            }
606
607 1
            return $object;
608
        }
609
610 1
        if (is_array($object)) {
611 1
            $exploded = explode('.',$name,2);
612 1
            if (count($exploded) == 2){
613
                $name = "[$exploded[0]].$exploded[1]";
614
            } else {
615 1
                $name = "[$exploded[0]]";
616
            }
617
618
619 1
        }
620
621
        try{
622 1
            return $this->getPropertyAccessor()->getValue($object, $name);
623 1
        } catch (\Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException $e){
624
            return NULL;
625
        }
626
    }
627
628
    /**
629
     * @return PropertyAccessor
630
     * @internal
631
     */
632
    public function getPropertyAccessor()
633
    {
634 1
        if ($this->propertyAccessor === NULL) {
635 1
            $this->propertyAccessor = new PropertyAccessor(TRUE, TRUE);
636 1
        }
637
638 1
        return $this->propertyAccessor;
639
    }
640
641
    /**
642
     * @param mixed $row item from db
643
     * @return \Nette\Utils\Html
644
     * @internal
645
     */
646
    public function getRowPrototype($row)
647
    {
648
        try {
649 1
            $primaryValue = $this->getProperty($row, $this->getPrimaryKey());
650 1
        } catch (\Exception $e) {
651 1
            $primaryValue = NULL;
652
        }
653
654 1
        $tr = \Nette\Utils\Html::el('tr');
655 1
        $primaryValue ? $tr->class[] = "grid-row-$primaryValue" : NULL;
656
657 1
        if ($this->rowCallback) {
658 1
            $tr = call_user_func_array($this->rowCallback, [$row, $tr]);
659 1
        }
660
661 1
        return $tr;
662
    }
663
664
    /**
665
     * Returns client-side options.
666
     * @return array
667
     */
668
    public function getClientSideOptions()
669
    {
670 1
        return (array) $this->options[self::CLIENT_SIDE_OPTIONS];
671
    }
672
673
    /**
674
     * @return bool
675
     */
676
    public function isStrictMode()
677
    {
678 1
        return $this->strictMode;
679
    }
680
681
    /**
682
     * @return Customization
683
     */
684
    public function getCustomization()
685
    {
686 1
        if ($this->customization === NULL) {
687 1
            $this->customization = new Customization($this);
688 1
        }
689
690 1
        return $this->customization;
691
    }
692
693
    /**********************************************************************************************/
694
695
    /**
696
     * Loads state informations.
697
     * @param array $params
698
     * @internal
699
     */
700
    public function loadState(array $params)
701
    {
702
        //loads state from session
703 1
        $session = $this->getRememberSession();
704 1
        if ($session && $this->getPresenter()->isSignalReceiver($this)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isSignalReceiver() does only exist in the following implementations of said interface: KdybyModule\CliPresenter, Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
705 1
            $session->remove();
706 1
        } elseif ($session && empty($params) && $session->params) {
707 1
            $params = (array) $session->params;
708 1
        }
709
710 1
        parent::loadState($params);
711 1
    }
712
713
    /**
714
     * Saves state informations for next request.
715
     * @param array $params
716
     * @param \Nette\Application\UI\PresenterComponentReflection $reflection (internal, used by Presenter)
717
     * @internal
718
     */
719
    public function saveState(array &$params, $reflection = NULL)
720
    {
721 1
        !empty($this->onRegistered) && $this->onRegistered($this);
722 1
        return parent::saveState($params, $reflection);
723
    }
724
725
    /**
726
     * Ajax method.
727
     * @internal
728
     */
729
    public function handleRefresh()
730
    {
731
        $this->reload();
732
    }
733
734
    /**
735
     * @param int $page
736
     * @internal
737
     */
738
    public function handlePage($page)
0 ignored issues
show
Unused Code introduced by
The parameter $page is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
739
    {
740 1
        $this->reload();
741
    }
742
743
    /**
744
     * @param array $sort
745
     * @internal
746
     */
747
    public function handleSort(array $sort)
0 ignored issues
show
Unused Code introduced by
The parameter $sort is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
748
    {
749 1
        $this->page = 1;
750 1
        $this->reload();
751
    }
752
753
    /**
754
     * @param \Nette\Forms\Controls\SubmitButton $button
755
     * @internal
756
     */
757
    public function handleFilter(\Nette\Forms\Controls\SubmitButton $button)
758
    {
759 1
        $values = $button->form->values[Filter::ID];
760 1
        $session = $this->rememberState //session filter
761 1
            ? isset($this->getRememberSession(TRUE)->params['filter'])
762 1
                ? $this->getRememberSession(TRUE)->params['filter']
763 1
                : []
764 1
            : [];
765
766 1
        foreach ($values as $name => $value) {
767 1
            if (is_numeric($value) || !empty($value) || isset($this->defaultFilter[$name]) || isset($session[$name])) {
768 1
                $this->filter[$name] = $this->getFilter($name)->changeValue($value);
769 1
            } elseif (isset($this->filter[$name])) {
770
                unset($this->filter[$name]);
771
            }
772 1
        }
773
774 1
        $this->page = 1;
775 1
        $this->reload();
776
    }
777
778
    /**
779
     * @param \Nette\Forms\Controls\SubmitButton $button
780
     * @internal
781
     */
782
    public function handleReset(\Nette\Forms\Controls\SubmitButton $button)
783
    {
784 1
        $this->sort = [];
785 1
        $this->filter = [];
786 1
        $this->perPage = NULL;
787
788 1
        if ($session = $this->getRememberSession()) {
789 1
            $session->remove();
790 1
        }
791
792 1
        $button->form->setValues([Filter::ID => $this->defaultFilter], TRUE);
793
794 1
        $this->page = 1;
795 1
        $this->reload();
796
    }
797
798
    /**
799
     * @param \Nette\Forms\Controls\SubmitButton $button
800
     * @internal
801
     */
802
    public function handlePerPage(\Nette\Forms\Controls\SubmitButton $button)
803
    {
804 1
        $perPage = (int) $button->form['count']->value;
805 1
        $this->perPage = $perPage == $this->defaultPerPage
806 1
            ? NULL
807
            : $perPage;
808
809 1
        $this->page = 1;
810 1
        $this->reload();
811
    }
812
813
    /**
814
     * Refresh wrapper.
815
     * @return void
816
     * @internal
817
     */
818
    public function reload()
819
    {
820 1
        if ($this->presenter->isAjax()) {
821
            $this->presenter->payload->grido = TRUE;
822
            $this->redrawControl();
823
        } else {
824 1
            $this->redirect('this');
825
        }
826
    }
827
828
    /**********************************************************************************************/
829
830
    /**
831
     * @return \Nette\Templating\FileTemplate
832
     * @internal
833
     */
834
    public function createTemplate()
835
    {
836 1
        $template = parent::createTemplate();
837 1
        $template->setFile($this->getCustomization()->getTemplateFiles()[Customization::TEMPLATE_DEFAULT]);
838 1
        $template->registerHelper('translate', [$this->getTranslator(), 'translate']);
839
840 1
        return $template;
841
    }
842
843
    /**
844
     * @internal
845
     * @throws Exception
846
     */
847
    public function render()
848
    {
849 1
        if (!$this->hasColumns()) {
850
            throw new Exception('Grid must have defined a column, please use method $grid->addColumn*().');
851
        }
852
853 1
        $this->saveRememberState();
854 1
        $data = $this->getData();
855
856 1
        if (!empty($this->onRender)) {
857 1
            $this->onRender($this);
858 1
        }
859
860 1
        $this->template->data = $data;
861 1
        $this->template->form = $form = $this['form'];
862 1
        $this->template->paginator = $this->getPaginator();
0 ignored issues
show
Bug introduced by
Accessing paginator on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
863
864 1
        $this->template->columns = $this->getComponent(Column::ID)->getComponents();
0 ignored issues
show
Bug introduced by
Accessing columns on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
865 1
        $this->template->actions = $this->hasActions() ? $this->getComponent(Action::ID)->getComponents() : [];
0 ignored issues
show
Bug introduced by
Accessing actions on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
866 1
        $this->template->formFilters = $this->hasFilters() ? $form->getComponent(Filter::ID)->getComponents() : [];
0 ignored issues
show
Bug introduced by
Accessing formFilters on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
867 1
        $this->template->customization = $this->getCustomization();
0 ignored issues
show
Bug introduced by
Accessing customization on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
868
869 1
        $form['count']->setValue($this->getPerPage());
870
871 1
        if ($options = $this->options[self::CLIENT_SIDE_OPTIONS]) {
872 1
            $this->getTablePrototype()->data[self::CLIENT_SIDE_OPTIONS] = json_encode($options);
873 1
        }
874
875 1
        $this->template->render();
876 1
    }
877
878
    protected function saveRememberState()
879
    {
880 1
        if ($this->rememberState) {
881 1
            $session = $this->getRememberSession(TRUE);
882 1
            $params = array_keys($this->getReflection()->getPersistentParams());
883 1
            foreach ($params as $param) {
884 1
                $session->params[$param] = $this->$param;
885 1
            }
886 1
        }
887 1
    }
888
889
    protected function applyFiltering()
890
    {
891 1
        $conditions = $this->__getConditions($this->getActualFilter());
892 1
        $this->getModel()->filter($conditions);
893 1
    }
894
895
    /**
896
     * @param array $filter
897
     * @return array
898
     * @internal
899
     */
900
    public function __getConditions(array $filter)
901
    {
902 1
        $conditions = [];
903 1
        if (!empty($filter)) {
904
            try {
905 1
                $this['form']->setDefaults([Filter::ID => $filter]);
906 1
            } catch (\Nette\InvalidArgumentException $e) {
907
                $this->__triggerUserNotice($e->getMessage());
908
                $filter = [];
909
                if ($session = $this->getRememberSession()) {
910
                    $session->remove();
911
                }
912
            }
913
914 1
            foreach ($filter as $column => $value) {
915 1
                if ($component = $this->getFilter($column, FALSE)) {
916 1
                    if ($condition = $component->__getCondition($value)) {
917 1
                        $conditions[] = $condition;
918 1
                    }
919 1
                } else {
920 1
                    $this->__triggerUserNotice("Filter with name '$column' does not exist.");
921
                }
922 1
            }
923 1
        }
924
925 1
        return $conditions;
926
    }
927
928
    protected function applySorting()
929
    {
930 1
        $sort = [];
931 1
        $this->sort = $this->sort ? $this->sort : $this->defaultSort;
932
933 1
        foreach ($this->sort as $column => $dir) {
934 1
            $component = $this->getColumn($column, FALSE);
935 1
            if (!$component) {
936 1
                if (!isset($this->defaultSort[$column])) {
937 1
                    $this->__triggerUserNotice("Column with name '$column' does not exist.");
938 1
                    break;
939
                }
940
941 1
            } elseif (!$component->isSortable()) {
942 1
                if (isset($this->defaultSort[$column])) {
943 1
                    $component->setSortable();
944 1
                } else {
945 1
                    $this->__triggerUserNotice("Column with name '$column' is not sortable.");
946 1
                    break;
947
                }
948 1
            }
949
950 1
            if (!in_array($dir, [Column::ORDER_ASC, Column::ORDER_DESC])) {
951 1
                if ($dir == '' && isset($this->defaultSort[$column])) {
952
                    unset($this->sort[$column]);
953
                    break;
954
                }
955
956 1
                $this->__triggerUserNotice("Dir '$dir' is not allowed.");
957 1
                break;
958
            }
959
960 1
            $sort[$component ? $component->column : $column] = $dir == Column::ORDER_ASC ? 'ASC' : 'DESC';
0 ignored issues
show
Documentation introduced by
The property $column is declared protected in Grido\Components\Columns\Column. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
961 1
        }
962
963 1
        if (!empty($sort)) {
964 1
            $this->getModel()->sort($sort);
965 1
        }
966 1
    }
967
968
    protected function applyPaging()
969
    {
970 1
        $paginator = $this->getPaginator()
971 1
            ->setItemCount($this->getCount())
972 1
            ->setPage($this->page);
973
974 1
        $perPage = $this->getPerPage();
975 1
        if ($perPage !== NULL && !in_array($perPage, $this->perPageList)) {
976 1
            $this->__triggerUserNotice("The number '$perPage' of items per page is out of range.");
977 1
        }
978
979 1
        $this->getModel()->limit($paginator->getOffset(), $paginator->getLength());
980 1
    }
981
982
    protected function createComponentForm($name)
983
    {
984 1
        $form = new \Nette\Application\UI\Form($this, $name);
985 1
        $form->setTranslator($this->getTranslator());
986 1
        $form->setMethod($form::GET);
987
988 1
        $buttons = $form->addContainer(self::BUTTONS);
989 1
        $buttons->addSubmit('search', 'Grido.Search')
990 1
            ->onClick[] = [$this, 'handleFilter'];
991 1
        $buttons->addSubmit('reset', 'Grido.Reset')
992 1
            ->onClick[] = [$this, 'handleReset'];
993 1
        $buttons->addSubmit('perPage', 'Grido.ItemsPerPage')
994 1
            ->onClick[] = [$this, 'handlePerPage'];
995
996 1
        $form->addSelect('count', 'Count', $this->getItemsForCountSelect())
997 1
            ->setTranslator(NULL)
998 1
            ->controlPrototype->attrs['title'] = $this->getTranslator()->translate('Grido.ItemsPerPage');
999 1
    }
1000
1001
    /**
1002
     * @return array
1003
     */
1004
    protected function getItemsForCountSelect()
1005
    {
1006 1
        return array_combine($this->perPageList, $this->perPageList);
1007
    }
1008
1009
    /**
1010
     * @internal
1011
     * @param string $message
1012
     */
1013
    public function __triggerUserNotice($message)
1014
    {
1015 1
        if ($this->getPresenter(FALSE) && $session = $this->getRememberSession()) {
1016 1
            $session->remove();
1017 1
        }
1018
1019 1
        $this->strictMode && trigger_error($message, E_USER_NOTICE);
1020 1
    }
1021
}
1022