aFilesToRemove as $sTmpFile){ @unlink($sTmpFile); } } private array $aAllDmFiles=[]; 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; } private array $aModules=[]; 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); } } } 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)){ //throw new \Exception("weard behaviour $sKey"); continue; } foreach ($aDefiningModules as $sDefiningModuleName){ $oCurrentXmlModule->AddDependency($sKey, $sDefiningModuleName, $this->aModules); } } } $this->OrderModules(); $this->CompleteModuleDependencies(); } private array $aModulesDepsByModuleName=[]; 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){ if ($sDepModuleName==="core"||$sDepModuleName==="application"){ continue; } $sXmlUIDs = implode('|', $oXmlModule->aXMlMetaInfosByModuleNames[$sDepModuleName]); foreach ($aCurrentDeps as $sDepString){ $oModuleDependency = new \ModuleDependency($sDepString); if (! in_array($sDepModuleName, $oModuleDependency->GetPotentialPrerequisiteModuleNames())){ $bResolved=false; foreach ($oModuleDependency->GetPotentialPrerequisiteModuleNames() as $sPotentialDepModuleName){ /** @var XmlModule $oXmlModule2 */ $oXmlModule2 = $this->aModules[$sPotentialDepModuleName]??null; if (! is_null($oXmlModule2) && $oXmlModule2->Depends($sDepModuleName)){ $bResolved=true; } } 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)); } public function testGetMetaInfo() { $this->FetchXmlMetaInfo(APPROOT . 'core/datamodel.core.xml'); $this->assertEquals("core", $this->sCurrentModule); $this->assertEquals(22, count($this->aDefineNodes)); $this->assertEquals(0, count($this->aDependencyNodes)); //var_dump($this->aDefineNodes); $this->assertEquals([], $this->aDefineNodes); $this->assertEquals([], $this->aDependencyNodes); } private string $sCurrentModule; private array $aDefineNodes; private array $aDependencyNodes; 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($oDelta) && ! is_null($oId)) { $sId = $oId->nodeValue; $sPath = $sPath ? $sPath . "->" . $sId : $sId; $oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sPath, $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 ]; } } } $this->FetchMetaInfo($oDomNode->childNodes, $sPath); } } 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){ /** @var XmlModule $oXmlModule */ $oXmlModule = $this->aModules[$sModuleName]; /*var_dump((string)$oXmlModule); var_dump((string)$this->aModules['itop-datacenter-mgmt']); var_dump((string)$this->aModules['itop-config-mgmt']); var_dump($aModuleDepsCount);*/ //var_dump($this->aModules['itop-bridge-cmdb-services']->aXMlMetaInfosByModuleNames["itop-bridge-cmdb-ticket"]); //var_dump($this->aModules['itop-bridge-cmdb-ticket']->aXMlMetaInfosByModuleNames["itop-bridge-cmdb-services"]); 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); } } }