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]):""); |
||
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 |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.