Total Complexity | 62 |
Total Lines | 306 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like TokenParser 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.
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 TokenParser, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class TokenParser |
||
12 | { |
||
13 | /** @var array */ |
||
14 | private $structure; |
||
15 | |||
16 | /** @var int */ |
||
17 | private $lastToken; |
||
18 | |||
19 | /** @var StructureBuilder */ |
||
20 | private $builder; |
||
21 | |||
22 | /** @var Definitions */ |
||
23 | private $definitions; |
||
24 | |||
25 | /** @var RelationsResolver */ |
||
26 | private $resolver; |
||
27 | |||
28 | public function __construct( |
||
29 | Definitions $definitions = null, |
||
30 | RelationsResolver $resolver = null, |
||
31 | StructureBuilder $builder = null |
||
32 | ) { |
||
33 | $this->builder = $builder ?? new StructureBuilder(); |
||
34 | $this->definitions = $definitions ?? new Definitions(); |
||
35 | $this->resolver = $resolver ?? new RelationsResolver(); |
||
36 | } |
||
37 | |||
38 | public function parse(CodeFinder $finder): Structure |
||
39 | { |
||
40 | foreach ($finder->files() as $code) { |
||
41 | $this->initParserAttributes(); |
||
42 | $this->process(token_get_all($code)); |
||
43 | $this->storeClassOrInterface(); |
||
44 | } |
||
45 | $this->resolver->resolve($this->definitions); |
||
46 | return $this->builder->buildFromDefinitions($this->definitions); |
||
47 | } |
||
48 | |||
49 | private function process(array $tokens): void |
||
50 | { |
||
51 | foreach ($tokens as $token) { |
||
52 | if (is_array($token)) { |
||
53 | $this->processComplex(...$token); |
||
54 | } else { |
||
55 | $this->processSimple($token); |
||
56 | } |
||
57 | } |
||
58 | } |
||
59 | |||
60 | private function processSimple(string $token): void |
||
61 | { |
||
62 | switch ($token) { |
||
63 | case '(': |
||
64 | break; |
||
65 | case ',': |
||
66 | $this->resetTypeHint(); |
||
67 | break; |
||
68 | case '=': |
||
69 | $this->resetToken(); |
||
70 | break; |
||
71 | case ')': |
||
72 | $this->saveMethodDefinition(); |
||
73 | break; |
||
74 | default: |
||
75 | // Ignore everything else |
||
76 | $this->lastToken = null; |
||
77 | } |
||
78 | } |
||
79 | |||
80 | private function processComplex(int $type, string $value): void |
||
81 | { |
||
82 | switch ($type) { |
||
83 | case T_WHITESPACE: |
||
84 | break; |
||
85 | case T_VAR: |
||
86 | case T_ARRAY: |
||
87 | case T_CONSTANT_ENCAPSED_STRING: |
||
88 | case T_LNUMBER: |
||
89 | case T_DNUMBER: |
||
90 | case T_PAAMAYIM_NEKUDOTAYIM: |
||
91 | $this->resetToken(); |
||
92 | break; |
||
93 | case T_FUNCTION: |
||
94 | $this->startMethodDefinition($type); |
||
95 | break; |
||
96 | case T_INTERFACE: |
||
97 | case T_CLASS: |
||
98 | $this->startClassOrInterfaceDefinition($type); |
||
99 | break; |
||
100 | case T_IMPLEMENTS: |
||
101 | case T_EXTENDS: |
||
102 | $this->startExtendsOrImplementsDeclaration($type); |
||
103 | break; |
||
104 | case T_VARIABLE: |
||
105 | $this->saveAttributeOrParameter($value); |
||
106 | break; |
||
107 | case T_STRING: |
||
108 | $this->saveIdentifier($value); |
||
109 | break; |
||
110 | case T_PUBLIC: |
||
111 | case T_PROTECTED: |
||
112 | case T_PRIVATE: |
||
113 | $this->saveModifier($type, $value); |
||
114 | break; |
||
115 | case T_DOC_COMMENT: |
||
116 | $this->saveDocBlock($value); |
||
117 | break; |
||
118 | default: |
||
119 | // Ignore everything else |
||
120 | $this->lastToken = null; |
||
121 | // And reset the docblock |
||
122 | $this->structure['docblock'] = null; |
||
123 | } |
||
124 | } |
||
125 | |||
126 | private function initParserAttributes(): void |
||
127 | { |
||
128 | $this->structure = [ |
||
129 | 'class' => null, |
||
130 | 'interface' => null, |
||
131 | 'function' => null, |
||
132 | 'attributes' => [], |
||
133 | 'functions' => [], |
||
134 | 'typehint' => null, |
||
135 | 'params' => [], |
||
136 | 'implements' => [], |
||
137 | 'extends' => null, |
||
138 | 'modifier' => 'public', |
||
139 | 'docblock' => null, |
||
140 | ]; |
||
141 | |||
142 | $this->lastToken = []; |
||
143 | } |
||
144 | |||
145 | private function resetTypeHint(): void |
||
146 | { |
||
147 | $this->structure['typehint'] = null; |
||
148 | } |
||
149 | |||
150 | private function resetToken(): void |
||
151 | { |
||
152 | if ($this->lastToken !== T_FUNCTION) { |
||
153 | $this->lastToken = null; |
||
154 | } |
||
155 | } |
||
156 | |||
157 | private function startMethodDefinition(int $type): void |
||
158 | { |
||
159 | switch ($this->lastToken) { |
||
160 | case null: |
||
161 | case T_PUBLIC: |
||
162 | case T_PROTECTED: |
||
163 | case T_PRIVATE: |
||
164 | $this->lastToken = $type; |
||
165 | break; |
||
166 | default: |
||
167 | $this->lastToken = null; |
||
168 | } |
||
169 | } |
||
170 | |||
171 | private function startClassOrInterfaceDefinition(int $type): void |
||
172 | { |
||
173 | if ($this->lastToken === null) { |
||
174 | // New initial interface or class token |
||
175 | // Store the class or interface definition if there is any in the |
||
176 | // parser arrays ( There might be more than one class/interface per |
||
177 | // file ) |
||
178 | $this->storeClassOrInterface(); |
||
179 | |||
180 | // Remember the last token |
||
181 | $this->lastToken = $type; |
||
182 | } else { |
||
183 | $this->lastToken = null; |
||
184 | } |
||
185 | } |
||
186 | |||
187 | private function startExtendsOrImplementsDeclaration(int $type): void |
||
188 | { |
||
189 | if ($this->lastToken === null) { |
||
190 | $this->lastToken = $type; |
||
191 | } else { |
||
192 | $this->lastToken = null; |
||
193 | } |
||
194 | } |
||
195 | |||
196 | private function saveMethodDefinition(): void |
||
197 | { |
||
198 | if ($this->lastToken === T_FUNCTION) { |
||
199 | // The function declaration has been closed |
||
200 | |||
201 | // Add the current function |
||
202 | $this->structure['functions'][] = [ |
||
203 | $this->structure['function'], |
||
204 | $this->structure['modifier'], |
||
205 | $this->structure['params'], |
||
206 | $this->structure['docblock'] |
||
207 | ]; |
||
208 | // Reset the last token |
||
209 | $this->lastToken = null; |
||
210 | //Reset the modifier state |
||
211 | $this->structure['modifier'] = 'public'; |
||
212 | // Reset the params array |
||
213 | $this->structure['params'] = []; |
||
214 | $this->structure['typehint'] = null; |
||
215 | // Reset the function name |
||
216 | $this->structure['function'] = null; |
||
217 | // Reset the docblock |
||
218 | $this->structure['docblock'] = null; |
||
219 | } else { |
||
220 | $this->lastToken = null; |
||
221 | } |
||
222 | } |
||
223 | |||
224 | private function saveAttributeOrParameter(string $identifier): void |
||
247 | } |
||
248 | } |
||
249 | |||
250 | private function saveIdentifier(string $identifier): void |
||
251 | { |
||
252 | switch ($this->lastToken) { |
||
253 | case T_IMPLEMENTS: |
||
254 | // Add interface to implements array |
||
255 | $this->structure['implements'][] = $identifier; |
||
256 | // We do not reset the last token here, because |
||
257 | // there might be multiple interfaces |
||
258 | break; |
||
259 | case T_EXTENDS: |
||
260 | // Set the superclass |
||
261 | $this->structure['extends'] = $identifier; |
||
262 | // Reset the last token |
||
263 | $this->lastToken = null; |
||
264 | break; |
||
265 | case T_FUNCTION: |
||
266 | // Add the current function only if there is no function name already |
||
267 | // Because if we know the function name already this is a type hint |
||
268 | if ($this->structure['function'] === null) { |
||
269 | // Function name |
||
270 | $this->structure['function'] = $identifier; |
||
271 | } else { |
||
272 | // Type hint |
||
273 | $this->structure['typehint'] = $identifier; |
||
274 | } |
||
275 | break; |
||
276 | case T_CLASS: |
||
277 | // Set the class name |
||
278 | $this->structure['class'] = $identifier; |
||
279 | // Reset the last token |
||
280 | $this->lastToken = null; |
||
281 | break; |
||
282 | case T_INTERFACE: |
||
283 | // Set the interface name |
||
284 | $this->structure['interface'] = $identifier; |
||
285 | // Reset the last Token |
||
286 | $this->lastToken = null; |
||
287 | break; |
||
288 | default: |
||
289 | $this->lastToken = null; |
||
290 | } |
||
291 | } |
||
292 | |||
293 | private function saveModifier(int $type, string $modifier): void |
||
300 | } |
||
301 | } |
||
302 | |||
303 | private function saveDocBlock(string $comment): void |
||
304 | { |
||
305 | if ($this->lastToken === null) { |
||
306 | $this->structure['docblock'] = $comment; |
||
307 | } else { |
||
308 | $this->lastToken = null; |
||
309 | $this->structure['docblock'] = null; |
||
310 | } |
||
311 | } |
||
312 | |||
313 | private function storeClassOrInterface(): void |
||
317 | } |
||
318 | } |
||
319 |