Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
BackupManager
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
4 / 4
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDbUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 runBackup
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getBackupCommand
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2
3declare(strict_types=1);
4
5namespace App\Service;
6
7use App\Enum\DbDrivers;
8use RuntimeException;
9use Symfony\Component\DependencyInjection\Attribute\Autowire;
10use Symfony\Component\Process\Exception\ProcessFailedException;
11use Symfony\Component\Process\Process;
12use ValueError;
13
14readonly class BackupManager
15{
16    public function __construct(
17        #[Autowire(env: 'DATABASE_URL')] private string $databaseUrl,
18    ) {}
19
20    public function getDbUrl(): string
21    {
22        return $this->databaseUrl;
23    }
24
25    /**
26     * @throws ProcessFailedException
27     */
28    public function runBackup(string $backupFile): void
29    {
30        $process = Process::fromShellCommandline(
31            $this->getBackupCommand().' > '.escapeshellarg($backupFile)
32        );
33        $process->setTimeout(300);
34        $process->mustRun();
35    }
36
37    public function getBackupCommand(): string
38    {
39        if ($this->databaseUrl === '' || $this->databaseUrl === '0') {
40            throw new RuntimeException('No DATABASE_URL found in environment');
41        }
42
43        $parts = parse_url($this->databaseUrl);
44        if ($parts === false) {
45            throw new RuntimeException('Invalid DATABASE_URL format');
46        }
47
48        $scheme = $parts['scheme'] ?? '';
49
50        try {
51            $dbDriver = DbDrivers::from($scheme);
52        } catch (ValueError) {
53            throw new RuntimeException('Invalid DB driver');
54        }
55
56        $dbUser = $parts['user'] ?? '';
57        $dbPass = $parts['pass'] ?? '';
58        $dbHost = $parts['host'] ?? '';
59        $dbPort = $parts['port'] ?? '';
60        $dbName = ltrim($parts['path'] ?? '', '/');
61
62        return match ($dbDriver) {
63            DbDrivers::PostgreSQL => sprintf(
64                '%s pg_dump -h %s -p %s -U %s %s',
65                sprintf('PGPASSWORD=%s', escapeshellarg($dbPass)),
66                escapeshellarg($dbHost),
67                escapeshellarg((string) $dbPort),
68                escapeshellarg($dbUser),
69                escapeshellarg($dbName)
70            ),
71            DbDrivers::MySQL => sprintf(
72                'mysqldump --no-tablespaces -h%s -P%s -u%s -p%s %s',
73                escapeshellarg($dbHost),
74                escapeshellarg((string) $dbPort),
75                escapeshellarg($dbUser),
76                escapeshellarg($dbPass),
77                escapeshellarg($dbName)
78            ),
79            DbDrivers::MariaDB => sprintf(
80                'mariadb-dump --no-tablespaces -h%s -P%s -u%s -p%s %s',
81                escapeshellarg($dbHost),
82                escapeshellarg((string) $dbPort),
83                escapeshellarg($dbUser),
84                escapeshellarg($dbPass),
85                escapeshellarg($dbName)
86            ),
87        };
88    }
89}