Complex classes like AbstractComponent 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 AbstractComponent, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | abstract class AbstractComponent implements ComponentInterface |
||
28 | { |
||
29 | /** |
||
30 | * Unnamed children indexed by numeric position in array |
||
31 | * |
||
32 | * @var ComponentInterface[] |
||
33 | */ |
||
34 | protected $children = []; |
||
35 | |||
36 | /** |
||
37 | * @var string|null |
||
38 | */ |
||
39 | protected $name; |
||
40 | |||
41 | /** |
||
42 | * Specifies whether the escaping interceptors should be disabled or enabled for the result of renderChildren() calls within this ViewHelper |
||
43 | * @see isChildrenEscapingEnabled() |
||
44 | * |
||
45 | * Note: If this is NULL the value of $this->escapingInterceptorEnabled is considered for backwards compatibility |
||
46 | * |
||
47 | * @var boolean|null |
||
48 | */ |
||
49 | protected $escapeChildren = null; |
||
50 | |||
51 | /** |
||
52 | * Specifies whether the escaping interceptors should be disabled or enabled for the render-result of this ViewHelper |
||
53 | * @see isOutputEscapingEnabled() |
||
54 | * |
||
55 | * @var boolean|null |
||
56 | */ |
||
57 | protected $escapeOutput = null; |
||
58 | |||
59 | /** |
||
60 | * @var ArgumentCollection|null |
||
61 | */ |
||
62 | protected $arguments = null; |
||
63 | |||
64 | private $_lastAddedWasTextNode = false; |
||
65 | |||
66 | public function onOpen(RenderingContextInterface $renderingContext): ComponentInterface |
||
70 | |||
71 | public function onClose(RenderingContextInterface $renderingContext): ComponentInterface |
||
75 | |||
76 | public function getName(): ?string |
||
80 | |||
81 | public function setArguments(ArgumentCollection $arguments): ComponentInterface |
||
86 | |||
87 | public function getArguments(): ArgumentCollection |
||
91 | |||
92 | public function addChild(ComponentInterface $component): ComponentInterface |
||
122 | |||
123 | public function getNamedChild(string $name): ComponentInterface |
||
143 | |||
144 | /** |
||
145 | * Gets a new RootNode with children copied from this current |
||
146 | * Component. Scans for children of a specific type (a Component |
||
147 | * class name like a ViewHelper class name) and an optional name |
||
148 | * which if not-null must also be matched (much like getNamedChild, |
||
149 | * except does not error when no children match and is capable of |
||
150 | * returning multiple children if they have the same name). |
||
151 | * |
||
152 | * @param string $typeClassName |
||
153 | * @param string|null $name |
||
154 | * @return ComponentInterface |
||
155 | */ |
||
156 | public function getTypedChildren(string $typeClassName, ?string $name = null): ComponentInterface |
||
181 | |||
182 | /** |
||
183 | * @return ComponentInterface[] |
||
184 | */ |
||
185 | public function getChildren(): iterable |
||
189 | |||
190 | /** |
||
191 | * Returns one of the following: |
||
192 | * |
||
193 | * - Itself, if there is more than one child node and one or more nodes are not TextNode or NumericNode |
||
194 | * - A plain value if there is a single child node of type TextNode or NumericNode |
||
195 | * - The one child node if there is only a single child node not of type TextNode or NumericNode |
||
196 | * - Null if there are no child nodes at all. |
||
197 | * |
||
198 | * @param bool $extractNode If TRUE, will extract the value of a single node if the node type contains a scalar value |
||
199 | * @return ComponentInterface|string|int|float|null |
||
200 | */ |
||
201 | public function flatten(bool $extractNode = false) |
||
217 | |||
218 | /** |
||
219 | * @param iterable|ComponentInterface[] $children |
||
220 | * @return ComponentInterface |
||
221 | */ |
||
222 | public function setChildren(iterable $children): ComponentInterface |
||
228 | |||
229 | /** |
||
230 | * Returns whether the escaping interceptors should be disabled or enabled for the result of renderChildren() calls within this ViewHelper |
||
231 | * |
||
232 | * Note: This method is no public API, use $this->escapeChildren instead! |
||
233 | * |
||
234 | * @return boolean |
||
235 | */ |
||
236 | public function isChildrenEscapingEnabled(): bool |
||
244 | |||
245 | /** |
||
246 | * Returns whether the escaping interceptors should be disabled or enabled for the render-result of this ViewHelper |
||
247 | * |
||
248 | * Note: This method is no public API, use $this->escapeOutput instead! |
||
249 | * |
||
250 | * @return boolean |
||
251 | */ |
||
252 | public function isOutputEscapingEnabled(): bool |
||
256 | |||
257 | public function evaluate(RenderingContextInterface $renderingContext) |
||
261 | |||
262 | public function allowUndeclaredArgument(string $argumentName): bool |
||
266 | |||
267 | /** |
||
268 | * Evaluate all child nodes and return the evaluated results. |
||
269 | * |
||
270 | * @param RenderingContextInterface $renderingContext |
||
271 | * @return mixed Normally, an object is returned - in case it is concatenated with a string, a string is returned. |
||
272 | * @throws Exception |
||
273 | */ |
||
274 | protected function evaluateChildren(RenderingContextInterface $renderingContext) |
||
296 | |||
297 | /** |
||
298 | * @param mixed $value |
||
299 | * @return string |
||
300 | */ |
||
301 | protected function castToString($value): string |
||
308 | } |
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.