| Total Complexity | 56 | 
| Total Lines | 370 | 
| Duplicated Lines | 0 % | 
| Changes | 3 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like BundleSyncHelper 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 BundleSyncHelper, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 39 | class BundleSyncHelper  | 
            ||
| 40 | { | 
            ||
| 41 | /**  | 
            ||
| 42 | * @var ZikulaHttpKernelInterface  | 
            ||
| 43 | */  | 
            ||
| 44 | private $kernel;  | 
            ||
| 45 | |||
| 46 | /**  | 
            ||
| 47 | * @var ExtensionRepositoryInterface  | 
            ||
| 48 | */  | 
            ||
| 49 | private $extensionRepository;  | 
            ||
| 50 | |||
| 51 | /**  | 
            ||
| 52 | * @var ExtensionVarRepositoryInterface  | 
            ||
| 53 | */  | 
            ||
| 54 | private $extensionVarRepository;  | 
            ||
| 55 | |||
| 56 | /**  | 
            ||
| 57 | * @var ExtensionDependencyRepository  | 
            ||
| 58 | */  | 
            ||
| 59 | private $extensionDependencyRepository;  | 
            ||
| 60 | |||
| 61 | /**  | 
            ||
| 62 | * @var TranslatorInterface  | 
            ||
| 63 | */  | 
            ||
| 64 | private $translator;  | 
            ||
| 65 | |||
| 66 | /**  | 
            ||
| 67 | * @var EventDispatcherInterface  | 
            ||
| 68 | */  | 
            ||
| 69 | private $dispatcher;  | 
            ||
| 70 | |||
| 71 | /**  | 
            ||
| 72 | * @var ExtensionStateHelper  | 
            ||
| 73 | */  | 
            ||
| 74 | private $extensionStateHelper;  | 
            ||
| 75 | |||
| 76 | /**  | 
            ||
| 77 | * @var BundlesSchemaHelper  | 
            ||
| 78 | */  | 
            ||
| 79 | private $bundlesSchemaHelper;  | 
            ||
| 80 | |||
| 81 | /**  | 
            ||
| 82 | * @var ComposerValidationHelper  | 
            ||
| 83 | */  | 
            ||
| 84 | private $composerValidationHelper;  | 
            ||
| 85 | |||
| 86 | /**  | 
            ||
| 87 | * @var SessionInterface  | 
            ||
| 88 | */  | 
            ||
| 89 | protected $session;  | 
            ||
| 90 | |||
| 91 | public function __construct(  | 
            ||
| 92 | ZikulaHttpKernelInterface $kernel,  | 
            ||
| 93 | ExtensionRepositoryInterface $extensionRepository,  | 
            ||
| 94 | ExtensionVarRepositoryInterface $extensionVarRepository,  | 
            ||
| 95 | ExtensionDependencyRepository $extensionDependencyRepository,  | 
            ||
| 96 | TranslatorInterface $translator,  | 
            ||
| 97 | EventDispatcherInterface $dispatcher,  | 
            ||
| 98 | ExtensionStateHelper $extensionStateHelper,  | 
            ||
| 99 | BundlesSchemaHelper $bundlesSchemaHelper,  | 
            ||
| 100 | ComposerValidationHelper $composerValidationHelper,  | 
            ||
| 101 | SessionInterface $session  | 
            ||
| 102 |     ) { | 
            ||
| 103 | $this->kernel = $kernel;  | 
            ||
| 104 | $this->extensionRepository = $extensionRepository;  | 
            ||
| 105 | $this->extensionVarRepository = $extensionVarRepository;  | 
            ||
| 106 | $this->extensionDependencyRepository = $extensionDependencyRepository;  | 
            ||
| 107 | $this->translator = $translator;  | 
            ||
| 108 | $this->dispatcher = $dispatcher;  | 
            ||
| 109 | $this->extensionStateHelper = $extensionStateHelper;  | 
            ||
| 110 | $this->bundlesSchemaHelper = $bundlesSchemaHelper;  | 
            ||
| 111 | $this->composerValidationHelper = $composerValidationHelper;  | 
            ||
| 112 | $this->session = $session;  | 
            ||
| 113 | }  | 
            ||
| 114 | |||
| 115 | /**  | 
            ||
| 116 | * Scan the extensions directory for bundles and returns an array with all (potential) bundles found.  | 
            ||
| 117 | */  | 
            ||
| 118 | public function scanForBundles($includeCore = false): array  | 
            ||
| 119 |     { | 
            ||
| 120 | // sync the extensions directory and the bundles table  | 
            ||
| 121 | $this->bundlesSchemaHelper->load();  | 
            ||
| 122 | $scanner = $this->bundlesSchemaHelper->getScanner();  | 
            ||
| 123 |         foreach ($scanner->getInvalid() as $invalidName) { | 
            ||
| 124 | $this->session->getFlashBag()->add(  | 
            ||
| 125 | 'warning',  | 
            ||
| 126 | $this->translator->trans(  | 
            ||
| 127 | 'WARNING: %extension% has an invalid composer.json file which could not be decoded.',  | 
            ||
| 128 | ['%extension%' => $invalidName]  | 
            ||
| 129 | )  | 
            ||
| 130 | );  | 
            ||
| 131 | }  | 
            ||
| 132 | $extensions = $scanner->getExtensionsMetaData();  | 
            ||
| 133 | |||
| 134 | $bundles = [];  | 
            ||
| 135 | $srcDir = $this->kernel->getProjectDir() . '/src/';  | 
            ||
| 136 | /** @var MetaData $bundleMetaData */  | 
            ||
| 137 |         foreach ($extensions as $name => $bundleMetaData) { | 
            ||
| 138 |             foreach ($bundleMetaData->getPsr4() as $ns => $path) { | 
            ||
| 139 | $this->kernel->getAutoloader()->addPsr4($ns, $srcDir . $path);  | 
            ||
| 140 | }  | 
            ||
| 141 | |||
| 142 | $bundleClass = $bundleMetaData->getClass();  | 
            ||
| 143 | |||
| 144 | /** @var $bundle \Zikula\Bundle\CoreBundle\AbstractBundle */  | 
            ||
| 145 | $bundle = new $bundleClass();  | 
            ||
| 146 | $bundleMetaData->setTranslator($this->translator);  | 
            ||
| 147 | $bundleVersionArray = $bundleMetaData->getFilteredVersionInfoArray();  | 
            ||
| 148 | |||
| 149 | $finder = new Finder();  | 
            ||
| 150 |             $finder->files()->in($bundle->getPath())->depth(0)->name('composer.json'); | 
            ||
| 151 |             foreach ($finder as $splFileInfo) { | 
            ||
| 152 | // there will only be one loop here  | 
            ||
| 153 | $this->composerValidationHelper->check($splFileInfo);  | 
            ||
| 154 |                 if ($this->composerValidationHelper->isValid()) { | 
            ||
| 155 | $bundles[$bundle->getName()] = $bundleVersionArray;  | 
            ||
| 156 | $bundles[$bundle->getName()]['oldnames'] = $bundleVersionArray['oldnames'] ?? '';  | 
            ||
| 157 |                 } else { | 
            ||
| 158 | $this->session->getFlashBag()->add(  | 
            ||
| 159 | 'error',  | 
            ||
| 160 | $this->translator->trans(  | 
            ||
| 161 | 'Cannot load %extension% because the composer file is invalid.',  | 
            ||
| 162 | ['%extension%' => $bundle->getName()]  | 
            ||
| 163 | )  | 
            ||
| 164 | );  | 
            ||
| 165 |                     foreach ($this->composerValidationHelper->getErrors() as $error) { | 
            ||
| 166 |                         $this->session->getFlashBag()->add('error', $error); | 
            ||
| 167 | }  | 
            ||
| 168 | }  | 
            ||
| 169 | }  | 
            ||
| 170 | }  | 
            ||
| 171 | |||
| 172 |         if ($includeCore) { | 
            ||
| 173 | $this->appendCoreExtensionsMetaData($bundles);  | 
            ||
| 174 | }  | 
            ||
| 175 | $this->validate($bundles);  | 
            ||
| 176 | |||
| 177 | return $bundles;  | 
            ||
| 178 | }  | 
            ||
| 179 | |||
| 180 | private function appendCoreExtensionsMetaData(array &$extensions): void  | 
            ||
| 181 |     { | 
            ||
| 182 |         foreach (ZikulaKernel::$coreExtension as $systemModule => $bundleClass) { | 
            ||
| 183 | $bundle = $this->kernel->getBundle($systemModule);  | 
            ||
| 184 |             if ($bundle instanceof AbstractBundle) { | 
            ||
| 185 | $extensions[$systemModule] = $bundle->getMetaData()->getFilteredVersionInfoArray();  | 
            ||
| 186 | }  | 
            ||
| 187 | }  | 
            ||
| 188 | }  | 
            ||
| 189 | |||
| 190 | /**  | 
            ||
| 191 | * Validate the extensions and ensure there are no duplicate names, display names or urls.  | 
            ||
| 192 | *  | 
            ||
| 193 | * @throws FatalError  | 
            ||
| 194 | */  | 
            ||
| 195 | private function validate(array $extensions = []): void  | 
            ||
| 196 |     { | 
            ||
| 197 | $fieldNames = ['name', 'displayname', 'url'];  | 
            ||
| 198 | $moduleValues = [  | 
            ||
| 199 | 'name' => [],  | 
            ||
| 200 | 'displayname' => [],  | 
            ||
| 201 | 'url' => []  | 
            ||
| 202 | ];  | 
            ||
| 203 | |||
| 204 | // check for duplicate name, display name or url  | 
            ||
| 205 |         foreach ($extensions as $dir => $modInfo) { | 
            ||
| 206 |             foreach ($fieldNames as $fieldName) { | 
            ||
| 207 | $key = mb_strtolower($modInfo[$fieldName]);  | 
            ||
| 208 |                 if (!empty($moduleValues[$fieldName][$key]) && !empty($modInfo[$fieldName])) { | 
            ||
| 209 |                     $message = $this->translator->trans('Fatal error: Two extensions share the same %field%. [%ext1%] and [%ext2%]', [ | 
            ||
| 210 | '%field%' => $fieldName,  | 
            ||
| 211 | '%ext1%' => $modInfo['name'],  | 
            ||
| 212 | '%ext2%' => $moduleValues[$fieldName][$key]  | 
            ||
| 213 | ]);  | 
            ||
| 214 | throw new FatalError($message, 500, error_get_last());  | 
            ||
| 215 | }  | 
            ||
| 216 | $moduleValues[$fieldName][$key] = $dir;  | 
            ||
| 217 | }  | 
            ||
| 218 | }  | 
            ||
| 219 | }  | 
            ||
| 220 | |||
| 221 | /**  | 
            ||
| 222 | * Sync extensions in the filesystem and the extensions table.  | 
            ||
| 223 | *  | 
            ||
| 224 | * @return array $upgradedExtensions[<name>] = <version>  | 
            ||
| 225 | */  | 
            ||
| 226 | public function syncExtensions(array $extensionsFromFile, bool $forceDefaults = false): array  | 
            ||
| 227 |     { | 
            ||
| 228 | // Get all extensions in DB, indexed by name  | 
            ||
| 229 |         $extensionsFromDB = $this->extensionRepository->getIndexedArrayCollection('name'); | 
            ||
| 230 | |||
| 231 | // see if any extensions have changed since last regeneration  | 
            ||
| 232 | $this->syncUpdatedExtensions($extensionsFromFile, $extensionsFromDB, $forceDefaults);  | 
            ||
| 233 | |||
| 234 | // See if any extensions have been lost since last sync  | 
            ||
| 235 | $this->syncLostExtensions($extensionsFromFile, $extensionsFromDB);  | 
            ||
| 236 | |||
| 237 | // See any extensions have been gained since last sync,  | 
            ||
| 238 | // or if any current extensions have been upgraded  | 
            ||
| 239 | $upgradedExtensions = $this->syncAddedExtensions($extensionsFromFile, $extensionsFromDB);  | 
            ||
| 240 | |||
| 241 | // Clear and reload the dependencies table with all current dependencies  | 
            ||
| 242 | $this->extensionDependencyRepository->reloadExtensionDependencies($extensionsFromFile);  | 
            ||
| 243 | |||
| 244 | return $upgradedExtensions;  | 
            ||
| 245 | }  | 
            ||
| 246 | |||
| 247 | /**  | 
            ||
| 248 | * Sync extensions that are already in the Database.  | 
            ||
| 249 | * - update from old names  | 
            ||
| 250 | * - update compatibility  | 
            ||
| 251 | * - update user settings (or reset to defaults)  | 
            ||
| 252 | * - ensure current core compatibility  | 
            ||
| 253 | */  | 
            ||
| 254 | private function syncUpdatedExtensions(  | 
            ||
| 255 | array $extensionsFromFile,  | 
            ||
| 256 | array &$extensionsFromDB,  | 
            ||
| 257 | bool $forceDefaults = false  | 
            ||
| 258 |     ): void { | 
            ||
| 259 |         foreach ($extensionsFromFile as $name => $extensionFromFile) { | 
            ||
| 260 |             foreach ($extensionsFromDB as $dbname => $extensionFromDB) { | 
            ||
| 261 |                 if (isset($extensionFromDB['name']) && in_array($extensionFromDB['name'], (array)$extensionFromFile['oldnames'], true)) { | 
            ||
| 262 | // migrate its modvars  | 
            ||
| 263 | $this->extensionVarRepository->updateName($dbname, $name);  | 
            ||
| 264 | // rename the extension register  | 
            ||
| 265 | $this->extensionRepository->updateName($dbname, $name);  | 
            ||
| 266 | // replace the old extension with the new one in the $extensionsFromDB array  | 
            ||
| 267 | $extensionsFromDB[$name] = $extensionFromDB;  | 
            ||
| 268 | unset($extensionsFromDB[$dbname]);  | 
            ||
| 269 | }  | 
            ||
| 270 | }  | 
            ||
| 271 | |||
| 272 | // If extension was previously determined to be incompatible with the core. return to original state  | 
            ||
| 273 |             if (isset($extensionsFromDB[$name]) && $extensionsFromDB[$name]['state'] > 10) { | 
            ||
| 274 | $extensionsFromDB[$name]['state'] -= Constant::INCOMPATIBLE_CORE_SHIFT;  | 
            ||
| 275 | $this->extensionStateHelper->updateState($extensionsFromDB[$name]['id'], $extensionsFromDB[$name]['state']);  | 
            ||
| 276 | }  | 
            ||
| 277 | |||
| 278 | // update the DB information for this extension to reflect user settings (e.g. url)  | 
            ||
| 279 |             if (isset($extensionsFromDB[$name]['id'])) { | 
            ||
| 280 | $extensionFromFile['id'] = $extensionsFromDB[$name]['id'];  | 
            ||
| 281 |                 if (Constant::STATE_UNINITIALISED !== $extensionsFromDB[$name]['state'] && Constant::STATE_INVALID !== $extensionsFromDB[$name]['state']) { | 
            ||
| 282 | unset($extensionFromFile['version']);  | 
            ||
| 283 | }  | 
            ||
| 284 |                 if (!$forceDefaults) { | 
            ||
| 285 | unset($extensionFromFile['displayname'], $extensionFromFile['description'], $extensionFromFile['url']);  | 
            ||
| 286 | }  | 
            ||
| 287 | |||
| 288 | unset($extensionFromFile['oldnames'], $extensionFromFile['dependencies']);  | 
            ||
| 289 | |||
| 290 | /** @var ExtensionEntity $extension */  | 
            ||
| 291 | $extension = $this->extensionRepository->find($extensionFromFile['id']);  | 
            ||
| 292 | $extension->merge($extensionFromFile);  | 
            ||
| 293 | $this->extensionRepository->persistAndFlush($extension);  | 
            ||
| 294 | }  | 
            ||
| 295 | |||
| 296 | // check extension core requirement is compatible with current core  | 
            ||
| 297 | $coreCompatibility = $extensionFromFile['coreCompatibility'];  | 
            ||
| 298 |             if (isset($extensionsFromDB[$name])) { | 
            ||
| 299 |                 if (!Semver::satisfies(ZikulaKernel::VERSION, $coreCompatibility)) { | 
            ||
| 300 | // extension is incompatible with current core  | 
            ||
| 301 | $extensionsFromDB[$name]['state'] += Constant::INCOMPATIBLE_CORE_SHIFT;  | 
            ||
| 302 | $this->extensionStateHelper->updateState($extensionsFromDB[$name]['id'], $extensionsFromDB[$name]['state']);  | 
            ||
| 303 | }  | 
            ||
| 304 |                 if (isset($extensionsFromDB[$name]['state'])) { | 
            ||
| 305 | $extensionFromFile['state'] = $extensionsFromDB[$name]['state'];  | 
            ||
| 306 | }  | 
            ||
| 307 | }  | 
            ||
| 308 | }  | 
            ||
| 309 | }  | 
            ||
| 310 | |||
| 311 | /**  | 
            ||
| 312 | * Remove extensions from the DB that have been removed from the filesystem.  | 
            ||
| 313 | */  | 
            ||
| 314 | private function syncLostExtensions(array $extensionsFromFile, array &$extensionsFromDB): void  | 
            ||
| 343 | }  | 
            ||
| 344 | }  | 
            ||
| 345 | |||
| 346 | /**  | 
            ||
| 347 | * Add extensions to the DB that have been added to the filesystem.  | 
            ||
| 348 | * - add uninitialized extensions  | 
            ||
| 349 | * - update missing or invalid extensions  | 
            ||
| 350 | *  | 
            ||
| 351 | * @return array $upgradedExtensions[<name>] => <version>  | 
            ||
| 352 | */  | 
            ||
| 353 | private function syncAddedExtensions(array $extensionsFromFile, array $extensionsFromDB): array  | 
            ||
| 409 | }  | 
            ||
| 410 | }  | 
            ||
| 411 |