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.