Complex classes like Parse 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 Parse, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 38 | class Parse {
|
||
| 39 | /** |
||
| 40 | * @param array $indents Array of indent levels |
||
| 41 | */ |
||
| 42 | protected $indents; |
||
| 43 | /** |
||
| 44 | * @var Tag[] Array of Root Document Tags |
||
| 45 | */ |
||
| 46 | public $root; |
||
| 47 | /** |
||
| 48 | * @var array $lines Each Line read in from template |
||
| 49 | */ |
||
| 50 | protected $lines; |
||
| 51 | /** |
||
| 52 | * Regex for parsing each HAMLE line |
||
| 53 | */ |
||
| 54 | |||
| 55 | const REGEX_PARSE_LINE = <<<'ENDREGEX' |
||
| 56 | /^(\s*)(?:(?:([a-zA-Z0-9-]*)((?:[\.#!][\w\-\_]+)*)(\[(?:(?:\{\$[^\}]+\})?[^\\\]{]*?(?:\\.)*?(?:{[^\$])*?)+\])?)|([_\/]{1,2})|([\|:\$]\w+)|({?\$[^}]+}?)|)(?: (.*))?$/
|
||
| 57 | ENDREGEX; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @var int Current Line Number |
||
| 61 | */ |
||
| 62 | protected $lineNo; |
||
| 63 | /** |
||
| 64 | * @var int Total Lines in File |
||
| 65 | */ |
||
| 66 | protected $lineCount; |
||
| 67 | |||
| 68 | function __construct() {
|
||
| 69 | $this->init(); |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Clear Lines, and Line Number, so if output is |
||
| 74 | * called, no output will be produced |
||
| 75 | */ |
||
| 76 | protected function init() {
|
||
| 77 | $this->lines = array(); |
||
| 78 | $this->lineNo = 0; |
||
| 79 | $this->lineCount = 0; |
||
| 80 | $this->root = array(); |
||
| 81 | } |
||
| 82 | |||
| 83 | protected function loadLines($s) {
|
||
| 84 | $this->lines = explode("\n", str_replace("\r", "", $s));
|
||
| 85 | $this->lineCount = count($this->lines); |
||
| 86 | $this->lineNo = 0; |
||
| 87 | } |
||
| 88 | |||
| 89 | function parseFilter(ParseFilter $filter) {
|
||
| 90 | foreach ($this->root as $k => $tag) {
|
||
| 91 | $this->root[$k] = $filter->filterTag($tag); |
||
| 92 | } |
||
| 93 | } |
||
| 94 | |||
| 95 | function parseSnip($s) {
|
||
| 96 | //save root tags |
||
| 97 | /** @var Tag[] $roots */ |
||
| 98 | $roots = $this->root; |
||
| 99 | $this->root = array(); |
||
| 100 | $this->loadLines($s); |
||
| 101 | $this->procLines(); |
||
| 102 | $this->root = array_merge($roots, $this->root); |
||
| 103 | } |
||
| 104 | |||
| 105 | function applySnip() {
|
||
| 106 | /** @var Tag\Snippet[] $fwdSnip */ |
||
| 107 | $fwdSnip = array(); |
||
| 108 | /** @var Tag\Snippet[] $revSnip */ |
||
| 109 | $revSnip = array(); |
||
| 110 | /** @var Tag[] $roots */ |
||
| 111 | $roots = array(); |
||
| 112 | foreach ($this->root as $snip) |
||
| 113 | if ($snip instanceOf Tag\Snippet) {
|
||
| 114 | if ($snip->getType() == "append") {
|
||
| 115 | array_unshift($revSnip, $snip); |
||
| 116 | } else {
|
||
| 117 | $fwdSnip[] = $snip; |
||
| 118 | } |
||
| 119 | } else {
|
||
| 120 | $roots[] = $snip; |
||
| 121 | } |
||
| 122 | foreach ($fwdSnip as $snip) |
||
| 123 | foreach ($roots as $root) |
||
| 124 | $snip->apply($root); |
||
| 125 | foreach ($revSnip as $snip) |
||
| 126 | foreach ($roots as $root) |
||
| 127 | $snip->apply($root); |
||
| 128 | $this->root = $roots; |
||
| 129 | } |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Parse HAMLE template, from a string |
||
| 133 | * @param string $s String to parse |
||
| 134 | * @return string Parsed HAMLE as HTML |
||
| 135 | */ |
||
| 136 | function str($s) {
|
||
| 137 | $this->init(); |
||
| 138 | $this->loadLines($s); |
||
| 139 | $this->procLines(); |
||
| 140 | } |
||
| 141 | |||
| 142 | function procLines() {
|
||
| 143 | /* @var $heir Tag[] Tag Heirachy Array */ |
||
| 144 | $heir = array(); |
||
| 145 | while ($this->lineNo < $this->lineCount) {
|
||
| 146 | $line = $this->lines[$this->lineNo]; |
||
| 147 | if (trim($line)) if (preg_match(self::REGEX_PARSE_LINE, $line, $m)) {
|
||
| 148 | if (FALSE !== strpos($m[1], "\t")) |
||
| 149 | throw new ParseError("Tabs are not supported in templates at this time");
|
||
| 150 | $indent = strlen($m[1]); |
||
| 151 | $tag = isset($m[2]) ? $tag = $m[2] : ""; |
||
| 152 | $classid = isset($m[3]) ? $m[3] : ""; |
||
| 153 | $params = str_replace(array('\[', '\]', '\\&'), array('[', ']', '%26'), isset($m[4]) ? $m[4] : "");
|
||
| 154 | $textcode = isset($m[5]) ? $m[5] : ""; |
||
| 155 | $text = isset($m[8]) ? $m[8] : ""; |
||
| 156 | $code = isset($m[6]) ? $m[6] : ""; |
||
| 157 | $i = self::indentLevel($indent); |
||
| 158 | unset($m[0]); |
||
| 159 | switch (strlen($code) ? $code[0] : ($textcode ? $textcode : "")) {
|
||
| 160 | case "|": //Control Tag |
||
| 161 | if ($code == "|snippet") |
||
| 162 | $hTag = new Tag\Snippet($text); |
||
| 163 | elseif ($code == "|form") |
||
| 164 | $hTag = new Tag\Form($text); |
||
| 165 | elseif ($code == "|formhint") |
||
| 166 | $hTag = new Tag\FormHint($text); |
||
| 167 | elseif ($code == "|else") {
|
||
| 168 | $hTag = new Tag\Control(substr($code, 1), $heir[$i - 1]); |
||
| 169 | $hTag->setVar($text); |
||
| 170 | } else {
|
||
| 171 | $hTag = new Tag\Control(substr($code, 1)); |
||
| 172 | $hTag->setVar($text); |
||
| 173 | } |
||
| 174 | break; |
||
| 175 | case ":": //Filter Tag |
||
| 176 | $hTag = new Tag\Filter(substr($code, 1)); |
||
| 177 | $hTag->addContent($text, Text::TOKEN_CODE); |
||
| 178 | foreach ($this->consumeBlock($indent) as $l) |
||
| 179 | $hTag->addContent($l, Text::TOKEN_CODE); |
||
| 180 | break; |
||
| 181 | case "_": //String Tag |
||
| 182 | case "__": //Unescape String Tag |
||
| 183 | $hTag = new Tag\Text($textcode); |
||
| 184 | $hTag->addContent($text); |
||
| 185 | break; |
||
| 186 | case "/": // HTML Comment |
||
| 187 | case "//": // Non Printed Comment |
||
| 188 | $hTag = new Tag\Comment($textcode); |
||
| 189 | $hTag->addContent($text); |
||
| 190 | foreach ($this->consumeBlock($indent) as $l) |
||
| 191 | $hTag->addContent($l, Text::TOKEN_CODE); |
||
| 192 | break; |
||
| 193 | default: |
||
| 194 | $attr = array(); |
||
| 195 | if(isset($params[0]) && $params[0] == "[") {
|
||
| 196 | $param = substr($params, 1, strlen($params) - 2); |
||
| 197 | $param = str_replace('+','%2B', $param);
|
||
| 198 | $param = str_replace('\\&','%26', $param);
|
||
| 199 | // parse_str($param, $attr); |
||
| 200 | $attr = $this->parseQueryString($param); |
||
| 201 | } |
||
| 202 | $class = array(); $id = ""; $ref = ""; |
||
| 203 | preg_match_all('/[#\.!][a-zA-Z0-9\-\_]+/m', $classid, $cid);
|
||
| 204 | if (isset($cid[0])) foreach ($cid[0] as $s) {
|
||
| 205 | if ($s[0] == "#") $id = substr($s, 1); |
||
| 206 | if ($s[0] == ".") $class[] = substr($s, 1); |
||
| 207 | if ($s[0] == "!") $ref = substr($s, 1); |
||
| 208 | } |
||
| 209 | if($ref) |
||
| 210 | $hTag = new Tag\DynHtml($tag, $class, $attr, $id, $ref); |
||
| 211 | else |
||
| 212 | $hTag = new Tag\Html($tag, $class, $attr, $id); |
||
| 213 | $hTag->addContent($text); |
||
| 214 | break; |
||
| 215 | } |
||
| 216 | $heir[$i] = $hTag; |
||
| 217 | if ($i > 0) |
||
| 218 | $heir[$i - 1]->addChild($hTag); |
||
| 219 | else |
||
| 220 | $this->root[] = $hTag; |
||
| 221 | } else |
||
| 222 | throw new ParseError("Unable to parse line {$this->lineNo}\n\"$line\"/" . preg_last_error());
|
||
| 223 | $this->lineNo++; |
||
| 224 | } |
||
| 225 | } |
||
| 226 | |||
| 227 | function parseQueryString($qs) {
|
||
| 228 | $out = []; |
||
| 229 | foreach(explode('&',$qs) as $s) {
|
||
| 230 | $kv = explode('=',$s,2);
|
||
| 231 | $out[urldecode($kv[0])] = isset($kv[1])?urldecode($kv[1]):null; |
||
| 232 | } |
||
| 233 | return $out; |
||
| 234 | } |
||
| 235 | |||
| 236 | function output() {
|
||
| 237 | $out = "<?php\nuse Seufert\\Hamle;\n?>"; |
||
| 238 | foreach ($this->root as $tag) |
||
| 239 | $out .= $tag->render(); |
||
| 240 | return $out; |
||
| 241 | |||
| 242 | } |
||
| 243 | |||
| 244 | function consumeBlock($indent) {
|
||
| 245 | $out = array(); |
||
| 246 | $m = array(); |
||
| 247 | while ($this->lineNo + 1 < $this->lineCount && |
||
| 248 | (!trim($this->lines[$this->lineNo + 1]) || |
||
| 249 | preg_match('/^(\s){' . $indent . '}((\s)+[^\s].*)$/',
|
||
| 250 | $this->lines[$this->lineNo + 1], $m))) {
|
||
| 251 | if (trim($this->lines[$this->lineNo + 1])) |
||
| 252 | $out[] = $m[2]; |
||
| 253 | $this->lineNo++; |
||
| 254 | } |
||
| 255 | return $out; |
||
| 256 | } |
||
| 257 | |||
| 258 | function indentLevel($indent) {
|
||
| 259 | if (!isset($this->indents)) $this->indents = array(); |
||
| 260 | if (!count($this->indents)) {
|
||
| 261 | $this->indents = array(0 => $indent); |
||
| 262 | // Key = indent level, Value = Depth in spaces |
||
| 263 | return 0; |
||
| 264 | } |
||
| 265 | foreach ($this->indents as $k => $v) {
|
||
| 266 | if ($v == $indent) {
|
||
| 267 | $this->indents = array_slice($this->indents, 0, $k + 1); |
||
| 268 | return $k; |
||
| 269 | } |
||
| 270 | } |
||
| 271 | $this->indents[] = $indent; |
||
| 272 | return max(array_keys($this->indents)); |
||
| 273 | } |
||
| 274 | |||
| 275 | function getLineNo() {
|
||
| 276 | return $this->lineNo; |
||
| 277 | } |
||
| 278 | } |
||
| 279 |