Complex classes like PackagesManager 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 PackagesManager, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | 1 | final class PackagesManager implements IPackagesManager |
|
47 | { |
||
48 | /** |
||
49 | * Implement nette smart magic |
||
50 | */ |
||
51 | 1 | use Nette\SmartObject; |
|
52 | |||
53 | /** |
||
54 | * Define package metadata keys |
||
55 | */ |
||
56 | private const PACKAGE_STATUS = 'status'; |
||
57 | private const PACKAGE_METADATA = 'metadata'; |
||
58 | |||
59 | /** |
||
60 | * Define actions |
||
61 | */ |
||
62 | private const ACTION_ENABLE = 'enable'; |
||
63 | private const ACTION_DISABLE = 'disable'; |
||
64 | private const ACTION_REGISTER = 'register'; |
||
65 | private const ACTION_UNREGISTER = 'unregister'; |
||
66 | |||
67 | /** |
||
68 | * @var callable[] |
||
69 | */ |
||
70 | public $onEnable = []; |
||
71 | |||
72 | /** |
||
73 | * @var callable[] |
||
74 | */ |
||
75 | public $onDisable = []; |
||
76 | |||
77 | /** |
||
78 | * @var callable[] |
||
79 | */ |
||
80 | public $onUpgrade = []; |
||
81 | |||
82 | /** |
||
83 | * @var callable[] |
||
84 | */ |
||
85 | public $onInstall = []; |
||
86 | |||
87 | /** |
||
88 | * @var callable[] |
||
89 | */ |
||
90 | public $onUninstall = []; |
||
91 | |||
92 | /** |
||
93 | * @var callable[] |
||
94 | */ |
||
95 | public $onRegister = []; |
||
96 | |||
97 | /** |
||
98 | * @var callable[] |
||
99 | */ |
||
100 | public $onUnregister = []; |
||
101 | |||
102 | /** |
||
103 | * @var string |
||
104 | */ |
||
105 | private $vendorDir; |
||
106 | |||
107 | /** |
||
108 | * @var string |
||
109 | */ |
||
110 | private $configDir; |
||
111 | |||
112 | /** |
||
113 | * @var Repository\IRepository |
||
114 | */ |
||
115 | private $repository; |
||
116 | |||
117 | /** |
||
118 | * @var Installers\IInstaller|NULL |
||
119 | */ |
||
120 | private $installer; |
||
121 | |||
122 | /** |
||
123 | * @var Nette\DI\Container |
||
124 | */ |
||
125 | private $container; |
||
126 | |||
127 | /** |
||
128 | * @var DependencyResolver\Solver |
||
129 | */ |
||
130 | private $dependencySolver; |
||
131 | |||
132 | /** |
||
133 | * @var Utils\ArrayHash |
||
134 | */ |
||
135 | private $packagesConfig; |
||
136 | |||
137 | /** |
||
138 | * @var Scripts\IScript[] |
||
139 | */ |
||
140 | private $scripts = []; |
||
141 | |||
142 | /** |
||
143 | * @param string $vendorDir |
||
144 | * @param string $configDir |
||
145 | * @param Repository\IRepository $repository |
||
146 | * @param Installers\IInstaller|NULL $installer |
||
147 | */ |
||
148 | public function __construct( |
||
149 | string $vendorDir, |
||
150 | string $configDir, |
||
151 | Repository\IRepository $repository, |
||
152 | Installers\IInstaller $installer = NULL |
||
153 | ) { |
||
154 | 1 | $this->vendorDir = $vendorDir; |
|
155 | 1 | $this->configDir = $configDir; |
|
156 | |||
157 | 1 | $this->repository = $repository; |
|
158 | 1 | $this->installer = $installer; |
|
159 | 1 | } |
|
160 | |||
161 | /** |
||
162 | * {@inheritdoc} |
||
163 | */ |
||
164 | public function getStatus(Entities\IPackage $package) : string |
||
165 | { |
||
166 | $packageConfig = $this->getPackagesConfig(); |
||
167 | |||
168 | if (!$packageConfig->offsetExists($package->getName()) || !isset($packageConfig[$package->getName()][self::PACKAGE_STATUS])) { |
||
169 | return Entities\IPackage::STATE_UNREGISTERED; |
||
170 | } |
||
171 | |||
172 | return $packageConfig[$package->getName()][self::PACKAGE_STATUS]; |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * {@inheritdoc} |
||
177 | */ |
||
178 | public function getVersion(Entities\IPackage $package) : string |
||
179 | { |
||
180 | if (!$path = $package->getPath()) { |
||
181 | throw new \RuntimeException('Package path is missing.'); |
||
182 | } |
||
183 | |||
184 | if (!file_exists($file = $path . DIRECTORY_SEPARATOR . 'composer.json')) { |
||
185 | throw new \RuntimeException('\'composer.json\' is missing.'); |
||
186 | } |
||
187 | |||
188 | $packageData = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY); |
||
189 | |||
190 | if (isset($packageData['version'])) { |
||
191 | return $packageData['version']; |
||
192 | } |
||
193 | |||
194 | if (file_exists($file = $this->vendorDir . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'installed.json')) { |
||
195 | $installed = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY); |
||
196 | |||
197 | foreach ($installed as $packageData) { |
||
198 | if ($packageData['name'] === $package->getName()) { |
||
199 | return $packageData['version']; |
||
200 | } |
||
201 | } |
||
202 | } |
||
203 | |||
204 | return '0.0.0'; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * {@inheritdoc} |
||
209 | */ |
||
210 | public function comparePackages(Entities\IPackage $first, Entities\IPackage $second, string $operator = '==') : bool |
||
211 | { |
||
212 | return strtolower($first->getName()) === strtolower($second->getName()) && |
||
213 | version_compare(strtolower($this->getVersion($first)), strtolower($this->getVersion($second)), $operator); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * {@inheritdoc} |
||
218 | */ |
||
219 | public function addScript(string $name, Scripts\IScript $service) |
||
220 | { |
||
221 | 1 | $this->scripts[$name] = $service; |
|
222 | 1 | } |
|
223 | |||
224 | /** |
||
225 | * {@inheritdoc} |
||
226 | */ |
||
227 | public function registerAvailable() : array |
||
228 | { |
||
229 | $actions = []; |
||
230 | |||
231 | $installedPackages = array_keys($this->repository->getPackages()); |
||
232 | $registeredPackages = array_keys((array) $this->getPackagesConfig()); |
||
233 | |||
234 | foreach (array_diff($installedPackages, $registeredPackages) as $name) { |
||
235 | /** @var Entities\IPackage $package */ |
||
236 | if ($package = $this->repository->findPackage($name)) { |
||
237 | $this->register($package); |
||
238 | $actions[] = [$name => self::ACTION_REGISTER]; |
||
239 | } |
||
240 | } |
||
241 | |||
242 | return $actions; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * {@inheritdoc} |
||
247 | */ |
||
248 | public function enableAvailable() : array |
||
249 | { |
||
250 | $actions = []; |
||
251 | |||
252 | while (TRUE) { |
||
253 | /** @var Entities\IPackage[] $packages */ |
||
254 | $packages = $this->repository->filterPackages(function (Entities\IPackage $package) : bool { |
||
255 | return $this->getStatus($package) === Entities\IPackage::STATE_DISABLED; |
||
256 | }); |
||
257 | |||
258 | if (!count($packages)) { |
||
259 | break; |
||
260 | } |
||
261 | |||
262 | /** @var Entities\IPackage $package */ |
||
263 | $package = reset($packages); |
||
264 | |||
265 | foreach ($this->testEnable($package)->getSolutions() as $job) { |
||
266 | if ($job->getAction() === DependencyResolver\Job::ACTION_ENABLE) { |
||
267 | $this->enable($job->getPackage()); |
||
268 | $actions[] = [$job->getPackage()->getName() => self::ACTION_ENABLE]; |
||
269 | |||
270 | } elseif ($job->getAction() === DependencyResolver\Job::ACTION_DISABLE) { |
||
271 | $this->disable($job->getPackage()); |
||
272 | $actions[] = [$job->getPackage()->getName() => self::ACTION_DISABLE]; |
||
273 | } |
||
274 | } |
||
275 | |||
276 | $this->enable($package); |
||
277 | |||
278 | $this->dependencySolver = NULL; |
||
279 | |||
280 | $actions[] = [$package->getName() => self::ACTION_ENABLE]; |
||
281 | } |
||
282 | |||
283 | return $actions; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * {@inheritdoc} |
||
288 | */ |
||
289 | public function disableAbsent() : array |
||
290 | { |
||
291 | $actions = []; |
||
292 | |||
293 | $installedPackages = array_keys($this->repository->getPackages()); |
||
294 | $registeredPackages = array_keys((array) $this->getPackagesConfig()); |
||
295 | |||
296 | foreach (array_diff($registeredPackages, $installedPackages) as $name) { |
||
297 | /** @var Entities\IPackage $package */ |
||
298 | if ($package = $this->repository->findPackage($name)) { |
||
299 | if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) { |
||
300 | $this->disable($package); |
||
301 | $actions[] = [$name => self::ACTION_DISABLE]; |
||
302 | } |
||
303 | |||
304 | $this->disable($package); |
||
305 | |||
306 | $actions[] = [$package->getName() => self::ACTION_DISABLE]; |
||
|
|||
307 | } |
||
308 | } |
||
309 | |||
310 | return $actions; |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * {@inheritdoc} |
||
315 | */ |
||
316 | public function install(string $name) : void |
||
317 | { |
||
318 | // Check if installer service is created |
||
319 | if ($this->installer === NULL) { |
||
320 | new Exceptions\InvalidStateException('Packages installer service is not created.'); |
||
321 | } |
||
322 | |||
323 | // Check if package is not already installed |
||
324 | if ($this->repository->findPackage($name)) { |
||
325 | throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" is already installed', $name)); |
||
326 | } |
||
327 | |||
328 | $this->installer->install($name); |
||
329 | |||
330 | // Reload repository after installation |
||
331 | $this->repository->reload(); |
||
332 | |||
333 | // Get newly installed package |
||
334 | if (!$package = $this->repository->findPackage($name)) { |
||
335 | throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" could not be found.', $name)); |
||
336 | } |
||
337 | |||
338 | // Process all package scripts |
||
339 | foreach ($package->getScripts() as $class) { |
||
340 | try { |
||
341 | $script = $this->getScript($class); |
||
342 | $script->install($package); |
||
343 | |||
344 | } catch (\Exception $e) { |
||
345 | foreach ($package->getScripts() as $class2) { |
||
346 | if ($class === $class2) { |
||
347 | break; |
||
348 | } |
||
349 | |||
350 | $script = $this->getScript($class2); |
||
351 | $script->uninstall($package); |
||
352 | } |
||
353 | |||
354 | throw new Exceptions\InvalidStateException($e->getMessage()); |
||
355 | } |
||
356 | } |
||
357 | |||
358 | $this->register($package); |
||
359 | |||
360 | $this->onInstall($this, $package); |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * {@inheritdoc} |
||
365 | */ |
||
366 | public function uninstall(string $name) : void |
||
367 | { |
||
368 | // Check if installer service is created |
||
369 | if ($this->installer === NULL) { |
||
370 | new Exceptions\InvalidStateException('Packages installer service is not created.'); |
||
371 | } |
||
372 | |||
373 | // Check if package is installed |
||
374 | if (!$package = $this->repository->findPackage($name)) { |
||
375 | throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" is already uninstalled', $name)); |
||
376 | } |
||
377 | |||
378 | // If package is still enabled, disable it first |
||
379 | if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) { |
||
380 | $this->disable($package); |
||
381 | } |
||
382 | |||
383 | // Process all package scripts |
||
384 | foreach ($package->getScripts() as $class) { |
||
385 | try { |
||
386 | $script = $this->getScript($class); |
||
387 | $script->uninstall($package); |
||
388 | |||
389 | } catch (\Exception $e) { |
||
390 | foreach ($package->getScripts() as $class2) { |
||
391 | if ($class === $class2) { |
||
392 | break; |
||
393 | } |
||
394 | |||
395 | $script = $this->getScript($class2); |
||
396 | $script->install($package); |
||
397 | } |
||
398 | |||
399 | throw new Exceptions\InvalidStateException($e->getMessage()); |
||
400 | } |
||
401 | } |
||
402 | |||
403 | $this->unregister($package); |
||
404 | |||
405 | if ($this->installer->isInstalled($package->getName())) { |
||
406 | $this->installer->uninstall($package->getName()); |
||
407 | |||
408 | } else { |
||
409 | if (!$path = $package->getPath()) { |
||
410 | throw new Exceptions\InvalidStateException('Package path is missing.'); |
||
411 | } |
||
412 | |||
413 | $this->output->writeln("Removing package folder."); |
||
414 | |||
415 | Utils\FileSystem::delete($path); |
||
416 | } |
||
417 | |||
418 | // Reload repository after uninstallation |
||
419 | $this->repository->reload(); |
||
420 | |||
421 | $this->onUninstall($this, $package); |
||
422 | } |
||
423 | |||
424 | /** |
||
425 | * {@inheritdoc} |
||
426 | */ |
||
427 | public function enable(Entities\IPackage $package) : void |
||
428 | { |
||
429 | if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) { |
||
430 | throw new Exceptions\InvalidArgumentException(sprintf('Package \'%s\' is already enabled', $package->getName())); |
||
431 | } |
||
432 | |||
433 | $dependencyResolver = $this->getDependencySolver(); |
||
434 | $dependencyResolver->testEnable($package); |
||
435 | |||
436 | foreach ($package->getScripts() as $class) { |
||
437 | try { |
||
438 | $installer = $this->getScript($class); |
||
439 | $installer->enable($package); |
||
440 | |||
441 | } catch (\Exception $ex) { |
||
442 | foreach ($package->getScripts() as $class2) { |
||
443 | if ($class === $class2) { |
||
444 | break; |
||
445 | } |
||
446 | |||
447 | $installer = $this->getScript($class2); |
||
448 | $installer->disable($package); |
||
449 | } |
||
450 | |||
451 | throw new Exceptions\InvalidStateException($ex->getMessage()); |
||
452 | } |
||
453 | } |
||
454 | |||
455 | $this->setStatus($package, Entities\IPackage::STATE_ENABLED); |
||
456 | |||
457 | $this->onEnable($this, $package); |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * {@inheritdoc} |
||
462 | */ |
||
463 | public function disable(Entities\IPackage $package) : void |
||
464 | { |
||
465 | if ($this->getStatus($package) === Entities\IPackage::STATE_DISABLED) { |
||
466 | throw new Exceptions\InvalidArgumentException(sprintf('Package \'%s\' is already disabled', $package->getName())); |
||
467 | } |
||
468 | |||
469 | $dependencyResolver = $this->getDependencySolver(); |
||
470 | $dependencyResolver->testDisable($package); |
||
471 | |||
472 | foreach ($package->getScripts() as $class) { |
||
473 | try { |
||
474 | $installer = $this->getScript($class); |
||
475 | $installer->disable($package); |
||
476 | |||
477 | } catch (\Exception $e) { |
||
478 | foreach ($package->getScripts() as $class2) { |
||
479 | if ($class === $class2) { |
||
480 | break; |
||
481 | } |
||
482 | |||
483 | $installer = $this->getScript($class2); |
||
484 | $installer->enable($package); |
||
485 | } |
||
486 | |||
487 | throw new Exceptions\InvalidStateException($e->getMessage()); |
||
488 | } |
||
489 | } |
||
490 | |||
491 | $this->setStatus($package, Entities\IPackage::STATE_DISABLED); |
||
492 | |||
493 | $this->onDisable($this, $package); |
||
494 | } |
||
495 | |||
496 | /** |
||
497 | * {@inheritdoc} |
||
498 | */ |
||
499 | public function testEnable(Entities\IPackage $package) : DependencyResolver\Problem |
||
500 | { |
||
501 | $problem = new DependencyResolver\Problem; |
||
502 | |||
503 | $dependencyResolver = $this->getDependencySolver(); |
||
504 | $dependencyResolver->testEnable($package, $problem); |
||
505 | |||
506 | return $problem; |
||
507 | } |
||
508 | |||
509 | /** |
||
510 | * {@inheritdoc} |
||
511 | */ |
||
512 | public function testDisable(Entities\IPackage $package) : DependencyResolver\Problem |
||
513 | { |
||
514 | $problem = new DependencyResolver\Problem; |
||
515 | |||
516 | $dependencyResolver = $this->getDependencySolver(); |
||
517 | $dependencyResolver->testDisable($package, $problem); |
||
518 | |||
519 | return $problem; |
||
520 | } |
||
521 | |||
522 | /** |
||
523 | * @param Entities\IPackage $package |
||
524 | * @param string $state |
||
525 | * |
||
526 | * @return void |
||
527 | */ |
||
528 | private function register(Entities\IPackage $package, string $state = Entities\IPackage::STATE_DISABLED) : void |
||
556 | |||
557 | /** |
||
558 | * @param Entities\IPackage $package |
||
559 | * |
||
560 | * @return void |
||
561 | */ |
||
562 | private function unregister(Entities\IPackage $package) : void |
||
563 | { |
||
564 | $packagesConfig = $this->getPackagesConfig(); |
||
565 | |||
566 | // Remove package info from configuration file |
||
567 | unset($packagesConfig[$package->getName()]); |
||
568 | |||
569 | $this->savePackagesConfig($packagesConfig); |
||
570 | |||
573 | |||
574 | /** |
||
575 | * @param Entities\IPackage $package |
||
576 | * @param string $status |
||
577 | * |
||
578 | * @return void |
||
579 | */ |
||
580 | private function setStatus(Entities\IPackage $package, string $status) : void |
||
597 | |||
598 | /** |
||
599 | * @param string $class |
||
600 | * |
||
601 | * @return Scripts\IScript |
||
602 | * |
||
603 | * @throws Exceptions\InvalidStateException |
||
604 | */ |
||
605 | private function getScript(string $class) : Scripts\IScript |
||
613 | |||
614 | /** |
||
615 | * @return DependencyResolver\Solver |
||
616 | */ |
||
617 | private function getDependencySolver() : DependencyResolver\Solver |
||
625 | |||
626 | /** |
||
627 | * @return void |
||
628 | */ |
||
629 | private function createSolver() : void |
||
633 | |||
634 | /** |
||
635 | * @return Utils\ArrayHash |
||
636 | */ |
||
637 | private function getPackagesConfig() : Utils\ArrayHash |
||
651 | |||
652 | /** |
||
653 | * @param Utils\ArrayHash $packagesConfig |
||
654 | * |
||
655 | * @return void |
||
656 | * |
||
657 | * @throws Exceptions\NotWritableException |
||
658 | */ |
||
659 | private function savePackagesConfig(Utils\ArrayHash $packagesConfig) : void |
||
670 | |||
671 | /** |
||
672 | * @return string |
||
673 | */ |
||
674 | private function getPackageConfigPath() : string |
||
678 | } |
||
679 |
Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.