aFilesToRemove as $sTmpFile){ @unlink($sTmpFile); } } public function ListDatamodelFiles() : array { if (count($this->aAllDmFiles)==0){ $aGlobPAtterns = [ APPROOT.'datamodels/2.x/*/datamodel.*.xml', APPROOT.'application/*.xml', APPROOT.'core/*.xml', ]; foreach ($aGlobPAtterns as $sPattern) { $this->aAllDmFiles = array_merge($this->aAllDmFiles, glob($sPattern)); } } return $this->aAllDmFiles; } public function FetchAllDependenciesViaDM() { foreach ($this->ListDatamodelFiles() as $sFile) { $this->FetchXmlMetaInfo($sFile); } foreach ($this->aDefineNodes as $sKey => $aModules){ foreach ($aModules as $sModuleName){ if (! array_key_exists($sModuleName, $this->aModules)){ $this->aModules[$sModuleName] = new XmlModule($sModuleName); } } $aSoftlyDependentModules = $this->aSoftDependencyNodes[$sKey] ?? null; if (is_null($aSoftlyDependentModules)){ continue; } foreach ($aSoftlyDependentModules as $sModuleName){ /** @var XmlModule $oCurrentXmlModule */ $oCurrentXmlModule = $this->aModules[$sModuleName] ?? null; if (is_null($oCurrentXmlModule)){ $oCurrentXmlModule = new XmlModule($sModuleName); $this->aModules[$sModuleName] = $oCurrentXmlModule; } $oCurrentXmlModule->AddDependency($sKey, $aModules, $this->aModules); } } foreach ($this->aDependencyNodes as $sKey => $aModules){ foreach ($aModules as $sModuleName){ /** @var XmlModule $oCurrentXmlModule */ $oCurrentXmlModule = $this->aModules[$sModuleName] ?? null; if (is_null($oCurrentXmlModule)){ $oCurrentXmlModule = new XmlModule($sModuleName); $this->aModules[$sModuleName] = $oCurrentXmlModule; } $aDefiningModules = $this->aDefineNodes[$sKey] ?? null; if (is_null($aDefiningModules)){ continue; } $oCurrentXmlModule->AddDependency($sKey, $aDefiningModules, $this->aModules); } } $this->OrderModules(); $this->CompleteModuleDependencies(); } public function testModules() { $this->FetchAllDependenciesViaDM(); $aDirsToScan = [ APPROOT.'datamodels/2.x', APPROOT.'extensions', APPROOT.'data/production-modules', ]; foreach(\ModuleDiscovery::GetAvailableModules($aDirsToScan) as $sModuleId => $aData) { list($sModuleName, $sVersion) = \ModuleDiscovery::GetModuleName($sModuleId); $aCurrentDeps = $aData['dependencies'] ?? []; $this->aModulesDepsByModuleName[$sModuleName] = $aCurrentDeps; } $aErrors=[]; /** @var XmlModule $oXmlModule */ foreach ($this->aModules as $sModuleName => $oXmlModule) { $aCurrentDeps = $this->aModulesDepsByModuleName[$sModuleName] ?? []; $aModuleErrors=[]; foreach ($oXmlModule->aDependencyModulesNames as $sDepModuleName => $oXmlModule2){ $sXmlUIDs = implode('|', $oXmlModule->aXMlMetaInfosByModuleNames[$sDepModuleName]); $bResolved=false; foreach ($aCurrentDeps as $sDepString){ $oModuleDependency = new \ModuleDependency($sDepString); if (in_array($sDepModuleName, $oModuleDependency->GetPotentialPrerequisiteModuleNames())) { $bResolved=true; break; } foreach ($oModuleDependency->GetPotentialPrerequisiteModuleNames() as $sPotentialDepModuleName){ /** @var XmlModule $oXmlModule2 */ $oXmlModule2 = $this->aModules[$sPotentialDepModuleName]??null; if (! is_null($oXmlModule2) && $oXmlModule2->Depends($sDepModuleName)){ $bResolved=true; break; } } if ($bResolved) { break; } } if (! $bResolved){ $aModuleErrors []= "$sModuleName depends on $sDepModuleName but missing in module dependencies: " . implode(' & ', $aCurrentDeps) . ". ($sXmlUIDs)"; } } if (count($aModuleErrors)){ $aErrors[$sModuleName]=$aModuleErrors; } } $this->assertEquals(0, count($aErrors), var_export($aErrors, true)); } private function GetModuleSuffix($sFile) : string { if (! preg_match('|.*datamodel\.([^\.]+)\.xml|', $sFile, $aMatches)){ throw new \Exception("Regexp issue: $sFile"); } return $aMatches[1]; } public function FetchXmlMetaInfo($sFile) : void { $oDomDoc = new \DOMDocument('1.0', 'UTF-8'); libxml_clear_errors(); $oDomDoc->loadXml(file_get_contents($sFile)); $aErrors = libxml_get_errors(); if (count($aErrors) > 0) { throw new \Exception("Malformed XML"); } $this->sCurrentModule = $this->GetModuleSuffix($sFile); if (! isset($this->aDefineNodes)){ $this->aDefineNodes=[]; } if (! isset($this->aDependencyNodes)){ $this->aDependencyNodes=[]; } foreach ($oDomDoc->childNodes as $oDomNode){ $this->FetchMetaInfo($oDomDoc->childNodes); } } private function FetchMetaInfo(\DOMNodeList $oDomNodeList, ?string $sPath=null) { /** @var \DOMNode $oDomNode */ foreach ($oDomNodeList as $oDomNode) { /** @var \DOMAttr $oDelta */ $oDelta = $oDomNode->attributes['_delta'] ?? null; /** @var \DOMAttr $oId */ $oId = $oDomNode->attributes['id'] ?? null; if (! is_null($oId)) { $sId = $oId->nodeValue; $sCurrentPath = $sPath ? $sPath."->".$sId : $sId; if (!is_null($oDelta)) { $oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sCurrentPath, $oDelta->nodeValue); $sKey = $oXmlModuleMetaInfo->GetUID(); if ($oXmlModuleMetaInfo->IsDefine()) { if (array_key_exists($sKey, $this->aDefineNodes)) { $this->aDefineNodes[$sKey][] = $this->sCurrentModule; } else { $this->aDefineNodes[$sKey] = [$this->sCurrentModule]; } } else { if (array_key_exists($sKey, $this->aDependencyNodes)) { $this->aDependencyNodes[$sKey][] = $this->sCurrentModule; } else { $this->aDependencyNodes[$sKey] = [$this->sCurrentModule]; } } } else { $oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sCurrentPath, "nodelta"); $sKey = $oXmlModuleMetaInfo->GetUID(); if (array_key_exists($sKey, $this->aSoftDependencyNodes)) { $this->aSoftDependencyNodes[$sKey][] = $this->sCurrentModule; } else { $this->aSoftDependencyNodes[$sKey] = [$this->sCurrentModule]; } } } else if ($oDomNode instanceof \DOMElement){ $sCurrentPath = $sPath ? $sPath . '->' . $oDomNode->nodeName : $oDomNode->nodeName; } else{ $sCurrentPath = $sPath; } $this->FetchMetaInfo($oDomNode->childNodes, $sCurrentPath); } } private function OrderModules() { $aModuleDepsCount = []; /** @var XmlModule $oXmlModule */ foreach ($this->aModules as $oXmlModule) { $aModuleDepsCount[$oXmlModule->sModuleName] = count($oXmlModule->aDependencyModulesNames); } $aOrderModules=[]; while (count($aModuleDepsCount)>0) { asort($aModuleDepsCount); foreach ($aModuleDepsCount as $sModuleName => $iCount){ if ($iCount>0){ throw new \Exception("still deps with $sModuleName"); } unset($aModuleDepsCount[$sModuleName]); $aOrderModules[$sModuleName] = $this->aModules[$sModuleName]; break; } echo "$sModuleName\n"; foreach ($aModuleDepsCount as $sStillToProcessModuleName => $c){ /** @var XmlModule $oXmlStillToProcessModule */ $oXmlStillToProcessModule = $this->aModules[$sStillToProcessModuleName]; if ($oXmlStillToProcessModule->Depends($sModuleName)){ $aModuleDepsCount[$sStillToProcessModuleName] = $c - 1 ; } } } $this->aModules = $aOrderModules; } private function CompleteModuleDependencies() { /** @var XmlModule $oXmlModule */ foreach ($this->aModules as $oXmlModule) { $oXmlModule->CompleteModuleDependencies($this->aModules); } } }