loadConfig(); $modes = $config['modes']; if (!isset($modes[$mode])) { throw new RuntimeException("模式不存在:{$mode}"); } $defaultMode = $config['default_mode']; $managedFiles = $config['managed_files']; $plan = []; $skippedFiles = []; foreach ($managedFiles as $relativePath) { $modeSource = $this->resolveModeFile($mode, $relativePath); if ($modeSource !== null) { $plan[] = [ 'file' => $relativePath, 'source_mode' => $mode, 'source_path' => $modeSource, ]; continue; } $defaultSource = $this->resolveModeFile($defaultMode, $relativePath); if ($defaultSource !== null) { $plan[] = [ 'file' => $relativePath, 'source_mode' => $defaultMode, 'source_path' => $defaultSource, ]; continue; } // File not found in mode OR default - skip it (don't throw) $skippedFiles[] = $relativePath; } return [ 'mode' => $mode, 'default_mode' => $defaultMode, 'managed_files' => $managedFiles, 'plan' => $plan, 'skipped_files' => $skippedFiles, ]; } public function applyMode(string $mode, string $operator = 'system'): array { // Use our overridden getModePlan (skips missing files instead of throwing) $result = parent::applyMode($mode, $operator); $appliedFiles = $result['applied_files'] ?? []; $backupId = $result['backup_id'] ?? ''; $config = $this->loadConfig(); $managedFiles = $config['managed_files']; $deletedFiles = []; foreach ($managedFiles as $relativePath) { // Skip files already handled by parent if (in_array($relativePath, $appliedFiles)) { continue; } $targetPath = $this->toRootPath($relativePath); if (!is_file($targetPath)) { continue; } // This file was skipped by getModePlan (not in mode or default) // Back it up before deleting if ($backupId !== '') { $backupDir = $this->joinPath($this->backupRoot, $backupId); $backupFilesDir = $this->joinPath($backupDir, 'files'); $backupFilePath = $this->joinPath($backupFilesDir, $this->toSystemPath($relativePath)); $this->ensureDir(dirname($backupFilePath)); copy($targetPath, $backupFilePath); // Update backup meta $metaPath = $this->joinPath($backupDir, 'meta.json'); if (is_file($metaPath)) { $meta = $this->readJsonFile($metaPath); $meta['files'][] = [ 'file' => $relativePath, 'existed' => true, ]; $this->writeJsonFile($metaPath, $meta); } // Only delete if we successfully backed up @unlink($targetPath); $deletedFiles[] = $relativePath; } } $result['deleted_files'] = $deletedFiles; return $result; } }