Complex classes like HtmlElement 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 HtmlElement, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class HtmlElement implements HtmlElementInterface |
||
10 | { |
||
11 | /** @var array */ |
||
12 | private $map; |
||
13 | |||
14 | /** @var EscaperInterface */ |
||
15 | private $escaper; |
||
16 | |||
17 | /** @var BranchValidatorInterface */ |
||
18 | private $branchValidator; |
||
19 | |||
20 | /** @var array The already resolved elements */ |
||
21 | private $resolved = array(); |
||
22 | |||
23 | /** @var array The default values of element options */ |
||
24 | private $defaults = array( |
||
25 | 'parent' => null, |
||
26 | 'children' => array(), |
||
27 | 'extends' => array(), |
||
28 | 'attr' => array(), |
||
29 | 'text' => null, |
||
30 | 'type' => null, |
||
31 | 'class' => Element::class |
||
32 | ); |
||
33 | |||
34 | /** @var array The mergeable attributes */ |
||
35 | private $mergeableAttributes = array('class', 'style'); |
||
36 | |||
37 | /** |
||
38 | * HtmlElement constructor. |
||
39 | * |
||
40 | * @param array $map The elements map |
||
41 | * @param BranchValidatorInterface|null $branchValidator The branch validator |
||
42 | * @param EscaperInterface|null $escaper The escaper, by default ZendFramework/Escaper is used |
||
43 | * @param string $encoding The encoding used for escaping, by default utf-8 is used |
||
44 | */ |
||
45 | public function __construct( |
||
55 | |||
56 | /** |
||
57 | * Load element on dynamic calls. |
||
58 | * |
||
59 | * @param string $name The element name |
||
60 | * @param array $arguments The arguments array to set: |
||
61 | * [0] = text (string|null) |
||
62 | * [1] = attributes (array) |
||
63 | * [2] = parameters (array) |
||
64 | * [3] = extras (array) |
||
65 | * [4] = children (array) |
||
66 | * |
||
67 | * @return ElementInterface |
||
68 | * |
||
69 | * @throws InvalidArgumentsNumberException If the arguments length is more than 3 |
||
70 | */ |
||
71 | public function __call($name, $arguments) |
||
77 | |||
78 | /** |
||
79 | * {@inheritdoc} |
||
80 | */ |
||
81 | public function getMap(): array |
||
85 | |||
86 | /** |
||
87 | * {@inheritdoc} |
||
88 | */ |
||
89 | public function setMap(array $map): HtmlElementInterface |
||
95 | |||
96 | /** |
||
97 | * {@inheritdoc} |
||
98 | */ |
||
99 | public function addManyToMap(array $elements): HtmlElementInterface |
||
107 | |||
108 | /** |
||
109 | * {@inheritdoc} |
||
110 | */ |
||
111 | public function addOneToMap(string $name, array $element): HtmlElementInterface |
||
117 | |||
118 | /** |
||
119 | * {@inheritdoc} |
||
120 | */ |
||
121 | public function getBranchValidator(): BranchValidatorInterface |
||
125 | |||
126 | /** |
||
127 | * {@inheritdoc} |
||
128 | */ |
||
129 | public function setBranchValidator(BranchValidatorInterface $branchValidator): HtmlElementInterface |
||
135 | |||
136 | /** |
||
137 | * {@inheritdoc} |
||
138 | */ |
||
139 | public function getEscaper(): EscaperInterface |
||
143 | |||
144 | /** |
||
145 | * {@inheritdoc} |
||
146 | */ |
||
147 | public function setEscaper(EscaperInterface $escaper): HtmlElementInterface |
||
153 | |||
154 | /** |
||
155 | * {@inheritdoc} |
||
156 | */ |
||
157 | public function load( |
||
176 | |||
177 | /** |
||
178 | * Get the element instance. |
||
179 | * |
||
180 | * @param string|array $name The element name |
||
181 | * @param string|null $text The element text |
||
182 | * @param array $extras The element extras |
||
183 | * @param array $parameters The parameters to replace in element |
||
184 | * @param bool $mainCall Determine if it's the main(first) call of the method |
||
185 | * |
||
186 | * @return ElementInterface |
||
187 | * |
||
188 | * @throws InvalidElementException If the current instance doesn't implement ElementInterface |
||
189 | */ |
||
190 | private function getInstance( |
||
227 | |||
228 | /** |
||
229 | * Get the resolved element representation. |
||
230 | * |
||
231 | * @param string|array $name The current element name |
||
232 | * @param array $parameters The parameters to replace in element |
||
233 | * @param bool $mainCall Determine if it's the main(first) call of the method |
||
234 | * |
||
235 | * @return array |
||
236 | */ |
||
237 | private function resolveElement($name, array $parameters, bool $mainCall = false): array |
||
268 | |||
269 | /** |
||
270 | * Check if an element has been already resolved. |
||
271 | * |
||
272 | * @param string $name |
||
273 | * |
||
274 | * @return bool |
||
275 | */ |
||
276 | private function alreadyResolved(string $name): bool |
||
280 | |||
281 | /** |
||
282 | * {@inheritdoc} |
||
283 | */ |
||
284 | public function exists(string $name): bool |
||
288 | |||
289 | /** |
||
290 | * Get the current element representation. |
||
291 | * |
||
292 | * @param string|array $name The element name |
||
293 | * |
||
294 | * @return array |
||
295 | * |
||
296 | * @throws InvalidElementException If the current element is defined dynamically and doesn't define a name |
||
297 | * @throws UndefinedElementException If the current element doesn't exist |
||
298 | */ |
||
299 | public function getCurrentElement($name): array |
||
320 | |||
321 | /** |
||
322 | * Replace parameters in text and attr. |
||
323 | * |
||
324 | * @param array $element The element with the parameters to replace |
||
325 | * @param array $parameters The array of parameters values |
||
326 | * |
||
327 | * @return array |
||
328 | */ |
||
329 | private function replaceParameters(array $element, array $parameters): array |
||
339 | |||
340 | /** |
||
341 | * Replace parameters in attr. |
||
342 | * |
||
343 | * @param array $attributes The attributes |
||
344 | * @param array $parameters The parameters |
||
345 | * |
||
346 | * @return array |
||
347 | */ |
||
348 | private function replaceParametersInAttributes(array $attributes, array $parameters): array |
||
368 | |||
369 | /** |
||
370 | * Extend element from another one. |
||
371 | * |
||
372 | * @param array $extend The array of the element to extend |
||
373 | * @param array $current The current element which extends |
||
374 | * |
||
375 | * @return array |
||
376 | */ |
||
377 | private function extendElement(array $extend, array $current): array |
||
393 | |||
394 | /** |
||
395 | * Extend attributes from another element. |
||
396 | * |
||
397 | * @param array $from The array of attributes to extend |
||
398 | * @param array $to The array of attributes which extends |
||
399 | * |
||
400 | * @return array |
||
401 | */ |
||
402 | private function extendAttributes(array $from, array $to): array |
||
416 | |||
417 | /** |
||
418 | * Extend mergeable attributes from another element. |
||
419 | * |
||
420 | * @param string|array $from The attribute to extend |
||
421 | * @param string|array $to The attribute which extends |
||
422 | * @param string $attr The attribute name |
||
423 | * |
||
424 | * @return string |
||
425 | */ |
||
426 | private function extendMergeableAttributes($from, $to, string $attr): string |
||
437 | } |
||
438 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.