From 1ccc3ad44018d3b5ab4012b7639ae21fdcf348f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 2 Feb 2026 19:48:27 +0100 Subject: [PATCH] Extracted logic used by both BackupManager and UpdateExecutor to new service --- src/Services/System/BackupManager.php | 56 ++++-------------- src/Services/System/CommandRunHelper.php | 75 ++++++++++++++++++++++++ src/Services/System/UpdateExecutor.php | 33 +---------- 3 files changed, 89 insertions(+), 75 deletions(-) create mode 100644 src/Services/System/CommandRunHelper.php diff --git a/src/Services/System/BackupManager.php b/src/Services/System/BackupManager.php index b646e433..9bdc7f71 100644 --- a/src/Services/System/BackupManager.php +++ b/src/Services/System/BackupManager.php @@ -35,17 +35,18 @@ use Symfony\Component\Process\Process; * This service handles all backup-related operations and can be used * by the Update Manager, CLI commands, or other services. */ -class BackupManager +readonly class BackupManager { private const BACKUP_DIR = 'var/backups'; public function __construct( #[Autowire(param: 'kernel.project_dir')] - private readonly string $projectDir, - private readonly LoggerInterface $logger, - private readonly Filesystem $filesystem, - private readonly VersionManagerInterface $versionManager, - private readonly EntityManagerInterface $entityManager, + private string $projectDir, + private LoggerInterface $logger, + private Filesystem $filesystem, + private VersionManagerInterface $versionManager, + private EntityManagerInterface $entityManager, + private CommandRunHelper $commandRunHelper, ) { } @@ -90,7 +91,7 @@ class BackupManager $backupFile = $backupDir . '/' . $prefix . '-v' . $currentVersion . '-' . date('Y-m-d-His') . '.zip'; } - $this->runCommand([ + $this->commandRunHelper->runCommand([ 'php', 'bin/console', 'partdb:backup', '--full', '--overwrite', @@ -103,7 +104,7 @@ class BackupManager } /** - * Get list of backups. + * Get list of backups, that are available, sorted by date descending. * * @return array */ @@ -126,7 +127,7 @@ class BackupManager } // Sort by date descending - usort($backups, fn($a, $b) => $b['date'] <=> $a['date']); + usort($backups, static fn($a, $b) => $b['date'] <=> $a['date']); return $backups; } @@ -135,7 +136,7 @@ class BackupManager * Get details about a specific backup file. * * @param string $filename The backup filename - * @return array|null Backup details or null if not found + * @return null|array{file: string, path: string, date: int, size: int, from_version: ?string, to_version: ?string, contains_database?: bool, contains_config?: bool, contains_attachments?: bool} Backup details or null if not found */ public function getBackupDetails(string $filename): ?array { @@ -449,39 +450,4 @@ class BackupManager $this->filesystem->mirror($uploads, $this->projectDir . '/uploads', null, ['override' => true]); } } - - /** - * Run a shell command with proper error handling. - */ - private function runCommand(array $command, string $description, int $timeout = 120): string - { - $process = new Process($command, $this->projectDir); - $process->setTimeout($timeout); - - // Set environment variables - $currentEnv = getenv(); - if (!is_array($currentEnv)) { - $currentEnv = []; - } - $env = array_merge($currentEnv, [ - 'HOME' => $this->projectDir, - 'COMPOSER_HOME' => $this->projectDir . '/var/composer', - 'PATH' => getenv('PATH') ?: '/usr/local/bin:/usr/bin:/bin', - ]); - $process->setEnv($env); - - $output = ''; - $process->run(function ($type, $buffer) use (&$output) { - $output .= $buffer; - }); - - if (!$process->isSuccessful()) { - $errorOutput = $process->getErrorOutput() ?: $process->getOutput(); - throw new \RuntimeException( - sprintf('%s failed: %s', $description, trim($errorOutput)) - ); - } - - return $output; - } } diff --git a/src/Services/System/CommandRunHelper.php b/src/Services/System/CommandRunHelper.php new file mode 100644 index 00000000..7a144d5f --- /dev/null +++ b/src/Services/System/CommandRunHelper.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\System; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Process\Process; + +class CommandRunHelper +{ + private UpdateExecutor $updateExecutor; + + public function __construct( + #[Autowire(param: 'kernel.project_dir')] private readonly string $project_dir + ) + { + } + + /** + * Run a shell command with proper error handling. + */ + public function runCommand(array $command, string $description, int $timeout = 120): string + { + $process = new Process($command, $this->project_dir); + $process->setTimeout($timeout); + + // Set environment variables needed for Composer and other tools + // This is especially important when running as www-data which may not have HOME set + // We inherit from current environment and override/add specific variables + $currentEnv = getenv(); + if (!is_array($currentEnv)) { + $currentEnv = []; + } + $env = array_merge($currentEnv, [ + 'HOME' => $this->project_dir.'/var/www-data-home', + 'COMPOSER_HOME' => $this->project_dir.'/var/composer', + 'PATH' => getenv('PATH') ?: '/usr/local/bin:/usr/bin:/bin', + ]); + $process->setEnv($env); + + $output = ''; + $process->run(function ($type, $buffer) use (&$output) { + $output .= $buffer; + }); + + if (!$process->isSuccessful()) { + $errorOutput = $process->getErrorOutput() ?: $process->getOutput(); + throw new \RuntimeException( + sprintf('%s failed: %s', $description, trim($errorOutput)) + ); + } + + return $output; + } +} diff --git a/src/Services/System/UpdateExecutor.php b/src/Services/System/UpdateExecutor.php index d6bc4127..90cabf82 100644 --- a/src/Services/System/UpdateExecutor.php +++ b/src/Services/System/UpdateExecutor.php @@ -46,6 +46,7 @@ class UpdateExecutor private array $steps = []; private ?string $currentLogFile = null; + private CommandRunHelper $commandRunHelper; public function __construct( #[Autowire(param: 'kernel.project_dir')] @@ -58,6 +59,7 @@ class UpdateExecutor #[Autowire(param: 'app.debug_mode')] private readonly bool $debugMode = false, ) { + $this->commandRunHelper = new CommandRunHelper($this); } /** @@ -516,36 +518,7 @@ class UpdateExecutor */ private function runCommand(array $command, string $description, int $timeout = 120): string { - $process = new Process($command, $this->project_dir); - $process->setTimeout($timeout); - - // Set environment variables needed for Composer and other tools - // This is especially important when running as www-data which may not have HOME set - // We inherit from current environment and override/add specific variables - $currentEnv = getenv(); - if (!is_array($currentEnv)) { - $currentEnv = []; - } - $env = array_merge($currentEnv, [ - 'HOME' => $this->project_dir, - 'COMPOSER_HOME' => $this->project_dir . '/var/composer', - 'PATH' => getenv('PATH') ?: '/usr/local/bin:/usr/bin:/bin', - ]); - $process->setEnv($env); - - $output = ''; - $process->run(function ($type, $buffer) use (&$output) { - $output .= $buffer; - }); - - if (!$process->isSuccessful()) { - $errorOutput = $process->getErrorOutput() ?: $process->getOutput(); - throw new \RuntimeException( - sprintf('%s failed: %s', $description, trim($errorOutput)) - ); - } - - return $output; + return $this->commandRunHelper->runCommand($command, $description, $timeout); } /**