Complex classes like Compiler 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 Compiler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class Compiler { |
||
15 | const T_IF = 'x-if'; |
||
|
|||
16 | const T_EACH = 'x-each'; |
||
17 | const T_WITH = 'x-with'; |
||
18 | const T_LITERAL = 'x-literal'; |
||
19 | const T_AS = 'x-as'; |
||
20 | const T_COMPONENT = 'x-component'; |
||
21 | const T_CHILDREN = 'x-children'; |
||
22 | const T_BLOCK = 'x-block'; |
||
23 | const T_ELSE = 'x-else'; |
||
24 | const T_EMPTY = 'x-empty'; |
||
25 | const T_X = 'x'; |
||
26 | const T_INCLUDE = 'x-include'; |
||
27 | const T_EBI = 'ebi'; |
||
28 | const T_UNESCAPE = 'x-unescape'; |
||
29 | const T_TAG = 'x-tag'; |
||
30 | |||
31 | const IDENT_REGEX = '`^([a-z0-9-]+)$`i'; |
||
32 | |||
33 | protected static $special = [ |
||
34 | self::T_COMPONENT => 1, |
||
35 | self::T_IF => 2, |
||
36 | self::T_ELSE => 3, |
||
37 | self::T_EACH => 4, |
||
38 | self::T_EMPTY => 5, |
||
39 | self::T_CHILDREN => 6, |
||
40 | self::T_INCLUDE => 7, |
||
41 | self::T_WITH => 8, |
||
42 | self::T_BLOCK => 9, |
||
43 | self::T_LITERAL => 10, |
||
44 | self::T_AS => 11, |
||
45 | self::T_UNESCAPE => 12, |
||
46 | self::T_TAG => 13 |
||
47 | ]; |
||
48 | |||
49 | protected static $htmlTags = [ |
||
50 | 'a' => 'i', |
||
51 | 'abbr' => 'i', |
||
52 | 'acronym' => 'i', // deprecated |
||
53 | 'address' => 'b', |
||
54 | // 'applet' => 'i', // deprecated |
||
55 | 'area' => 'i', |
||
56 | 'article' => 'b', |
||
57 | 'aside' => 'b', |
||
58 | 'audio' => 'i', |
||
59 | 'b' => 'i', |
||
60 | 'base' => 'i', |
||
61 | // 'basefont' => 'i', |
||
62 | 'bdi' => 'i', |
||
63 | 'bdo' => 'i', |
||
64 | // 'bgsound' => 'i', |
||
65 | // 'big' => 'i', |
||
66 | 'x' => 'i', |
||
67 | // 'blink' => 'i', |
||
68 | 'blockquote' => 'b', |
||
69 | 'body' => 'b', |
||
70 | 'br' => 'i', |
||
71 | 'button' => 'i', |
||
72 | 'canvas' => 'b', |
||
73 | 'caption' => 'i', |
||
74 | // 'center' => 'b', |
||
75 | 'cite' => 'i', |
||
76 | 'code' => 'i', |
||
77 | 'col' => 'i', |
||
78 | 'colgroup' => 'i', |
||
79 | // 'command' => 'i', |
||
80 | 'content' => 'i', |
||
81 | 'data' => 'i', |
||
82 | 'datalist' => 'i', |
||
83 | 'dd' => 'b', |
||
84 | 'del' => 'i', |
||
85 | 'details' => 'i', |
||
86 | 'dfn' => 'i', |
||
87 | 'dialog' => 'i', |
||
88 | // 'dir' => 'i', |
||
89 | 'div' => 'i', |
||
90 | 'dl' => 'b', |
||
91 | 'dt' => 'b', |
||
92 | // 'element' => 'i', |
||
93 | 'em' => 'i', |
||
94 | 'embed' => 'i', |
||
95 | 'fieldset' => 'b', |
||
96 | 'figcaption' => 'b', |
||
97 | 'figure' => 'b', |
||
98 | // 'font' => 'i', |
||
99 | 'footer' => 'b', |
||
100 | 'form' => 'b', |
||
101 | 'frame' => 'i', |
||
102 | 'frameset' => 'i', |
||
103 | 'h1' => 'b', |
||
104 | 'h2' => 'b', |
||
105 | 'h3' => 'b', |
||
106 | 'h4' => 'b', |
||
107 | 'h5' => 'b', |
||
108 | 'h6' => 'b', |
||
109 | 'head' => 'b', |
||
110 | 'header' => 'b', |
||
111 | 'hgroup' => 'b', |
||
112 | 'hr' => 'b', |
||
113 | 'html' => 'b', |
||
114 | 'i' => 'i', |
||
115 | 'iframe' => 'i', |
||
116 | 'image' => 'i', |
||
117 | 'img' => 'i', |
||
118 | 'input' => 'i', |
||
119 | 'ins' => 'i', |
||
120 | 'isindex' => 'i', |
||
121 | 'kbd' => 'i', |
||
122 | 'keygen' => 'i', |
||
123 | 'label' => 'i', |
||
124 | 'legend' => 'i', |
||
125 | 'li' => 'i', |
||
126 | 'link' => 'i', |
||
127 | // 'listing' => 'i', |
||
128 | 'main' => 'b', |
||
129 | 'map' => 'i', |
||
130 | 'mark' => 'i', |
||
131 | // 'marquee' => 'i', |
||
132 | 'menu' => 'i', |
||
133 | 'menuitem' => 'i', |
||
134 | 'meta' => 'i', |
||
135 | 'meter' => 'i', |
||
136 | 'multicol' => 'i', |
||
137 | 'nav' => 'b', |
||
138 | 'nobr' => 'i', |
||
139 | 'noembed' => 'i', |
||
140 | 'noframes' => 'i', |
||
141 | 'noscript' => 'b', |
||
142 | 'object' => 'i', |
||
143 | 'ol' => 'b', |
||
144 | 'optgroup' => 'i', |
||
145 | 'option' => 'b', |
||
146 | 'output' => 'i', |
||
147 | 'p' => 'b', |
||
148 | 'param' => 'i', |
||
149 | 'picture' => 'i', |
||
150 | // 'plaintext' => 'i', |
||
151 | 'pre' => 'b', |
||
152 | 'progress' => 'i', |
||
153 | 'q' => 'i', |
||
154 | 'rp' => 'i', |
||
155 | 'rt' => 'i', |
||
156 | 'rtc' => 'i', |
||
157 | 'ruby' => 'i', |
||
158 | 's' => 'i', |
||
159 | 'samp' => 'i', |
||
160 | 'script' => 'i', |
||
161 | 'section' => 'b', |
||
162 | 'select' => 'i', |
||
163 | // 'shadow' => 'i', |
||
164 | 'slot' => 'i', |
||
165 | 'small' => 'i', |
||
166 | 'source' => 'i', |
||
167 | // 'spacer' => 'i', |
||
168 | 'span' => 'i', |
||
169 | // 'strike' => 'i', |
||
170 | 'strong' => 'i', |
||
171 | 'style' => 'i', |
||
172 | 'sub' => 'i', |
||
173 | 'summary' => 'i', |
||
174 | 'sup' => 'i', |
||
175 | 'table' => 'b', |
||
176 | 'tbody' => 'i', |
||
177 | 'td' => 'i', |
||
178 | 'template' => 'i', |
||
179 | 'textarea' => 'i', |
||
180 | 'tfoot' => 'b', |
||
181 | 'th' => 'i', |
||
182 | 'thead' => 'i', |
||
183 | 'time' => 'i', |
||
184 | 'title' => 'i', |
||
185 | 'tr' => 'i', |
||
186 | 'track' => 'i', |
||
187 | // 'tt' => 'i', |
||
188 | 'u' => 'i', |
||
189 | 'ul' => 'b', |
||
190 | 'var' => 'i', |
||
191 | 'video' => 'b', |
||
192 | 'wbr' => 'i', |
||
193 | |||
194 | /// SVG /// |
||
195 | 'animate' => 's', |
||
196 | 'animateColor' => 's', |
||
197 | 'animateMotion' => 's', |
||
198 | 'animateTransform' => 's', |
||
199 | // 'canvas' => 's', |
||
200 | 'circle' => 's', |
||
201 | 'desc' => 's', |
||
202 | 'defs' => 's', |
||
203 | 'discard' => 's', |
||
204 | 'ellipse' => 's', |
||
205 | 'g' => 's', |
||
206 | // 'image' => 's', |
||
207 | 'line' => 's', |
||
208 | 'marker' => 's', |
||
209 | 'mask' => 's', |
||
210 | 'missing-glyph' => 's', |
||
211 | 'mpath' => 's', |
||
212 | 'metadata' => 's', |
||
213 | 'path' => 's', |
||
214 | 'pattern' => 's', |
||
215 | 'polygon' => 's', |
||
216 | 'polyline' => 's', |
||
217 | 'rect' => 's', |
||
218 | 'set' => 's', |
||
219 | 'svg' => 's', |
||
220 | 'switch' => 's', |
||
221 | 'symbol' => 's', |
||
222 | 'text' => 's', |
||
223 | // 'unknown' => 's', |
||
224 | 'use' => 's', |
||
225 | ]; |
||
226 | |||
227 | /** |
||
228 | * @var ExpressionLanguage |
||
229 | */ |
||
230 | protected $expressions; |
||
231 | |||
232 | 60 | public function __construct() { |
|
244 | |||
245 | /** |
||
246 | * Register a runtime function. |
||
247 | * |
||
248 | * @param string $name The name of the function. |
||
249 | * @param callable $function The function callback. |
||
250 | */ |
||
251 | 58 | public function defineFunction($name, $function = null) { |
|
262 | |||
263 | 58 | private function getFunctionEvaluator($function) { |
|
276 | |||
277 | 58 | private function getFunctionCompiler($name, $function) { |
|
301 | |||
302 | 52 | public function compile($src, array $options = []) { |
|
354 | |||
355 | 43 | protected function isComponent($tag) { |
|
358 | |||
359 | 52 | protected function compileNode(DOMNode $node, CompilerBuffer $out) { |
|
387 | |||
388 | protected function domToArray(DOMNode $root) { |
||
425 | |||
426 | 1 | protected function newline(DOMNode $node, CompilerBuffer $out) { |
|
431 | |||
432 | 1 | protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) { |
|
440 | |||
441 | 47 | protected function compileTextNode(DOMNode $node, CompilerBuffer $out) { |
|
465 | |||
466 | 49 | protected function compileElementNode(DOMElement $node, CompilerBuffer $out) { |
|
467 | 49 | list($attributes, $special) = $this->splitAttributes($node); |
|
468 | |||
469 | 49 | if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) { |
|
470 | 5 | $this->compileExpressionNode($node, $attributes, $special, $out); |
|
471 | 45 | } elseif (!empty($special) || $this->isComponent($node->tagName)) { |
|
472 | 32 | $this->compileSpecialNode($node, $attributes, $special, $out); |
|
473 | } else { |
||
474 | 25 | $this->compileOpenTag($node, $attributes, $special, $out); |
|
475 | |||
476 | 25 | foreach ($node->childNodes as $childNode) { |
|
477 | 24 | $this->compileNode($childNode, $out); |
|
478 | } |
||
479 | |||
480 | 25 | $this->compileCloseTag($node, $special, $out); |
|
481 | } |
||
482 | 49 | } |
|
483 | |||
484 | 47 | protected function splitExpressions($value) { |
|
488 | |||
489 | 47 | protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) { |
|
508 | |||
509 | /** |
||
510 | * Get the compiler function to wrap an attribute. |
||
511 | * |
||
512 | * Attribute functions are regular expression functions, but with a special naming convention. The following naming |
||
513 | * conventions are supported: |
||
514 | * |
||
515 | * - **@tag:attribute**: Applies to an attribute only on a specific tag. |
||
516 | * - **@attribute**: Applies to all attributes with a given name. |
||
517 | * |
||
518 | * @param DOMAttr $attr The attribute to look at. |
||
519 | * @return callable|null A function or **null** if the attribute doesn't have a function. |
||
520 | */ |
||
521 | 20 | private function getAttributeFunction(DOMAttr $attr) { |
|
530 | |||
531 | /** |
||
532 | * @param DOMElement $node |
||
533 | */ |
||
534 | 49 | protected function splitAttributes(DOMElement $node) { |
|
552 | |||
553 | 32 | protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
595 | |||
596 | /** |
||
597 | * Compile component registering. |
||
598 | * |
||
599 | * @param DOMElement $node |
||
600 | * @param $attributes |
||
601 | * @param $special |
||
602 | * @param CompilerBuffer $out |
||
603 | */ |
||
604 | 9 | public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) { |
|
624 | |||
625 | 1 | private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
648 | |||
649 | /** |
||
650 | * Compile component inclusion and rendering. |
||
651 | * |
||
652 | * @param DOMElement $node |
||
653 | * @param DOMAttr[] $attributes |
||
654 | * @param DOMAttr[] $special |
||
655 | * @param CompilerBuffer $out |
||
656 | */ |
||
657 | 11 | protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
694 | |||
695 | /** |
||
696 | * @param DOMElement $parent |
||
697 | * @return CompilerBuffer |
||
698 | */ |
||
699 | 11 | protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) { |
|
727 | |||
728 | 23 | protected function compileTagComment(DOMElement $node, $attributes, $special, CompilerBuffer $out) { |
|
745 | |||
746 | /** |
||
747 | * @param DOMElement $node |
||
748 | * @param DOMAttr[] $attributes |
||
749 | * @param DOMAttr[] $special |
||
750 | * @param CompilerBuffer $out |
||
751 | * @param bool $force |
||
752 | */ |
||
753 | 44 | protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) { |
|
754 | 44 | $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : ''; |
|
755 | |||
756 | 44 | if ($node->tagName === self::T_X && empty($tagNameExpr)) { |
|
757 | 4 | return; |
|
758 | } |
||
759 | |||
760 | 43 | if (!empty($tagNameExpr)) { |
|
761 | $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]); |
||
762 | $out->echoLiteral('<'); |
||
763 | $out->echoCode($tagNameExpr); |
||
764 | } else { |
||
765 | 43 | $out->echoLiteral('<'.$node->tagName); |
|
766 | } |
||
767 | |||
768 | /* @var DOMAttr $attribute */ |
||
769 | 43 | foreach ($attributes as $name => $attribute) { |
|
770 | // Check for an attribute expression. |
||
771 | 15 | if ($this->isExpression($attribute->value)) { |
|
772 | 12 | $out->echoCode( |
|
773 | 12 | '$this->attribute('.var_export($name, true).', '. |
|
774 | 12 | $this->expr(substr($attribute->value, 1, -1), $out, $attribute). |
|
775 | 12 | ')'); |
|
776 | 3 | } elseif (null !== $fn = $this->getAttributeFunction($attribute)) { |
|
777 | 2 | $value = call_user_func($fn, var_export($attribute->value, true)); |
|
778 | |||
779 | 2 | $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')'); |
|
780 | } else { |
||
781 | 2 | $out->echoLiteral(' '.$name.'="'); |
|
782 | 2 | $out->echoLiteral(htmlspecialchars($attribute->value)); |
|
783 | 15 | $out->echoLiteral('"'); |
|
784 | } |
||
785 | } |
||
786 | |||
787 | 43 | if ($node->hasChildNodes() || $force) { |
|
788 | 42 | $out->echoLiteral('>'); |
|
789 | } else { |
||
790 | 2 | $out->echoLiteral(" />"); |
|
791 | } |
||
792 | 43 | } |
|
793 | |||
794 | 18 | private function isExpression($value) { |
|
797 | |||
798 | 44 | protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) { |
|
813 | |||
814 | 4 | protected function isEmptyText(DOMNode $node) { |
|
817 | |||
818 | 11 | protected function isEmptyNode(DOMNode $node) { |
|
834 | |||
835 | 8 | protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
863 | |||
864 | 12 | protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
896 | |||
897 | 1 | protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
917 | |||
918 | 2 | protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
931 | |||
932 | 19 | protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
941 | |||
942 | /** |
||
943 | * Find a special node in relation to another node. |
||
944 | * |
||
945 | * This method is used to find things such as x-empty and x-else elements. |
||
946 | * |
||
947 | * @param DOMElement $node The node to search in relation to. |
||
948 | * @param string $attribute The name of the attribute to search for. |
||
949 | * @param string $parentAttribute The name of the parent attribute to resolve conflicts. |
||
950 | * @return DOMElement|null Returns the found element node or **null** if not found. |
||
951 | */ |
||
952 | 20 | protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) { |
|
981 | |||
982 | /** |
||
983 | * @param DOMElement $node |
||
984 | * @param array $attributes |
||
985 | * @param array $special |
||
986 | * @param CompilerBuffer $out |
||
987 | */ |
||
988 | 12 | private function compileEachLoop(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
1021 | |||
1022 | 47 | protected function ltrim($text, \DOMNode $node, CompilerBuffer $out) { |
|
1042 | |||
1043 | 47 | protected function rtrim($text, \DOMNode $node, CompilerBuffer $out) { |
|
1064 | |||
1065 | 47 | protected function inPre(\DOMNode $node) { |
|
1073 | |||
1074 | 3 | private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
1092 | |||
1093 | /** |
||
1094 | * Compile an x-expr node. |
||
1095 | * |
||
1096 | * @param DOMElement $node The node to compile. |
||
1097 | * @param DOMAttr[] $attributes The node's attributes. |
||
1098 | * @param DOMAttr[] $special An array of special attributes. |
||
1099 | * @param CompilerBuffer $out The compiler output. |
||
1100 | */ |
||
1101 | 5 | private function compileExpressionNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) { |
|
1116 | } |
||
1117 |
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.
To visualize
will produce issues in the first and second line, while this second example
will produce no issues.