Complex classes like FormField 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 FormField, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | abstract class FormField |
||
17 | { |
||
18 | /** |
||
19 | * Name of the field |
||
20 | * |
||
21 | * @var |
||
22 | */ |
||
23 | protected $name; |
||
24 | |||
25 | /** |
||
26 | * Type of the field |
||
27 | * |
||
28 | * @var |
||
29 | */ |
||
30 | protected $type; |
||
31 | |||
32 | /** |
||
33 | * All options for the field |
||
34 | * |
||
35 | * @var |
||
36 | */ |
||
37 | protected $options = []; |
||
38 | |||
39 | /** |
||
40 | * Is field rendered |
||
41 | * |
||
42 | * @var bool |
||
43 | */ |
||
44 | protected $rendered = false; |
||
45 | |||
46 | /** |
||
47 | * @var Form |
||
48 | */ |
||
49 | protected $parent; |
||
50 | |||
51 | /** |
||
52 | * @var string |
||
53 | */ |
||
54 | protected $template; |
||
55 | |||
56 | /** |
||
57 | * @var FormHelper |
||
58 | */ |
||
59 | protected $formHelper; |
||
60 | |||
61 | /** |
||
62 | * Name of the property for value setting |
||
63 | * |
||
64 | * @var string |
||
65 | */ |
||
66 | protected $valueProperty = 'value'; |
||
67 | |||
68 | /** |
||
69 | * Name of the property for default value |
||
70 | * |
||
71 | * @var string |
||
72 | */ |
||
73 | protected $defaultValueProperty = 'default_value'; |
||
74 | |||
75 | /** |
||
76 | * Is default value set? |
||
77 | * @var bool |
||
78 | */ |
||
79 | protected $hasDefault = false; |
||
80 | |||
81 | /** |
||
82 | * @var \Closure|null |
||
83 | */ |
||
84 | protected $valueClosure = null; |
||
85 | |||
86 | /** |
||
87 | * @param $name |
||
88 | * @param $type |
||
89 | * @param Form $parent |
||
90 | * @param array $options |
||
91 | */ |
||
92 | 71 | public function __construct($name, $type, Form $parent, array $options = []) |
|
93 | { |
||
94 | 71 | $this->name = $name; |
|
95 | 71 | $this->type = $type; |
|
96 | 71 | $this->parent = $parent; |
|
97 | 71 | $this->formHelper = $this->parent->getFormHelper(); |
|
98 | 71 | $this->setTemplate(); |
|
99 | 71 | $this->setDefaultOptions($options); |
|
100 | 71 | $this->setupValue(); |
|
101 | 66 | } |
|
102 | |||
103 | 71 | protected function setupValue() |
|
104 | { |
||
105 | 71 | $value = $this->getOption($this->valueProperty); |
|
106 | 71 | $isChild = $this->getOption('is_child'); |
|
107 | |||
108 | 71 | if ($value instanceof \Closure) { |
|
109 | $this->valueClosure = $value; |
||
110 | } |
||
111 | |||
112 | 71 | if (($value === null || $value instanceof \Closure) && !$isChild) { |
|
113 | 62 | $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $this->name)); |
|
114 | 18 | } elseif (!$isChild) { |
|
115 | 12 | $this->hasDefault = true; |
|
116 | } |
||
117 | 66 | } |
|
118 | |||
119 | /** |
||
120 | * Get the template, can be config variable or view path |
||
121 | * |
||
122 | * @return string |
||
123 | */ |
||
124 | abstract protected function getTemplate(); |
||
125 | |||
126 | /** |
||
127 | * @return string |
||
128 | */ |
||
129 | 27 | protected function getViewTemplate() |
|
133 | |||
134 | /** |
||
135 | * @param array $options |
||
136 | * @param bool $showLabel |
||
137 | * @param bool $showField |
||
138 | * @param bool $showError |
||
139 | * @return string |
||
140 | */ |
||
141 | 27 | public function render(array $options = [], $showLabel = true, $showField = true, $showError = true) |
|
142 | { |
||
143 | 27 | $this->prepareOptions($options); |
|
144 | 27 | $value = $this->getValue(); |
|
145 | 27 | $defaultValue = $this->getDefaultValue(); |
|
146 | |||
147 | 27 | if ($showField) { |
|
148 | 27 | $this->rendered = true; |
|
149 | } |
||
150 | |||
151 | // Override default value with value |
||
152 | 27 | if (!$this->isValidValue($value) && $this->isValidValue($defaultValue)) { |
|
153 | $this->setOption($this->valueProperty, $defaultValue); |
||
154 | } |
||
155 | |||
156 | 27 | if (!$this->needsLabel()) { |
|
157 | 8 | $showLabel = false; |
|
158 | } |
||
159 | |||
160 | 27 | if ($showError) { |
|
161 | 26 | $showError = $this->parent->haveErrorsEnabled(); |
|
162 | } |
||
163 | |||
164 | 27 | return $this->formHelper->getView()->make( |
|
165 | 27 | $this->getViewTemplate(), |
|
166 | [ |
||
167 | 27 | 'name' => $this->name, |
|
168 | 27 | 'nameKey' => $this->getNameKey(), |
|
169 | 27 | 'type' => $this->type, |
|
170 | 27 | 'options' => $this->options, |
|
171 | 27 | 'showLabel' => $showLabel, |
|
172 | 27 | 'showField' => $showField, |
|
173 | 27 | 'showError' => $showError |
|
174 | ] |
||
175 | 27 | )->render(); |
|
176 | } |
||
177 | |||
178 | /** |
||
179 | * Get the attribute value from the model by name |
||
180 | * |
||
181 | * @param mixed $model |
||
182 | * @param string $name |
||
183 | * @return mixed |
||
184 | */ |
||
185 | 64 | protected function getModelValueAttribute($model, $name) |
|
196 | |||
197 | /** |
||
198 | * Transform array like syntax to dot syntax |
||
199 | * |
||
200 | * @param $key |
||
201 | * @return mixed |
||
202 | */ |
||
203 | 71 | protected function transformKey($key) |
|
207 | |||
208 | /** |
||
209 | * Prepare options for rendering |
||
210 | * |
||
211 | * @param array $options |
||
212 | * @return array |
||
213 | */ |
||
214 | 71 | protected function prepareOptions(array $options = []) |
|
271 | |||
272 | /** |
||
273 | * Get name of the field |
||
274 | * |
||
275 | * @return string |
||
276 | */ |
||
277 | 23 | public function getName() |
|
281 | |||
282 | /** |
||
283 | * Set name of the field |
||
284 | * |
||
285 | * @param string $name |
||
286 | * @return $this |
||
287 | */ |
||
288 | 11 | public function setName($name) |
|
294 | |||
295 | /** |
||
296 | * Get dot notation key for fields |
||
297 | * |
||
298 | * @return string |
||
299 | **/ |
||
300 | 39 | public function getNameKey() |
|
304 | |||
305 | /** |
||
306 | * Get field options |
||
307 | * |
||
308 | * @return array |
||
309 | */ |
||
310 | 10 | public function getOptions() |
|
314 | |||
315 | /** |
||
316 | * Get single option from options array. Can be used with dot notation ('attr.class') |
||
317 | * |
||
318 | * @param $option |
||
319 | * @param mixed $default |
||
320 | * |
||
321 | * @return mixed |
||
322 | */ |
||
323 | 71 | public function getOption($option, $default = null) |
|
327 | |||
328 | /** |
||
329 | * Set field options |
||
330 | * |
||
331 | * @param array $options |
||
332 | * @return $this |
||
333 | */ |
||
334 | 11 | public function setOptions($options) |
|
340 | |||
341 | /** |
||
342 | * Set single option on the field |
||
343 | * |
||
344 | * @param string $name |
||
345 | * @param mixed $value |
||
346 | * @return $this |
||
347 | */ |
||
348 | 71 | public function setOption($name, $value) |
|
354 | |||
355 | /** |
||
356 | * Get the type of the field |
||
357 | * |
||
358 | * @return string |
||
359 | */ |
||
360 | 41 | public function getType() |
|
364 | |||
365 | /** |
||
366 | * Set type of the field |
||
367 | * |
||
368 | * @param mixed $type |
||
369 | * @return $this |
||
370 | */ |
||
371 | 1 | public function setType($type) |
|
379 | |||
380 | /** |
||
381 | * @return Form |
||
382 | */ |
||
383 | 71 | public function getParent() |
|
387 | |||
388 | /** |
||
389 | * Check if the field is rendered |
||
390 | * |
||
391 | * @return bool |
||
392 | */ |
||
393 | 4 | public function isRendered() |
|
397 | |||
398 | /** |
||
399 | * Default options for field |
||
400 | * |
||
401 | * @return array |
||
402 | */ |
||
403 | 51 | protected function getDefaults() |
|
407 | |||
408 | /** |
||
409 | * Defaults used across all fields |
||
410 | * |
||
411 | * @return array |
||
412 | */ |
||
413 | 71 | private function allDefaults() |
|
432 | |||
433 | /** |
||
434 | * Get real name of the field without form namespace |
||
435 | * |
||
436 | * @return string |
||
437 | */ |
||
438 | 70 | public function getRealName() |
|
442 | |||
443 | /** |
||
444 | * @param $value |
||
445 | * @return $this |
||
446 | */ |
||
447 | 65 | public function setValue($value) |
|
467 | |||
468 | /** |
||
469 | * Set the template property on the object |
||
470 | */ |
||
471 | 71 | private function setTemplate() |
|
475 | |||
476 | /** |
||
477 | * Add error class to wrapper if validation errors exist |
||
478 | */ |
||
479 | 71 | protected function addErrorClass() |
|
493 | |||
494 | |||
495 | /** |
||
496 | * Merge all defaults with field specific defaults and set template if passed |
||
497 | * |
||
498 | * @param array $options |
||
499 | */ |
||
500 | 71 | protected function setDefaultOptions(array $options = []) |
|
506 | |||
507 | 71 | protected function setupLabel() |
|
521 | |||
522 | /** |
||
523 | * Check if fields needs label |
||
524 | * |
||
525 | * @return bool |
||
526 | */ |
||
527 | 27 | protected function needsLabel() |
|
538 | |||
539 | /** |
||
540 | * Disable field |
||
541 | * |
||
542 | * @return $this |
||
543 | */ |
||
544 | 1 | public function disable() |
|
550 | |||
551 | /** |
||
552 | * Enable field |
||
553 | * |
||
554 | * @return $this |
||
555 | */ |
||
556 | 1 | public function enable() |
|
562 | |||
563 | /** |
||
564 | * Get validation rules for a field if any with label for attributes |
||
565 | * |
||
566 | * @return array|null |
||
567 | */ |
||
568 | 4 | public function getValidationRules() |
|
594 | |||
595 | /** |
||
596 | * Get value property |
||
597 | * |
||
598 | * @param mixed|null $default |
||
599 | * @return mixed |
||
600 | */ |
||
601 | 30 | public function getValue($default = null) |
|
605 | |||
606 | /** |
||
607 | * Get default value property |
||
608 | * |
||
609 | * @param mixed|null $default |
||
610 | * @return mixed |
||
611 | */ |
||
612 | 27 | public function getDefaultValue($default = null) |
|
616 | |||
617 | /** |
||
618 | * Check if provided value is valid for this type |
||
619 | * |
||
620 | * @return bool |
||
621 | */ |
||
622 | 66 | protected function isValidValue($value) |
|
626 | } |
||
627 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.