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 |
||
51 | class Grid extends Components\Container |
||
52 | 1 | { |
|
53 | /***** DEFAULTS ****/ |
||
54 | const BUTTONS = 'buttons'; |
||
55 | |||
56 | const CLIENT_SIDE_OPTIONS = 'grido-options'; |
||
57 | |||
58 | /** @var int @persistent */ |
||
59 | public $page = 1; |
||
60 | |||
61 | /** @var int @persistent */ |
||
62 | public $perPage; |
||
63 | |||
64 | /** @var array @persistent */ |
||
65 | public $sort = []; |
||
66 | |||
67 | 1 | /** @var array @persistent */ |
|
68 | public $filter = []; |
||
69 | |||
70 | /** @var array event on all grid's components registered */ |
||
71 | public $onRegistered; |
||
72 | |||
73 | /** @var array event on render */ |
||
74 | public $onRender; |
||
75 | |||
76 | /** @var array event for modifying data */ |
||
77 | public $onFetchData; |
||
78 | |||
79 | /** @var callback returns tr html element; function($row, Html $tr) */ |
||
80 | protected $rowCallback; |
||
81 | |||
82 | /** @var \Nette\Utils\Html */ |
||
83 | protected $tablePrototype; |
||
84 | |||
85 | /** @var bool */ |
||
86 | protected $rememberState = FALSE; |
||
87 | |||
88 | /** @var string */ |
||
89 | protected $rememberStateSectionName; |
||
90 | |||
91 | /** @var string */ |
||
92 | protected $primaryKey = 'id'; |
||
93 | |||
94 | /** @var string */ |
||
95 | protected $filterRenderType; |
||
96 | |||
97 | /** @var array */ |
||
98 | protected $perPageList = [10, 20, 30, 50, 100]; |
||
99 | |||
100 | /** @var int */ |
||
101 | protected $defaultPerPage = 20; |
||
102 | |||
103 | /** @var array */ |
||
104 | protected $defaultFilter = []; |
||
105 | |||
106 | /** @var array */ |
||
107 | protected $defaultSort = []; |
||
108 | |||
109 | /** @var DataSources\IDataSource */ |
||
110 | protected $model; |
||
111 | |||
112 | /** @var int total count of items */ |
||
113 | protected $count; |
||
114 | |||
115 | /** @var mixed */ |
||
116 | protected $data; |
||
117 | |||
118 | /** @var Paginator */ |
||
119 | protected $paginator; |
||
120 | 1 | ||
121 | /** @var \Nette\Localization\ITranslator */ |
||
122 | protected $translator; |
||
123 | |||
124 | /** @var PropertyAccessor */ |
||
125 | protected $propertyAccessor; |
||
126 | |||
127 | /** @var bool */ |
||
128 | protected $strictMode = TRUE; |
||
129 | |||
130 | /** @var array */ |
||
131 | protected $options = [ |
||
132 | self::CLIENT_SIDE_OPTIONS => [] |
||
133 | ]; |
||
134 | |||
135 | /** @var Customization */ |
||
136 | protected $customization; |
||
137 | |||
138 | /** |
||
139 | * Sets a model that implements the interface Grido\DataSources\IDataSource or data-source object. |
||
140 | * @param mixed $model |
||
141 | * @param bool $forceWrapper |
||
142 | 1 | * @throws Exception |
|
143 | * @return Grid |
||
144 | */ |
||
145 | public function setModel($model, $forceWrapper = FALSE) |
||
153 | |||
154 | /** |
||
155 | * Sets the default number of items per page. |
||
156 | * @param int $perPage |
||
157 | * @return Grid |
||
158 | */ |
||
159 | public function setDefaultPerPage($perPage) |
||
171 | |||
172 | /** |
||
173 | * Sets default filtering. |
||
174 | * @param array $filter |
||
175 | * @return Grid |
||
176 | */ |
||
177 | public function setDefaultFilter(array $filter) |
||
182 | |||
183 | /** |
||
184 | * Sets default sorting. |
||
185 | * @param array $sort |
||
186 | * @return Grid |
||
187 | 1 | * @throws Exception |
|
188 | */ |
||
189 | public function setDefaultSort(array $sort) |
||
190 | { |
||
191 | 1 | static $replace = ['asc' => Column::ORDER_ASC, 'desc' => Column::ORDER_DESC]; |
|
192 | |||
193 | 1 | foreach ($sort as $column => $dir) { |
|
194 | 1 | $dir = strtr(strtolower($dir), $replace); |
|
195 | 1 | if (!in_array($dir, $replace)) { |
|
196 | 1 | throw new Exception("Dir '$dir' for column '$column' is not allowed."); |
|
197 | } |
||
198 | |||
199 | 1 | $this->defaultSort[$column] = $dir; |
|
200 | 1 | } |
|
201 | |||
202 | 1 | return $this; |
|
203 | 1 | } |
|
204 | |||
205 | /** |
||
206 | * Sets items to per-page select. |
||
207 | * @param array $perPageList |
||
208 | 1 | * @return Grid |
|
209 | */ |
||
210 | public function setPerPageList(array $perPageList) |
||
220 | 1 | ||
221 | /** |
||
222 | * Sets translator. |
||
223 | * @param \Nette\Localization\ITranslator $translator |
||
224 | * @return Grid |
||
225 | */ |
||
226 | public function setTranslator(\Nette\Localization\ITranslator $translator) |
||
231 | |||
232 | /** |
||
233 | * Sets type of filter rendering. |
||
234 | * Defaults inner (Filter::RENDER_INNER) if column does not exist then outer filter (Filter::RENDER_OUTER). |
||
235 | * @param string $type |
||
236 | 1 | * @throws Exception |
|
237 | * @return Grid |
||
238 | */ |
||
239 | public function setFilterRenderType($type) |
||
249 | |||
250 | /** |
||
251 | * Sets custom paginator. |
||
252 | * @param Paginator $paginator |
||
253 | * @return Grid |
||
254 | */ |
||
255 | public function setPaginator(Paginator $paginator) |
||
260 | |||
261 | /** |
||
262 | * Sets grid primary key. |
||
263 | * Defaults is "id". |
||
264 | * @param string $key |
||
265 | * @return Grid |
||
266 | */ |
||
267 | public function setPrimaryKey($key) |
||
272 | |||
273 | /** |
||
274 | * Sets file name of custom template. |
||
275 | * @param string $file |
||
276 | * @return Grid |
||
277 | */ |
||
278 | public function setTemplateFile($file) |
||
287 | |||
288 | /** |
||
289 | * Sets saving state to session. |
||
290 | * @param bool $state |
||
291 | * @param string $sectionName |
||
292 | * @return Grid |
||
293 | */ |
||
294 | public function setRememberState($state = TRUE, $sectionName = NULL) |
||
303 | |||
304 | /** |
||
305 | * Sets callback for customizing tr html object. |
||
306 | * Callback returns tr html element; function($row, Html $tr). |
||
307 | * @param $callback |
||
308 | * @return Grid |
||
309 | */ |
||
310 | public function setRowCallback($callback) |
||
315 | |||
316 | /** |
||
317 | * Sets client-side options. |
||
318 | * @param array $options |
||
319 | * @return Grid |
||
320 | */ |
||
321 | public function setClientSideOptions(array $options) |
||
326 | |||
327 | /** |
||
328 | * Determines whether any user error will cause a notice. |
||
329 | * @param bool $mode |
||
330 | * @return \Grido\Grid |
||
331 | */ |
||
332 | public function setStrictMode($mode) |
||
337 | |||
338 | /** |
||
339 | * @param \Grido\Customization $customization |
||
340 | */ |
||
341 | public function setCustomization(Customization $customization) |
||
345 | |||
346 | /**********************************************************************************************/ |
||
347 | |||
348 | /** |
||
349 | * Returns total count of data. |
||
350 | * @return int |
||
351 | */ |
||
352 | public function getCount() |
||
360 | |||
361 | /** |
||
362 | * Returns default per page. |
||
363 | * @return int |
||
364 | */ |
||
365 | public function getDefaultPerPage() |
||
366 | { |
||
367 | 1 | if (!in_array($this->defaultPerPage, $this->perPageList)) { |
|
368 | 1 | $this->defaultPerPage = $this->perPageList[0]; |
|
369 | 1 | } |
|
370 | |||
371 | 1 | return $this->defaultPerPage; |
|
372 | } |
||
373 | |||
374 | /** |
||
375 | * Returns default filter. |
||
376 | * @return array |
||
377 | */ |
||
378 | public function getDefaultFilter() |
||
382 | |||
383 | /** |
||
384 | * Returns default sort. |
||
385 | * @return array |
||
386 | */ |
||
387 | public function getDefaultSort() |
||
391 | 1 | ||
392 | /** |
||
393 | * Returns list of possible items per page. |
||
394 | * @return array |
||
395 | */ |
||
396 | public function getPerPageList() |
||
397 | { |
||
398 | 1 | return $this->perPageList; |
|
399 | } |
||
400 | |||
401 | /** |
||
402 | * Returns primary key. |
||
403 | * @return string |
||
404 | */ |
||
405 | public function getPrimaryKey() |
||
409 | |||
410 | /** |
||
411 | * Returns remember state. |
||
412 | * @return bool |
||
413 | */ |
||
414 | public function getRememberState() |
||
418 | |||
419 | 1 | /** |
|
420 | * Returns row callback. |
||
421 | * @return callback |
||
422 | */ |
||
423 | public function getRowCallback() |
||
427 | |||
428 | /** |
||
429 | * Returns items per page. |
||
430 | * @return int |
||
431 | */ |
||
432 | public function getPerPage() |
||
438 | |||
439 | /** |
||
440 | * Returns actual filter values. |
||
441 | * @param string $key |
||
442 | * @return mixed |
||
443 | */ |
||
444 | public function getActualFilter($key = NULL) |
||
449 | |||
450 | /** |
||
451 | * Returns fetched data. |
||
452 | * @param bool $applyPaging |
||
453 | * @param bool $useCache |
||
454 | * @param bool $fetch |
||
455 | * @throws Exception |
||
456 | * @return array|DataSources\IDataSource|\Nette\Database\Table\Selection |
||
457 | */ |
||
458 | public function getData($applyPaging = TRUE, $useCache = TRUE, $fetch = TRUE) |
||
495 | |||
496 | /** |
||
497 | * Returns translator. |
||
498 | * @return Translations\FileTranslator |
||
499 | */ |
||
500 | public function getTranslator() |
||
508 | |||
509 | /** |
||
510 | * Returns remember session for set expiration, etc. |
||
511 | * @param bool $forceStart - if TRUE, session will be started if not |
||
512 | * @return \Nette\Http\SessionSection|NULL |
||
513 | */ |
||
514 | public function getRememberSession($forceStart = FALSE) |
||
527 | |||
528 | /** |
||
529 | * Returns table html element of grid. |
||
530 | * @return \Nette\Utils\Html |
||
531 | */ |
||
532 | public function getTablePrototype() |
||
541 | |||
542 | /** |
||
543 | * @return string |
||
544 | * @internal |
||
545 | */ |
||
546 | public function getFilterRenderType() |
||
567 | |||
568 | /** |
||
569 | * @return DataSources\IDataSource |
||
570 | */ |
||
571 | public function getModel() |
||
575 | |||
576 | /** |
||
577 | * @return Paginator |
||
578 | * @internal |
||
579 | */ |
||
580 | public function getPaginator() |
||
590 | |||
591 | /** |
||
592 | * A simple wrapper around symfony/property-access with Nette Database dot notation support. |
||
593 | * @param array|object $object |
||
594 | * @param string $name |
||
595 | * @return mixed |
||
596 | * @internal |
||
597 | */ |
||
598 | public function getProperty($object, $name) |
||
617 | |||
618 | /** |
||
619 | * @return PropertyAccessor |
||
620 | * @internal |
||
621 | */ |
||
622 | public function getPropertyAccessor() |
||
630 | |||
631 | /** |
||
632 | * @param mixed $row item from db |
||
633 | * @return \Nette\Utils\Html |
||
634 | * @internal |
||
635 | */ |
||
636 | public function getRowPrototype($row) |
||
653 | |||
654 | /** |
||
655 | * Returns client-side options. |
||
656 | * @return array |
||
657 | */ |
||
658 | public function getClientSideOptions() |
||
662 | |||
663 | /** |
||
664 | * @return bool |
||
665 | */ |
||
666 | public function isStrictMode() |
||
670 | |||
671 | /** |
||
672 | * @return Customization |
||
673 | */ |
||
674 | public function getCustomization() |
||
682 | |||
683 | /**********************************************************************************************/ |
||
684 | |||
685 | /** |
||
686 | * Loads state informations. |
||
687 | * @param array $params |
||
688 | * @internal |
||
689 | */ |
||
690 | public function loadState(array $params) |
||
702 | |||
703 | /** |
||
704 | * Saves state informations for next request. |
||
705 | * @param array $params |
||
706 | * @param \Nette\Application\UI\PresenterComponentReflection $reflection (internal, used by Presenter) |
||
707 | * @internal |
||
708 | */ |
||
709 | public function saveState(array &$params, $reflection = NULL) |
||
714 | |||
715 | /** |
||
716 | * Ajax method. |
||
717 | * @internal |
||
718 | */ |
||
719 | public function handleRefresh() |
||
723 | |||
724 | /** |
||
725 | * @param int $page |
||
726 | * @internal |
||
727 | */ |
||
728 | public function handlePage($page) |
||
732 | |||
733 | /** |
||
734 | * @param array $sort |
||
735 | * @internal |
||
736 | */ |
||
737 | public function handleSort(array $sort) |
||
742 | |||
743 | /** |
||
744 | * @param \Nette\Forms\Controls\SubmitButton $button |
||
745 | * @internal |
||
746 | */ |
||
747 | public function handleFilter(\Nette\Forms\Controls\SubmitButton $button) |
||
767 | |||
768 | /** |
||
769 | * @param \Nette\Forms\Controls\SubmitButton $button |
||
770 | * @internal |
||
771 | */ |
||
772 | public function handleReset(\Nette\Forms\Controls\SubmitButton $button) |
||
787 | |||
788 | /** |
||
789 | * @param \Nette\Forms\Controls\SubmitButton $button |
||
790 | * @internal |
||
791 | */ |
||
792 | public function handlePerPage(\Nette\Forms\Controls\SubmitButton $button) |
||
802 | |||
803 | /** |
||
804 | * Refresh wrapper. |
||
805 | * @return void |
||
806 | * @internal |
||
807 | */ |
||
808 | public function reload() |
||
817 | |||
818 | /**********************************************************************************************/ |
||
819 | |||
820 | /** |
||
821 | * @return \Nette\Templating\FileTemplate |
||
822 | * @internal |
||
823 | */ |
||
824 | public function createTemplate() |
||
832 | |||
833 | /** |
||
834 | * @internal |
||
835 | * @throws Exception |
||
836 | */ |
||
837 | public function render() |
||
838 | { |
||
839 | 1 | if (!$this->hasColumns()) { |
|
840 | throw new Exception('Grid must have defined a column, please use method $grid->addColumn*().'); |
||
841 | } |
||
842 | |||
843 | 1 | $this->saveRememberState(); |
|
844 | 1 | $data = $this->getData(); |
|
845 | |||
846 | 1 | if (!empty($this->onRender)) { |
|
847 | 1 | $this->onRender($this); |
|
848 | 1 | } |
|
849 | |||
850 | 1 | $form = $this['form']; |
|
851 | |||
852 | 1 | $this->getTemplate()->add('data', $data); |
|
853 | 1 | $this->getTemplate()->add('form', $form); |
|
854 | 1 | $this->getTemplate()->add('paginator', $this->getPaginator()); |
|
855 | 1 | $this->getTemplate()->add('customization', $this->getCustomization()); |
|
856 | 1 | $this->getTemplate()->add('columns', $this->getComponent(Column::ID)->getComponents()); |
|
857 | 1 | $this->getTemplate()->add('actions', $this->hasActions() |
|
858 | 1 | ? $this->getComponent(Action::ID)->getComponents() |
|
859 | 1 | : [] |
|
860 | 1 | ); |
|
861 | |||
862 | 1 | $this->getTemplate()->add('buttons', $this->hasButtons() |
|
863 | 1 | ? $this->getComponent(Button::ID)->getComponents() |
|
864 | 1 | : [] |
|
865 | 1 | ); |
|
866 | |||
867 | 1 | $this->getTemplate()->add('formFilters', $this->hasFilters() |
|
868 | 1 | ? $form->getComponent(Filter::ID)->getComponents() |
|
869 | 1 | : [] |
|
870 | 1 | ); |
|
871 | |||
872 | 1 | $form['count']->setValue($this->getPerPage()); |
|
873 | |||
874 | 1 | if ($options = $this->options[self::CLIENT_SIDE_OPTIONS]) { |
|
875 | 1 | $this->getTablePrototype()->setAttribute('data-' . self::CLIENT_SIDE_OPTIONS, json_encode($options)); |
|
876 | 1 | } |
|
877 | |||
878 | 1 | $this->getTemplate()->render(); |
|
879 | 1 | } |
|
880 | |||
881 | protected function saveRememberState() |
||
882 | { |
||
883 | 1 | if ($this->rememberState) { |
|
884 | 1 | $session = $this->getRememberSession(TRUE); |
|
885 | 1 | $params = array_keys($this->getReflection()->getPersistentParams()); |
|
886 | 1 | foreach ($params as $param) { |
|
887 | 1 | $session->params[$param] = $this->$param; |
|
888 | 1 | } |
|
889 | 1 | } |
|
890 | 1 | } |
|
891 | |||
892 | protected function applyFiltering() |
||
893 | { |
||
894 | 1 | $conditions = $this->__getConditions($this->getActualFilter()); |
|
895 | 1 | $this->getModel()->filter($conditions); |
|
896 | 1 | } |
|
897 | |||
898 | /** |
||
899 | * @param array $filter |
||
900 | * @return array |
||
901 | * @internal |
||
902 | */ |
||
903 | public function __getConditions(array $filter) |
||
904 | { |
||
905 | 1 | $conditions = []; |
|
906 | 1 | if (!empty($filter)) { |
|
907 | try { |
||
908 | 1 | $this['form']->setDefaults([Filter::ID => $filter]); |
|
909 | 1 | } catch (\Nette\InvalidArgumentException $e) { |
|
910 | $this->__triggerUserNotice($e->getMessage()); |
||
911 | $filter = []; |
||
912 | if ($session = $this->getRememberSession()) { |
||
913 | $session->remove(); |
||
914 | } |
||
915 | } |
||
916 | |||
917 | 1 | foreach ($filter as $column => $value) { |
|
918 | 1 | if ($component = $this->getFilter($column, FALSE)) { |
|
919 | 1 | if ($condition = $component->__getCondition($value)) { |
|
920 | 1 | $conditions[] = $condition; |
|
921 | 1 | } |
|
922 | 1 | } else { |
|
923 | 1 | $this->__triggerUserNotice("Filter with name '$column' does not exist."); |
|
924 | } |
||
925 | 1 | } |
|
926 | 1 | } |
|
927 | |||
928 | 1 | return $conditions; |
|
929 | } |
||
930 | |||
931 | protected function applySorting() |
||
932 | { |
||
933 | 1 | $sort = []; |
|
934 | 1 | $this->sort = $this->sort ? $this->sort : $this->defaultSort; |
|
935 | |||
936 | 1 | foreach ($this->sort as $column => $dir) { |
|
937 | 1 | $component = $this->getColumn($column, FALSE); |
|
938 | 1 | if (!$component) { |
|
939 | 1 | if (!isset($this->defaultSort[$column])) { |
|
940 | 1 | $this->__triggerUserNotice("Column with name '$column' does not exist."); |
|
941 | 1 | break; |
|
942 | } |
||
943 | |||
944 | 1 | } elseif (!$component->isSortable()) { |
|
945 | 1 | if (isset($this->defaultSort[$column])) { |
|
946 | 1 | $component->setSortable(); |
|
947 | 1 | } else { |
|
948 | 1 | $this->__triggerUserNotice("Column with name '$column' is not sortable."); |
|
949 | 1 | break; |
|
950 | } |
||
951 | 1 | } |
|
952 | |||
953 | 1 | if (!in_array($dir, [Column::ORDER_ASC, Column::ORDER_DESC])) { |
|
954 | 1 | if ($dir == '' && isset($this->defaultSort[$column])) { |
|
955 | unset($this->sort[$column]); |
||
956 | break; |
||
957 | } |
||
958 | |||
959 | 1 | $this->__triggerUserNotice("Dir '$dir' is not allowed."); |
|
960 | 1 | break; |
|
961 | } |
||
962 | |||
963 | 1 | $sort[$component ? $component->column : $column] = $dir == Column::ORDER_ASC ? 'ASC' : 'DESC'; |
|
964 | 1 | } |
|
965 | |||
966 | 1 | if (!empty($sort)) { |
|
967 | 1 | $this->getModel()->sort($sort); |
|
968 | 1 | } |
|
969 | 1 | } |
|
970 | |||
971 | protected function applyPaging() |
||
972 | { |
||
973 | 1 | $paginator = $this->getPaginator() |
|
974 | 1 | ->setItemCount($this->getCount()) |
|
975 | 1 | ->setPage($this->page); |
|
976 | |||
977 | 1 | $perPage = $this->getPerPage(); |
|
978 | 1 | if ($perPage !== NULL && !in_array($perPage, $this->perPageList)) { |
|
979 | 1 | $this->__triggerUserNotice("The number '$perPage' of items per page is out of range."); |
|
980 | 1 | } |
|
981 | |||
982 | 1 | $this->getModel()->limit($paginator->getOffset(), $paginator->getLength()); |
|
983 | 1 | } |
|
984 | |||
985 | protected function createComponentForm($name) |
||
1003 | |||
1004 | /** |
||
1005 | * @return array |
||
1006 | */ |
||
1007 | protected function getItemsForCountSelect() |
||
1011 | |||
1012 | /** |
||
1013 | * @internal |
||
1014 | * @param string $message |
||
1015 | */ |
||
1016 | public function __triggerUserNotice($message) |
||
1017 | { |
||
1018 | 1 | if ($this->getPresenter(FALSE) && $session = $this->getRememberSession()) { |
|
1019 | 1 | $session->remove(); |
|
1020 | 1 | } |
|
1024 | } |
||
1025 |
If you access a property on an interface, you most likely code against a concrete implementation of the interface.
Available Fixes
Adding an additional type check:
Changing the type hint: