| Total Complexity | 54 | 
| Total Lines | 459 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like ExtensionManagementService 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 ExtensionManagementService, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 36 | class ExtensionManagementService implements SingletonInterface  | 
            ||
| 37 | { | 
            ||
| 38 | /**  | 
            ||
| 39 | * @var DownloadQueue  | 
            ||
| 40 | */  | 
            ||
| 41 | protected $downloadQueue;  | 
            ||
| 42 | |||
| 43 | /**  | 
            ||
| 44 | * @var DependencyUtility  | 
            ||
| 45 | */  | 
            ||
| 46 | protected $dependencyUtility;  | 
            ||
| 47 | |||
| 48 | /**  | 
            ||
| 49 | * @var InstallUtility  | 
            ||
| 50 | */  | 
            ||
| 51 | protected $installUtility;  | 
            ||
| 52 | |||
| 53 | /**  | 
            ||
| 54 | * @var ExtensionModelUtility  | 
            ||
| 55 | */  | 
            ||
| 56 | protected $extensionModelUtility;  | 
            ||
| 57 | |||
| 58 | /**  | 
            ||
| 59 | * @var bool  | 
            ||
| 60 | */  | 
            ||
| 61 | protected $automaticInstallationEnabled = true;  | 
            ||
| 62 | |||
| 63 | /**  | 
            ||
| 64 | * @var bool  | 
            ||
| 65 | */  | 
            ||
| 66 | protected $skipDependencyCheck = false;  | 
            ||
| 67 | |||
| 68 | /**  | 
            ||
| 69 | * @var EventDispatcherInterface  | 
            ||
| 70 | */  | 
            ||
| 71 | protected $eventDispatcher;  | 
            ||
| 72 | |||
| 73 | /**  | 
            ||
| 74 | * @var FileHandlingUtility  | 
            ||
| 75 | */  | 
            ||
| 76 | protected $fileHandlingUtility;  | 
            ||
| 77 | |||
| 78 | /**  | 
            ||
| 79 | * @var RemoteRegistry  | 
            ||
| 80 | */  | 
            ||
| 81 | protected $remoteRegistry;  | 
            ||
| 82 | |||
| 83 | /**  | 
            ||
| 84 | * @var string  | 
            ||
| 85 | */  | 
            ||
| 86 | protected $downloadPath = 'Local';  | 
            ||
| 87 | |||
| 88 | public function __construct(RemoteRegistry $remoteRegistry, FileHandlingUtility $fileHandlingUtility)  | 
            ||
| 89 |     { | 
            ||
| 90 | $this->remoteRegistry = $remoteRegistry;  | 
            ||
| 91 | $this->fileHandlingUtility = $fileHandlingUtility;  | 
            ||
| 92 | }  | 
            ||
| 93 | |||
| 94 | public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)  | 
            ||
| 95 |     { | 
            ||
| 96 | $this->eventDispatcher = $eventDispatcher;  | 
            ||
| 97 | }  | 
            ||
| 98 | |||
| 99 | /**  | 
            ||
| 100 | * @param DownloadQueue $downloadQueue  | 
            ||
| 101 | */  | 
            ||
| 102 | public function injectDownloadQueue(DownloadQueue $downloadQueue)  | 
            ||
| 103 |     { | 
            ||
| 104 | $this->downloadQueue = $downloadQueue;  | 
            ||
| 105 | }  | 
            ||
| 106 | |||
| 107 | /**  | 
            ||
| 108 | * @param DependencyUtility $dependencyUtility  | 
            ||
| 109 | */  | 
            ||
| 110 | public function injectDependencyUtility(DependencyUtility $dependencyUtility)  | 
            ||
| 111 |     { | 
            ||
| 112 | $this->dependencyUtility = $dependencyUtility;  | 
            ||
| 113 | }  | 
            ||
| 114 | |||
| 115 | /**  | 
            ||
| 116 | * @param InstallUtility $installUtility  | 
            ||
| 117 | */  | 
            ||
| 118 | public function injectInstallUtility(InstallUtility $installUtility)  | 
            ||
| 119 |     { | 
            ||
| 120 | $this->installUtility = $installUtility;  | 
            ||
| 121 | }  | 
            ||
| 122 | |||
| 123 | /**  | 
            ||
| 124 | * @param ExtensionModelUtility $extensionModelUtility  | 
            ||
| 125 | */  | 
            ||
| 126 | public function injectExtensionModelUtility(ExtensionModelUtility $extensionModelUtility)  | 
            ||
| 127 |     { | 
            ||
| 128 | $this->extensionModelUtility = $extensionModelUtility;  | 
            ||
| 129 | }  | 
            ||
| 130 | |||
| 131 | /**  | 
            ||
| 132 | * @param string $extensionKey  | 
            ||
| 133 | */  | 
            ||
| 134 | public function markExtensionForInstallation($extensionKey)  | 
            ||
| 135 |     { | 
            ||
| 136 | // We have to check for dependencies of the extension first, before marking it for installation  | 
            ||
| 137 | // because this extension might have dependencies, which need to be installed first  | 
            ||
| 138 | $this->installUtility->reloadAvailableExtensions();  | 
            ||
| 139 | $extension = $this->getExtension($extensionKey);  | 
            ||
| 140 | $this->dependencyUtility->checkDependencies($extension);  | 
            ||
| 141 | $this->downloadQueue->addExtensionToInstallQueue($extension);  | 
            ||
| 142 | }  | 
            ||
| 143 | |||
| 144 | /**  | 
            ||
| 145 | * Mark an extension for copy  | 
            ||
| 146 | *  | 
            ||
| 147 | * @param string $extensionKey  | 
            ||
| 148 | * @param string $sourceFolder  | 
            ||
| 149 | */  | 
            ||
| 150 | public function markExtensionForCopy($extensionKey, $sourceFolder)  | 
            ||
| 151 |     { | 
            ||
| 152 | $this->downloadQueue->addExtensionToCopyQueue($extensionKey, $sourceFolder);  | 
            ||
| 153 | }  | 
            ||
| 154 | |||
| 155 | /**  | 
            ||
| 156 | * Mark an extension for download  | 
            ||
| 157 | *  | 
            ||
| 158 | * @param Extension $extension  | 
            ||
| 159 | */  | 
            ||
| 160 | public function markExtensionForDownload(Extension $extension)  | 
            ||
| 161 |     { | 
            ||
| 162 | // We have to check for dependencies of the extension first, before marking it for download  | 
            ||
| 163 | // because this extension might have dependencies, which need to be downloaded and installed first  | 
            ||
| 164 | $this->dependencyUtility->checkDependencies($extension);  | 
            ||
| 165 |         if (!$this->dependencyUtility->hasDependencyErrors()) { | 
            ||
| 166 | $this->downloadQueue->addExtensionToQueue($extension);  | 
            ||
| 167 | }  | 
            ||
| 168 | }  | 
            ||
| 169 | |||
| 170 | /**  | 
            ||
| 171 | * @param Extension $extension  | 
            ||
| 172 | */  | 
            ||
| 173 | public function markExtensionForUpdate(Extension $extension)  | 
            ||
| 174 |     { | 
            ||
| 175 | // We have to check for dependencies of the extension first, before marking it for download  | 
            ||
| 176 | // because this extension might have dependencies, which need to be downloaded and installed first  | 
            ||
| 177 | $this->dependencyUtility->checkDependencies($extension);  | 
            ||
| 178 | $this->downloadQueue->addExtensionToQueue($extension, 'update');  | 
            ||
| 179 | }  | 
            ||
| 180 | |||
| 181 | /**  | 
            ||
| 182 | * Enables or disables the dependency check for system environment (PHP, TYPO3) before extension installation  | 
            ||
| 183 | *  | 
            ||
| 184 | * @param bool $skipDependencyCheck  | 
            ||
| 185 | */  | 
            ||
| 186 | public function setSkipDependencyCheck($skipDependencyCheck)  | 
            ||
| 187 |     { | 
            ||
| 188 | $this->skipDependencyCheck = $skipDependencyCheck;  | 
            ||
| 189 | }  | 
            ||
| 190 | |||
| 191 | /**  | 
            ||
| 192 | * @param bool $automaticInstallationEnabled  | 
            ||
| 193 | */  | 
            ||
| 194 | public function setAutomaticInstallationEnabled($automaticInstallationEnabled)  | 
            ||
| 195 |     { | 
            ||
| 196 | $this->automaticInstallationEnabled = (bool)$automaticInstallationEnabled;  | 
            ||
| 197 | }  | 
            ||
| 198 | |||
| 199 | /**  | 
            ||
| 200 | * Install the extension  | 
            ||
| 201 | *  | 
            ||
| 202 | * @param Extension $extension  | 
            ||
| 203 | * @return bool|array Returns FALSE if dependencies cannot be resolved, otherwise array with installation information  | 
            ||
| 204 | */  | 
            ||
| 205 | public function installExtension(Extension $extension)  | 
            ||
| 206 |     { | 
            ||
| 207 | $this->downloadExtension($extension);  | 
            ||
| 208 |         if (!$this->checkDependencies($extension)) { | 
            ||
| 209 | return false;  | 
            ||
| 210 | }  | 
            ||
| 211 | |||
| 212 | $downloadedDependencies = [];  | 
            ||
| 213 | $updatedDependencies = [];  | 
            ||
| 214 | $installQueue = [];  | 
            ||
| 215 | |||
| 216 | // First resolve all dependencies and the sub-dependencies until all queues are empty as new extensions might be  | 
            ||
| 217 | // added each time  | 
            ||
| 218 | // Extensions have to be installed in reverse order. Extensions which were added at last are dependencies of  | 
            ||
| 219 | // earlier ones and need to be available before  | 
            ||
| 220 | while (!$this->downloadQueue->isCopyQueueEmpty()  | 
            ||
| 221 |             || !$this->downloadQueue->isQueueEmpty('download') | 
            ||
| 222 |             || !$this->downloadQueue->isQueueEmpty('update') | 
            ||
| 223 |         ) { | 
            ||
| 224 | // First copy all available extension  | 
            ||
| 225 | // This might change other queues again  | 
            ||
| 226 | $copyQueue = $this->downloadQueue->resetExtensionCopyStorage();  | 
            ||
| 227 |             if (!empty($copyQueue)) { | 
            ||
| 228 | $this->copyDependencies($copyQueue);  | 
            ||
| 229 | }  | 
            ||
| 230 | $installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);  | 
            ||
| 231 | // Get download and update information  | 
            ||
| 232 | $queue = $this->downloadQueue->resetExtensionQueue();  | 
            ||
| 233 |             if (!empty($queue['download'])) { | 
            ||
| 234 | $downloadedDependencies = array_merge($downloadedDependencies, $this->downloadDependencies($queue['download']));  | 
            ||
| 235 | }  | 
            ||
| 236 | $installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);  | 
            ||
| 237 |             if ($this->automaticInstallationEnabled) { | 
            ||
| 238 |                 if (!empty($queue['update'])) { | 
            ||
| 239 | $this->downloadDependencies($queue['update']);  | 
            ||
| 240 | $updatedDependencies = array_merge($updatedDependencies, $this->uninstallDependenciesToBeUpdated($queue['update']));  | 
            ||
| 241 | }  | 
            ||
| 242 | $installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);  | 
            ||
| 243 | }  | 
            ||
| 244 | }  | 
            ||
| 245 | |||
| 246 | // If there were any dependency errors we have to abort here  | 
            ||
| 247 |         if ($this->dependencyUtility->hasDependencyErrors()) { | 
            ||
| 248 | return false;  | 
            ||
| 249 | }  | 
            ||
| 250 | |||
| 251 | // Attach extension to install queue  | 
            ||
| 252 | $this->downloadQueue->addExtensionToInstallQueue($extension);  | 
            ||
| 253 | $installQueue += $this->downloadQueue->resetExtensionInstallStorage();  | 
            ||
| 254 | $installedDependencies = [];  | 
            ||
| 255 |         if ($this->automaticInstallationEnabled) { | 
            ||
| 256 | $installedDependencies = $this->installDependencies($installQueue);  | 
            ||
| 257 | }  | 
            ||
| 258 | |||
| 259 | return array_merge($downloadedDependencies, $updatedDependencies, $installedDependencies);  | 
            ||
| 260 | }  | 
            ||
| 261 | |||
| 262 | /**  | 
            ||
| 263 | * Returns the unresolved dependency errors  | 
            ||
| 264 | *  | 
            ||
| 265 | * @return array  | 
            ||
| 266 | */  | 
            ||
| 267 | public function getDependencyErrors()  | 
            ||
| 268 |     { | 
            ||
| 269 | return $this->dependencyUtility->getDependencyErrors();  | 
            ||
| 270 | }  | 
            ||
| 271 | |||
| 272 | /**  | 
            ||
| 273 | * @param string $extensionKey  | 
            ||
| 274 | * @return Extension  | 
            ||
| 275 | * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException  | 
            ||
| 276 | */  | 
            ||
| 277 | public function getExtension($extensionKey)  | 
            ||
| 278 |     { | 
            ||
| 279 | return $this->extensionModelUtility->mapExtensionArrayToModel(  | 
            ||
| 280 | $this->installUtility->enrichExtensionWithDetails($extensionKey)  | 
            ||
| 281 | );  | 
            ||
| 282 | }  | 
            ||
| 283 | |||
| 284 | /**  | 
            ||
| 285 | * Checks if an extension is available in the system  | 
            ||
| 286 | *  | 
            ||
| 287 | * @param string $extensionKey  | 
            ||
| 288 | * @return bool  | 
            ||
| 289 | */  | 
            ||
| 290 | public function isAvailable($extensionKey)  | 
            ||
| 291 |     { | 
            ||
| 292 | return $this->installUtility->isAvailable($extensionKey);  | 
            ||
| 293 | }  | 
            ||
| 294 | |||
| 295 | /**  | 
            ||
| 296 | * @param string $extensionKey  | 
            ||
| 297 | * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available  | 
            ||
| 298 | * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed  | 
            ||
| 299 | * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed  | 
            ||
| 300 | * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found  | 
            ||
| 301 | */  | 
            ||
| 302 | public function reloadPackageInformation($extensionKey)  | 
            ||
| 303 |     { | 
            ||
| 304 | $this->installUtility->reloadPackageInformation($extensionKey);  | 
            ||
| 305 | }  | 
            ||
| 306 | |||
| 307 | /**  | 
            ||
| 308 | * Download an extension  | 
            ||
| 309 | *  | 
            ||
| 310 | * @param Extension $extension  | 
            ||
| 311 | */  | 
            ||
| 312 | protected function downloadExtension(Extension $extension)  | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | /**  | 
            ||
| 319 | * Check dependencies for an extension and its required extensions  | 
            ||
| 320 | *  | 
            ||
| 321 | * @param Extension $extension  | 
            ||
| 322 | * @return bool Returns TRUE if all dependencies can be resolved, otherwise FALSE  | 
            ||
| 323 | */  | 
            ||
| 324 | protected function checkDependencies(Extension $extension)  | 
            ||
| 325 |     { | 
            ||
| 326 | $this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);  | 
            ||
| 327 | $this->dependencyUtility->checkDependencies($extension);  | 
            ||
| 328 | |||
| 329 | return !$this->dependencyUtility->hasDependencyErrors();  | 
            ||
| 330 | }  | 
            ||
| 331 | |||
| 332 | /**  | 
            ||
| 333 | * Sets the path to the repository in an extension  | 
            ||
| 334 | * (Initialisation/Extensions) depending on the extension  | 
            ||
| 335 | * that is currently installed  | 
            ||
| 336 | *  | 
            ||
| 337 | * @param string $extensionKey  | 
            ||
| 338 | */  | 
            ||
| 339 | protected function setInExtensionRepository($extensionKey)  | 
            ||
| 348 | }  | 
            ||
| 349 | |||
| 350 | /**  | 
            ||
| 351 | * Copies locally provided extensions to typo3conf/ext  | 
            ||
| 352 | *  | 
            ||
| 353 | * @param array $copyQueue  | 
            ||
| 354 | */  | 
            ||
| 355 | protected function copyDependencies(array $copyQueue)  | 
            ||
| 356 |     { | 
            ||
| 357 | $installPaths = Extension::returnAllowedInstallPaths();  | 
            ||
| 358 |         foreach ($copyQueue as $extensionKey => $sourceFolder) { | 
            ||
| 359 | $destination = $installPaths['Local'] . $extensionKey;  | 
            ||
| 360 | GeneralUtility::mkdir($destination);  | 
            ||
| 361 | GeneralUtility::copyDirectory($sourceFolder . $extensionKey, $destination);  | 
            ||
| 362 | $this->markExtensionForInstallation($extensionKey);  | 
            ||
| 363 | $this->downloadQueue->removeExtensionFromCopyQueue($extensionKey);  | 
            ||
| 364 | }  | 
            ||
| 365 | }  | 
            ||
| 366 | |||
| 367 | /**  | 
            ||
| 368 | * Uninstall extensions that will be updated  | 
            ||
| 369 | * This is not strictly necessary but cleaner all in all  | 
            ||
| 370 | *  | 
            ||
| 371 | * @param Extension[] $updateQueue  | 
            ||
| 372 | * @return array  | 
            ||
| 373 | */  | 
            ||
| 374 | protected function uninstallDependenciesToBeUpdated(array $updateQueue)  | 
            ||
| 375 |     { | 
            ||
| 376 | $resolvedDependencies = [];  | 
            ||
| 377 |         foreach ($updateQueue as $extensionToUpdate) { | 
            ||
| 378 | $this->installUtility->uninstall($extensionToUpdate->getExtensionKey());  | 
            ||
| 379 | $resolvedDependencies['updated'][$extensionToUpdate->getExtensionKey()] = $extensionToUpdate;  | 
            ||
| 380 | }  | 
            ||
| 381 | return $resolvedDependencies;  | 
            ||
| 382 | }  | 
            ||
| 383 | |||
| 384 | /**  | 
            ||
| 385 | * Install dependent extensions  | 
            ||
| 386 | *  | 
            ||
| 387 | * @param array $installQueue  | 
            ||
| 388 | * @return array  | 
            ||
| 389 | */  | 
            ||
| 390 | protected function installDependencies(array $installQueue)  | 
            ||
| 405 | }  | 
            ||
| 406 | |||
| 407 | /**  | 
            ||
| 408 | * Download dependencies  | 
            ||
| 409 | * expects an array of extension objects to download  | 
            ||
| 410 | *  | 
            ||
| 411 | * @param Extension[] $downloadQueue  | 
            ||
| 412 | * @return array  | 
            ||
| 413 | */  | 
            ||
| 414 | protected function downloadDependencies(array $downloadQueue)  | 
            ||
| 415 |     { | 
            ||
| 416 | $resolvedDependencies = [];  | 
            ||
| 417 |         foreach ($downloadQueue as $extensionToDownload) { | 
            ||
| 418 | $this->rawDownload($extensionToDownload);  | 
            ||
| 419 | $this->downloadQueue->removeExtensionFromQueue($extensionToDownload);  | 
            ||
| 420 | $resolvedDependencies['downloaded'][$extensionToDownload->getExtensionKey()] = $extensionToDownload;  | 
            ||
| 421 | $this->markExtensionForInstallation($extensionToDownload->getExtensionKey());  | 
            ||
| 422 | }  | 
            ||
| 423 | return $resolvedDependencies;  | 
            ||
| 424 | }  | 
            ||
| 425 | |||
| 426 | /**  | 
            ||
| 427 | * Get and resolve dependencies  | 
            ||
| 428 | *  | 
            ||
| 429 | * @param Extension $extension  | 
            ||
| 430 | * @return array  | 
            ||
| 431 | */  | 
            ||
| 432 | public function getAndResolveDependencies(Extension $extension)  | 
            ||
| 441 | }  | 
            ||
| 442 | |||
| 443 | /**  | 
            ||
| 444 | * Downloads the extension the user wants to install  | 
            ||
| 445 | * This is separated from downloading the dependencies  | 
            ||
| 446 | * as an extension is able to provide it's own dependencies  | 
            ||
| 447 | *  | 
            ||
| 448 | * @param Extension $extension  | 
            ||
| 449 | */  | 
            ||
| 450 | public function downloadMainExtension(Extension $extension)  | 
            ||
| 451 |     { | 
            ||
| 452 | // The extension object has a uid if the extension is not present in the system  | 
            ||
| 453 | // or an update of a present extension is triggered.  | 
            ||
| 454 |         if ($extension->getUid()) { | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 455 | $this->rawDownload($extension);  | 
            ||
| 456 | }  | 
            ||
| 457 | }  | 
            ||
| 458 | |||
| 459 | protected function rawDownload(Extension $extension): void  | 
            ||
| 460 |     { | 
            ||
| 461 | if (  | 
            ||
| 462 | Environment::isComposerMode()  | 
            ||
| 463 |             || (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode') | 
            ||
| 464 |         ) { | 
            ||
| 465 |             throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078620); | 
            ||
| 466 | }  | 
            ||
| 467 | |||
| 468 | $remoteIdentifier = $extension->getRemoteIdentifier();  | 
            ||
| 469 | |||
| 470 |         if ($this->remoteRegistry->hasRemote($remoteIdentifier)) { | 
            ||
| 471 | $this->remoteRegistry  | 
            ||
| 472 | ->getRemote($remoteIdentifier)  | 
            ||
| 473 | ->downloadExtension(  | 
            ||
| 474 | $extension->getExtensionKey(),  | 
            ||
| 475 | $extension->getVersion(),  | 
            ||
| 476 | $this->fileHandlingUtility,  | 
            ||
| 477 | $extension->getMd5hash(),  | 
            ||
| 478 | $this->downloadPath  | 
            ||
| 479 | );  | 
            ||
| 480 | }  | 
            ||
| 481 | }  | 
            ||
| 482 | |||
| 483 | /**  | 
            ||
| 484 | * Set the download path  | 
            ||
| 485 | *  | 
            ||
| 486 | * @param string $downloadPath  | 
            ||
| 487 | * @throws ExtensionManagerException  | 
            ||
| 488 | */  | 
            ||
| 489 | public function setDownloadPath(string $downloadPath): void  | 
            ||
| 495 | }  | 
            ||
| 496 | }  | 
            ||
| 497 | 
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
integervalues, zero is a special case, in particular the following results might be unexpected: