Newwebshop/app/Core/DependencyManager.php

725 lines
23 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Dependency-Manager für PrestaShop-Modul-Kompatibilität
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Core;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
class DependencyManager
{
private static $instance = null;
private $moduleManager;
private $plugin;
private $extension;
private $eventDispatcher;
private $cache;
private $logger;
private $enabled = true;
private $dependencyGraph = [];
private $conflictResolutions = [];
private function __construct()
{
$this->moduleManager = ModuleManager::getInstance();
$this->plugin = Plugin::getInstance();
$this->extension = Extension::getInstance();
$this->eventDispatcher = EventDispatcher::getInstance();
$this->cache = Cache::getInstance();
$this->logger = Logger::getInstance();
$this->loadDependencyGraph();
$this->loadConflictResolutions();
}
/**
* Singleton-Instanz abrufen
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Dependency-Graph laden
*/
private function loadDependencyGraph()
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT * FROM ws_dependencies
WHERE active = 1
ORDER BY priority DESC
');
$stmt->execute();
$dependencies = $stmt->fetchAllAssociative();
foreach ($dependencies as $dependency) {
$this->dependencyGraph[$dependency['dependent_name']][] = [
'type' => $dependency['dependency_type'],
'name' => $dependency['dependency_name'],
'version' => $dependency['dependency_version'],
'required' => (bool)$dependency['required'],
'priority' => $dependency['priority']
];
}
} catch (Exception $e) {
$this->logger->error('Dependency-Graph laden Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Konflikt-Lösungen laden
*/
private function loadConflictResolutions()
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT * FROM ws_conflict_resolutions
WHERE active = 1
');
$stmt->execute();
$resolutions = $stmt->fetchAllAssociative();
foreach ($resolutions as $resolution) {
$this->conflictResolutions[] = [
'conflict_type' => $resolution['conflict_type'],
'item1_name' => $resolution['item1_name'],
'item2_name' => $resolution['item2_name'],
'resolution_type' => $resolution['resolution_type'],
'resolution_action' => $resolution['resolution_action'],
'priority' => $resolution['priority']
];
}
} catch (Exception $e) {
$this->logger->error('Konflikt-Lösungen laden Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Dependencies für Item prüfen
*/
public function checkDependencies($itemName, $itemType = 'module')
{
$dependencies = $this->getDependencies($itemName, $itemType);
$results = [];
foreach ($dependencies as $dependency) {
$result = $this->checkSingleDependency($dependency);
$results[] = $result;
}
return $results;
}
/**
* Einzelne Dependency prüfen
*/
private function checkSingleDependency($dependency)
{
$type = $dependency['type'];
$name = $dependency['name'];
$version = $dependency['version'];
$required = $dependency['required'];
$result = [
'type' => $type,
'name' => $name,
'version' => $version,
'required' => $required,
'satisfied' => false,
'current_version' => null,
'error' => null
];
try {
switch ($type) {
case 'module':
$module = $this->moduleManager->getModule($name);
if ($module && $module['active']) {
$result['satisfied'] = true;
$result['current_version'] = $module['version'];
if ($version && version_compare($module['version'], $version, '<')) {
$result['satisfied'] = false;
$result['error'] = 'Version zu alt';
}
} else {
if ($required) {
$result['error'] = 'Modul nicht gefunden oder inaktiv';
}
}
break;
case 'plugin':
$plugin = $this->plugin->getPlugin($name);
if ($plugin && $plugin['active']) {
$result['satisfied'] = true;
$result['current_version'] = $plugin['version'];
if ($version && version_compare($plugin['version'], $version, '<')) {
$result['satisfied'] = false;
$result['error'] = 'Version zu alt';
}
} else {
if ($required) {
$result['error'] = 'Plugin nicht gefunden oder inaktiv';
}
}
break;
case 'extension':
$extension = $this->extension->getExtension($name);
if ($extension && $extension['active']) {
$result['satisfied'] = true;
$result['current_version'] = $extension['version'];
if ($version && version_compare($extension['version'], $version, '<')) {
$result['satisfied'] = false;
$result['error'] = 'Version zu alt';
}
} else {
if ($required) {
$result['error'] = 'Extension nicht gefunden oder inaktiv';
}
}
break;
case 'php':
$currentVersion = PHP_VERSION;
$result['current_version'] = $currentVersion;
if (version_compare($currentVersion, $version, '>=')) {
$result['satisfied'] = true;
} else {
$result['error'] = 'PHP-Version zu alt';
}
break;
case 'extension_php':
if (extension_loaded($name)) {
$result['satisfied'] = true;
$result['current_version'] = phpversion($name);
if ($version && version_compare(phpversion($name), $version, '<')) {
$result['satisfied'] = false;
$result['error'] = 'Extension-Version zu alt';
}
} else {
if ($required) {
$result['error'] = 'PHP-Extension nicht geladen';
}
}
break;
}
} catch (\Exception $e) {
$result['error'] = 'Prüfung fehlgeschlagen: ' . $e->getMessage();
}
return $result;
}
/**
* Dependencies für Item abrufen
*/
public function getDependencies($itemName, $itemType = 'module')
{
$cacheKey = 'dependencies_' . $itemType . '_' . $itemName;
// Cache prüfen
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $cached;
}
$dependencies = [];
// Direkte Dependencies
if (isset($this->dependencyGraph[$itemName])) {
$dependencies = array_merge($dependencies, $this->dependencyGraph[$itemName]);
}
// Rekursive Dependencies
foreach ($dependencies as $dependency) {
$subDependencies = $this->getDependencies($dependency['name'], $dependency['type']);
$dependencies = array_merge($dependencies, $subDependencies);
}
// Duplikate entfernen
$dependencies = $this->removeDuplicateDependencies($dependencies);
// Cache setzen
$this->cache->set($cacheKey, $dependencies, 3600); // 1 Stunde
return $dependencies;
}
/**
* Duplikate aus Dependencies entfernen
*/
private function removeDuplicateDependencies($dependencies)
{
$unique = [];
$seen = [];
foreach ($dependencies as $dependency) {
$key = $dependency['type'] . ':' . $dependency['name'];
if (!isset($seen[$key])) {
$seen[$key] = true;
$unique[] = $dependency;
}
}
return $unique;
}
/**
* Dependency hinzufügen
*/
public function addDependency($dependentName, $dependentType, $dependencyName, $dependencyType, $dependencyVersion = null, $required = true, $priority = 10)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
INSERT INTO ws_dependencies (
dependent_name, dependent_type, dependency_name, dependency_type,
dependency_version, required, priority, active, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE
dependency_version = ?, required = ?, priority = ?, updated_at = NOW()
');
$stmt->execute([
$dependentName,
$dependentType,
$dependencyName,
$dependencyType,
$dependencyVersion,
$required ? 1 : 0,
$priority,
$dependencyVersion,
$required ? 1 : 0,
$priority
]);
// Dependency-Graph neu laden
$this->loadDependencyGraph();
// Cache invalidieren
$this->cache->delete('dependencies_' . $dependentType . '_' . $dependentName);
$this->logger->info('Dependency hinzugefügt', [
'dependent_name' => $dependentName,
'dependent_type' => $dependentType,
'dependency_name' => $dependencyName,
'dependency_type' => $dependencyType,
'dependency_version' => $dependencyVersion,
'required' => $required,
'priority' => $priority
]);
return true;
} catch (Exception $e) {
$this->logger->error('Dependency hinzufügen Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Dependency entfernen
*/
public function removeDependency($dependentName, $dependentType, $dependencyName, $dependencyType)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
DELETE FROM ws_dependencies
WHERE dependent_name = ? AND dependent_type = ?
AND dependency_name = ? AND dependency_type = ?
');
$stmt->execute([
$dependentName,
$dependentType,
$dependencyName,
$dependencyType
]);
// Dependency-Graph neu laden
$this->loadDependencyGraph();
// Cache invalidieren
$this->cache->delete('dependencies_' . $dependentType . '_' . $dependentName);
$this->logger->info('Dependency entfernt', [
'dependent_name' => $dependentName,
'dependent_type' => $dependentType,
'dependency_name' => $dependencyName,
'dependency_type' => $dependencyType
]);
return true;
} catch (Exception $e) {
$this->logger->error('Dependency entfernen Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Konflikte prüfen
*/
public function checkConflicts($itemName, $itemType = 'module')
{
$conflicts = [];
// Bekannte Konflikte prüfen
foreach ($this->conflictResolutions as $resolution) {
if ($this->matchesConflict($itemName, $itemType, $resolution)) {
$conflicts[] = $resolution;
}
}
// Dynamische Konflikt-Prüfung
$dynamicConflicts = $this->checkDynamicConflicts($itemName, $itemType);
$conflicts = array_merge($conflicts, $dynamicConflicts);
return $conflicts;
}
/**
* Konflikt-Matching
*/
private function matchesConflict($itemName, $itemType, $resolution)
{
return ($resolution['item1_name'] === $itemName && $resolution['item1_type'] === $itemType) ||
($resolution['item2_name'] === $itemName && $resolution['item2_type'] === $itemType);
}
/**
* Dynamische Konflikt-Prüfung
*/
private function checkDynamicConflicts($itemName, $itemType)
{
$conflicts = [];
// Hook-Konflikte prüfen
$hookConflicts = $this->checkHookConflicts($itemName, $itemType);
$conflicts = array_merge($conflicts, $hookConflicts);
// Namespace-Konflikte prüfen
$namespaceConflicts = $this->checkNamespaceConflicts($itemName, $itemType);
$conflicts = array_merge($conflicts, $namespaceConflicts);
// Resource-Konflikte prüfen
$resourceConflicts = $this->checkResourceConflicts($itemName, $itemType);
$conflicts = array_merge($conflicts, $resourceConflicts);
return $conflicts;
}
/**
* Hook-Konflikte prüfen
*/
private function checkHookConflicts($itemName, $itemType)
{
$conflicts = [];
// Hook-Registrierungen prüfen
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT hook_name, COUNT(*) as count
FROM ws_hook_registry
WHERE item_name != ? AND active = 1
GROUP BY hook_name
HAVING count > 1
');
$stmt->execute([$itemName]);
$hookConflicts = $stmt->fetchAllAssociative();
foreach ($hookConflicts as $conflict) {
$conflicts[] = [
'type' => 'hook_conflict',
'hook_name' => $conflict['hook_name'],
'description' => 'Mehrere Items registrieren denselben Hook',
'severity' => 'warning'
];
}
} catch (Exception $e) {
$this->logger->error('Hook-Konflikt-Prüfung Fehler', [
'error' => $e->getMessage()
]);
}
return $conflicts;
}
/**
* Namespace-Konflikte prüfen
*/
private function checkNamespaceConflicts($itemName, $itemType)
{
$conflicts = [];
// Namespace-Kollisionen prüfen
$namespaces = $this->getItemNamespaces($itemName, $itemType);
foreach ($namespaces as $namespace) {
if ($this->namespaceExists($namespace)) {
$conflicts[] = [
'type' => 'namespace_conflict',
'namespace' => $namespace,
'description' => 'Namespace bereits belegt',
'severity' => 'error'
];
}
}
return $conflicts;
}
/**
* Resource-Konflikte prüfen
*/
private function checkResourceConflicts($itemName, $itemType)
{
$conflicts = [];
// Asset-Konflikte prüfen
$assets = $this->getItemAssets($itemName, $itemType);
foreach ($assets as $asset) {
if ($this->assetExists($asset)) {
$conflicts[] = [
'type' => 'resource_conflict',
'resource' => $asset,
'description' => 'Resource bereits vorhanden',
'severity' => 'warning'
];
}
}
return $conflicts;
}
/**
* Namespaces für Item abrufen
*/
private function getItemNamespaces($itemName, $itemType)
{
$namespaces = [];
switch ($itemType) {
case 'module':
$namespaces[] = 'App\\Modules\\' . ucfirst($itemName);
break;
case 'plugin':
$namespaces[] = 'App\\Plugins\\' . ucfirst($itemName);
break;
case 'extension':
$namespaces[] = 'App\\Extensions\\' . ucfirst($itemName);
break;
}
return $namespaces;
}
/**
* Assets für Item abrufen
*/
private function getItemAssets($itemName, $itemType)
{
$assets = [];
$basePath = __DIR__ . '/../../../';
switch ($itemType) {
case 'module':
$assets[] = $basePath . 'modules/' . $itemName . '/assets/';
break;
case 'plugin':
$assets[] = $basePath . 'plugins/' . $itemName . '/assets/';
break;
case 'extension':
$assets[] = $basePath . 'extensions/' . $itemName . '/assets/';
break;
}
return $assets;
}
/**
* Namespace-Existenz prüfen
*/
private function namespaceExists($namespace)
{
// Vereinfachte Prüfung - in der Praxis würde hier eine vollständige Namespace-Analyse stehen
return class_exists($namespace . '\\Main');
}
/**
* Asset-Existenz prüfen
*/
private function assetExists($asset)
{
return file_exists($asset);
}
/**
* Konflikt-Lösung hinzufügen
*/
public function addConflictResolution($conflictType, $item1Name, $item1Type, $item2Name, $item2Type, $resolutionType, $resolutionAction, $priority = 10)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
INSERT INTO ws_conflict_resolutions (
conflict_type, item1_name, item1_type, item2_name, item2_type,
resolution_type, resolution_action, priority, active, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, NOW())
');
$stmt->execute([
$conflictType,
$item1Name,
$item1Type,
$item2Name,
$item2Type,
$resolutionType,
$resolutionAction,
$priority
]);
// Konflikt-Lösungen neu laden
$this->loadConflictResolutions();
$this->logger->info('Konflikt-Lösung hinzugefügt', [
'conflict_type' => $conflictType,
'item1_name' => $item1Name,
'item1_type' => $item1Type,
'item2_name' => $item2Name,
'item2_type' => $item2Type,
'resolution_type' => $resolutionType,
'resolution_action' => $resolutionAction,
'priority' => $priority
]);
return true;
} catch (Exception $e) {
$this->logger->error('Konflikt-Lösung hinzufügen Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Dependency-Resolver
*/
public function resolveDependencies($itemName, $itemType = 'module')
{
$dependencies = $this->getDependencies($itemName, $itemType);
$resolved = [];
$unresolved = [];
foreach ($dependencies as $dependency) {
$result = $this->checkSingleDependency($dependency);
if ($result['satisfied']) {
$resolved[] = $result;
} else {
$unresolved[] = $result;
}
}
return [
'resolved' => $resolved,
'unresolved' => $unresolved,
'all_satisfied' => empty($unresolved)
];
}
/**
* Dependency-Manager aktivieren/deaktivieren
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* Dependency-Manager Status prüfen
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Dependency-Graph abrufen
*/
public function getDependencyGraph()
{
return $this->dependencyGraph;
}
/**
* Konflikt-Lösungen abrufen
*/
public function getConflictResolutions()
{
return $this->conflictResolutions;
}
}