Total Complexity | 58 |
Total Lines | 439 |
Duplicated Lines | 0 % |
Changes | 4 | ||
Bugs | 0 | Features | 0 |
Complex classes like CallableRegistry 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 CallableRegistry, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
36 | class CallableRegistry |
||
37 | { |
||
38 | /** |
||
39 | * The namespace options |
||
40 | * |
||
41 | * These are the options of the registered namespaces. |
||
42 | * |
||
43 | * @var array |
||
44 | */ |
||
45 | protected $aNamespaceOptions = []; |
||
46 | |||
47 | /** |
||
48 | * The directory options |
||
49 | * |
||
50 | * These are the options of the registered directories. |
||
51 | * |
||
52 | * @var array |
||
53 | */ |
||
54 | protected $aDirectoryOptions = []; |
||
55 | |||
56 | /** |
||
57 | * The namespaces |
||
58 | * |
||
59 | * These are all the namespaces found in registered directories. |
||
60 | * |
||
61 | * @var array |
||
62 | */ |
||
63 | protected $aNamespaces = []; |
||
64 | |||
65 | /** |
||
66 | * The package providing the class or directory being registered. |
||
67 | * |
||
68 | * @var Config|null |
||
69 | */ |
||
70 | protected $xCurrentConfig = null; |
||
71 | |||
72 | /** |
||
73 | * The string that will be used to compute the js file hash |
||
74 | * |
||
75 | * @var string |
||
76 | */ |
||
77 | protected $sHash = ''; |
||
78 | |||
79 | /** |
||
80 | * @var array |
||
81 | */ |
||
82 | private $aDefaultClassOptions = [ |
||
83 | 'separator' => '.', |
||
84 | 'protected' => [], |
||
85 | 'functions' => [], |
||
86 | 'timestamp' => 0, |
||
87 | ]; |
||
88 | |||
89 | /** |
||
90 | * The methods that must not be exported to js |
||
91 | * |
||
92 | * @var array |
||
93 | */ |
||
94 | private $aProtectedMethods = []; |
||
95 | |||
96 | /** |
||
97 | * @var bool |
||
98 | */ |
||
99 | protected $bDirectoriesParsed = false; |
||
100 | |||
101 | /** |
||
102 | * @var bool |
||
103 | */ |
||
104 | protected $bNamespacesParsed = false; |
||
105 | |||
106 | /** |
||
107 | * The Composer autoloader |
||
108 | * |
||
109 | * @var ClassLoader |
||
110 | */ |
||
111 | private $xAutoloader = null; |
||
112 | |||
113 | /** |
||
114 | * The class constructor |
||
115 | * |
||
116 | * @param ClassContainer $cls |
||
117 | */ |
||
118 | public function __construct(protected ClassContainer $cls) |
||
133 | } |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * @param Config|null $xConfig |
||
138 | * |
||
139 | * @return void |
||
140 | */ |
||
141 | public function setCurrentConfig(Config $xConfig = null) |
||
142 | { |
||
143 | $this->xCurrentConfig = $xConfig; |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * Get all registered namespaces |
||
148 | * |
||
149 | * @return array |
||
150 | */ |
||
151 | public function getNamespaces(): array |
||
152 | { |
||
153 | return $this->aNamespaces; |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * Get the hash |
||
158 | * |
||
159 | * @return string |
||
160 | */ |
||
161 | public function getHash(): string |
||
162 | { |
||
163 | return $this->sHash; |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Get a given class options from specified directory options |
||
168 | * |
||
169 | * @param string $sClassName The class name |
||
170 | * @param array $aClassOptions The default class options |
||
171 | * @param array $aDirectoryOptions The directory options |
||
172 | * |
||
173 | * @return array |
||
174 | */ |
||
175 | private function makeClassOptions(string $sClassName, array $aClassOptions, array $aDirectoryOptions): array |
||
176 | { |
||
177 | foreach($this->aDefaultClassOptions as $sOption => $xValue) |
||
178 | { |
||
179 | if(!isset($aClassOptions[$sOption])) |
||
180 | { |
||
181 | $aClassOptions[$sOption] = $xValue; |
||
182 | } |
||
183 | } |
||
184 | $aClassOptions['excluded'] = (bool)($aClassOptions['excluded'] ?? false); // Convert to bool. |
||
185 | if(is_string($aClassOptions['protected'])) |
||
186 | { |
||
187 | $aClassOptions['protected'] = [$aClassOptions['protected']]; // Convert to array. |
||
188 | } |
||
189 | |||
190 | $aDirectoryOptions['functions'] = []; // The 'functions' section is not allowed here. |
||
191 | $aOptionGroups = [ |
||
192 | $aDirectoryOptions, // Options at directory level |
||
193 | $aDirectoryOptions['classes']['*'] ?? [], // Options for all classes |
||
194 | $aDirectoryOptions['classes'][$sClassName] ?? [], // Options for this specific class |
||
195 | ]; |
||
196 | foreach($aOptionGroups as $aOptionGroup) |
||
197 | { |
||
198 | if(isset($aOptionGroup['separator'])) |
||
199 | { |
||
200 | $aClassOptions['separator'] = (string)$aOptionGroup['separator']; |
||
201 | } |
||
202 | if(isset($aOptionGroup['excluded'])) |
||
203 | { |
||
204 | $aClassOptions['excluded'] = (bool)$aOptionGroup['excluded']; |
||
205 | } |
||
206 | if(isset($aOptionGroup['protected'])) |
||
207 | { |
||
208 | if(is_string($aOptionGroup['protected'])) |
||
209 | { |
||
210 | $aOptionGroup['protected'] = [$aOptionGroup['protected']]; // Convert to array. |
||
211 | } |
||
212 | $aClassOptions['protected'] = array_merge($aClassOptions['protected'], $aOptionGroup['protected']); |
||
213 | } |
||
214 | if(isset($aOptionGroup['functions'])) |
||
215 | { |
||
216 | $aClassOptions['functions'] = array_merge($aClassOptions['functions'], $aOptionGroup['functions']); |
||
217 | } |
||
218 | } |
||
219 | if(isset($aDirectoryOptions['config']) && !isset($aClassOptions['config'])) |
||
220 | { |
||
221 | $aClassOptions['config'] = $aDirectoryOptions['config']; |
||
222 | } |
||
223 | |||
224 | return $aClassOptions; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * |
||
229 | * @param string $sClassName The class name |
||
230 | * @param array $aClassOptions The default class options |
||
231 | * @param array $aDirectoryOptions The directory options |
||
232 | * @param bool $bAddToHash Add the class name to the hash value |
||
233 | * |
||
234 | * @return void |
||
235 | */ |
||
236 | private function _registerClass(string $sClassName, array $aClassOptions, |
||
237 | array $aDirectoryOptions = [], bool $bAddToHash = true) |
||
238 | { |
||
239 | $aOptions = $this->makeClassOptions($sClassName, $aClassOptions, $aDirectoryOptions); |
||
240 | $this->cls->registerClass($sClassName, $aOptions); |
||
241 | if($bAddToHash) |
||
242 | { |
||
243 | $this->sHash .= $sClassName . $aOptions['timestamp']; |
||
244 | } |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * |
||
249 | * @param string $sClassName The class name |
||
250 | * @param array $aClassOptions The default class options |
||
251 | * |
||
252 | * @return void |
||
253 | */ |
||
254 | public function registerClass(string $sClassName, array $aClassOptions) |
||
255 | { |
||
256 | if($this->xCurrentConfig !== null) |
||
257 | { |
||
258 | $aClassOptions['config'] = $this->xCurrentConfig; |
||
259 | } |
||
260 | $this->_registerClass($sClassName, $aClassOptions); |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Find options for a class which is registered with namespace |
||
265 | * |
||
266 | * @param string $sClassName The class name |
||
267 | * |
||
268 | * @return void |
||
269 | */ |
||
270 | public function registerClassFromNamespace(string $sClassName) |
||
271 | { |
||
272 | // Find the corresponding namespace |
||
273 | foreach($this->aNamespaceOptions as $sNamespace => $aDirectoryOptions) |
||
274 | { |
||
275 | // Check if the namespace matches the class. |
||
276 | if(strncmp($sClassName, $sNamespace . '\\', strlen($sNamespace) + 1) === 0) |
||
277 | { |
||
278 | // Save the class options |
||
279 | $aClassOptions = ['namespace' => $sNamespace]; |
||
280 | $this->_registerClass($sClassName, $aClassOptions, $aDirectoryOptions, false); |
||
281 | return; |
||
282 | } |
||
283 | } |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Find the options associated with a registered class name |
||
288 | * |
||
289 | * @param string $sClassName The class name |
||
290 | * |
||
291 | * @return array |
||
292 | */ |
||
293 | public function getProtectedMethods(string $sClassName): array |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * |
||
304 | * @param string $sDirectory The directory being registered |
||
305 | * @param array $aOptions The associated options |
||
306 | * |
||
307 | * @return void |
||
308 | */ |
||
309 | public function registerDirectory(string $sDirectory, array $aOptions) |
||
310 | { |
||
311 | // Set the autoload option default value |
||
312 | if(!isset($aOptions['autoload'])) |
||
313 | { |
||
314 | $aOptions['autoload'] = true; |
||
315 | } |
||
316 | if($this->xCurrentConfig !== null) |
||
317 | { |
||
318 | $aOptions['config'] = $this->xCurrentConfig; |
||
319 | } |
||
320 | $this->aDirectoryOptions[$sDirectory] = $aOptions; |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * |
||
325 | * @param string $sNamespace The namespace |
||
326 | * @param array $aOptions The associated options |
||
327 | * |
||
328 | * @return void |
||
329 | */ |
||
330 | private function addNamespace(string $sNamespace, array $aOptions) |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * |
||
338 | * @param string $sNamespace The namespace of the directory being registered |
||
339 | * @param array $aOptions The associated options |
||
340 | * |
||
341 | * @return void |
||
342 | */ |
||
343 | public function registerNamespace(string $sNamespace, array $aOptions) |
||
344 | { |
||
345 | // Separator default value |
||
346 | if(!isset($aOptions['separator'])) |
||
347 | { |
||
348 | $aOptions['separator'] = '.'; |
||
349 | } |
||
350 | $aOptions['separator'] = trim($aOptions['separator']); |
||
351 | if(!in_array($aOptions['separator'], ['.', '_'])) |
||
352 | { |
||
353 | $aOptions['separator'] = '.'; |
||
354 | } |
||
355 | if($aOptions['separator'] === '_') |
||
356 | { |
||
357 | $this->cls->useUnderscore(); |
||
358 | } |
||
359 | // Set the autoload option default value |
||
360 | if(!isset($aOptions['autoload'])) |
||
361 | { |
||
362 | $aOptions['autoload'] = true; |
||
363 | } |
||
364 | if($this->xCurrentConfig !== null) |
||
365 | { |
||
366 | $aOptions['config'] = $this->xCurrentConfig; |
||
367 | } |
||
368 | // Register the dir with PSR4 autoloading |
||
369 | if(($aOptions['autoload']) && $this->xAutoloader != null) |
||
370 | { |
||
371 | $this->xAutoloader->setPsr4($sNamespace . '\\', $aOptions['directory']); |
||
372 | } |
||
373 | |||
374 | $this->aNamespaceOptions[$sNamespace] = $aOptions; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * Read classes from directories registered without namespaces |
||
379 | * |
||
380 | * @return void |
||
381 | */ |
||
382 | public function parseDirectories() |
||
383 | { |
||
384 | // This is to be done only once. |
||
385 | if($this->bDirectoriesParsed) |
||
386 | { |
||
387 | return; |
||
388 | } |
||
389 | $this->bDirectoriesParsed = true; |
||
390 | |||
391 | // Browse directories without namespaces and read all the files. |
||
392 | $aClassMap = []; |
||
|
|||
393 | foreach($this->aDirectoryOptions as $sDirectory => $aDirectoryOptions) |
||
394 | { |
||
395 | $itFile = new SortedFileIterator($sDirectory); |
||
396 | // Iterate on dir content |
||
397 | foreach($itFile as $xFile) |
||
398 | { |
||
399 | // Skip everything except PHP files |
||
400 | if(!$xFile->isFile() || $xFile->getExtension() !== 'php') |
||
401 | { |
||
402 | continue; |
||
403 | } |
||
404 | |||
405 | $sClassName = $xFile->getBasename('.php'); |
||
406 | $aClassOptions = ['timestamp' => $xFile->getMTime()]; |
||
407 | if(($aDirectoryOptions['autoload']) && $this->xAutoloader !== null) |
||
408 | { |
||
409 | // Set classmap autoloading. Must be done before registering the class. |
||
410 | $this->xAutoloader->addClassMap([$sClassName => $xFile->getPathname()]); |
||
411 | } |
||
412 | $this->_registerClass($sClassName, $aClassOptions, $aDirectoryOptions); |
||
413 | } |
||
414 | } |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * Read classes from directories registered with namespaces |
||
419 | * |
||
420 | * @return void |
||
421 | */ |
||
422 | public function parseNamespaces() |
||
462 | } |
||
463 | } |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Register all the callable classes |
||
468 | * |
||
469 | * @return void |
||
470 | */ |
||
471 | public function parseCallableClasses() |
||
475 | } |
||
476 | } |
||
477 |