Passed
Push — master ( 5bf686...2c03a6 )
by Jan
05:18
created

BackupCommand::configureDumper()   B

Complexity

Conditions 8
Paths 64

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 12
c 1
b 0
f 0
nc 64
nop 2
dl 0
loc 24
rs 8.4444
1
<?php
2
3
namespace App\Command;
4
5
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
6
use Doctrine\ORM\EntityManagerInterface;
7
use PhpZip\Constants\ZipCompressionMethod;
8
use PhpZip\Exception\ZipException;
9
use PhpZip\ZipFile;
10
use Spatie\DbDumper\Databases\MySql;
11
use Spatie\DbDumper\DbDumper;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\Input;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Console\Style\SymfonyStyle;
19
20
class BackupCommand extends Command
21
{
22
    protected static $defaultName = 'partdb:backup';
23
    protected static $defaultDescription = 'Backup the files and the database of Part-DB';
24
25
    private string $project_dir;
26
    private EntityManagerInterface $entityManager;
27
28
    public function __construct(string $project_dir, EntityManagerInterface $entityManager)
29
    {
30
        $this->project_dir = $project_dir;
31
        $this->entityManager = $entityManager;
32
33
        parent::__construct();
34
    }
35
36
    protected function configure(): void
37
    {
38
        $this->setHelp('This command allows you to backup the files and database of Part-DB.');
39
40
        $this
41
            ->addArgument('output', InputArgument::REQUIRED, 'The file to which the backup should be written')
42
            ->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite the output file, if it already exists without asking')
43
            ->addOption('database', null, InputOption::VALUE_NONE, 'Backup the database')
44
            ->addOption('attachments', null, InputOption::VALUE_NONE, 'Backup the attachments files')
45
            ->addOption('config', null, InputOption::VALUE_NONE, 'Backup the config files')
46
            ->addOption('full', null, InputOption::VALUE_NONE, 'Backup database, attachments and config files')
47
        ;
48
    }
49
50
    protected function execute(InputInterface $input, OutputInterface $output): int
51
    {
52
        $io = new SymfonyStyle($input, $output);
53
        $output_filepath = $input->getArgument('output');
54
        $backup_database = $input->getOption('database');
55
        $backup_attachments = $input->getOption('attachments');
56
        $backup_config = $input->getOption('config');
57
        $backup_full = $input->getOption('full');
58
59
        if ($backup_full) {
60
            $backup_database = true;
61
            $backup_attachments = true;
62
            $backup_config = true;
63
        }
64
65
        //When all options are false, we abort and show an error message
66
        if (! $backup_database && ! $backup_attachments && ! $backup_config) {
67
            $io->error('You have to select at least one option what to backup! Use --full to backup everything.');
68
69
            return Command::FAILURE;
70
        }
71
72
        $io->info('Backup Part-DB to '.$output_filepath);
73
74
        //Check if the file already exists
75
        if (file_exists($output_filepath)) {
76
            //Then ask the user, if he wants to overwrite the file
77
            if (!$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) {
78
                $io->error('Backup aborted!');
79
80
                return Command::FAILURE;
81
            }
82
        }
83
84
        $io->note('Starting backup...');
85
86
        //Open ZIP file
87
        $zip = new ZipFile();
88
89
        if ($backup_config) {
90
            $this->backupConfig($zip, $io);
91
        }
92
        if ($backup_attachments) {
93
            $this->backupAttachments($zip, $io);
94
        }
95
        if ($backup_database) {
96
            $this->backupDatabase($zip, $io);
97
        }
98
99
        $zip->setArchiveComment('Part-DB Backup of '.date('Y-m-d H:i:s'));
100
101
        //Write and close ZIP file
102
        try {
103
            $zip->saveAsFile($output_filepath);
104
        } catch (ZipException $e) {
105
            $io->error('Could not write ZIP file: '.$e->getMessage());
106
107
            return Command::FAILURE;
108
        }
109
        $zip->close();
110
111
        $io->success('Backup finished! You can find the backup file at '.$output_filepath);
112
113
        return Command::SUCCESS;
114
    }
115
116
    /**
117
     * Constructs the MySQL PDO DSN.
118
     * Taken from https://github.com/doctrine/dbal/blob/3.5.x/src/Driver/PDO/MySQL/Driver.php
119
     *
120
     * @param mixed[] $params
121
     */
122
    private function configureDumper(array $params, DbDumper $dumper): void
123
    {
124
        if (isset($params['host']) && $params['host'] !== '') {
125
            $dumper->setHost($params['host']);
126
        }
127
128
        if (isset($params['port'])) {
129
            $dumper->setPort($params['port']);
130
        }
131
132
        if (isset($params['dbname'])) {
133
            $dumper->setDbName($params['dbname']);
134
        }
135
136
        if (isset($params['unix_socket'])) {
137
            $dumper->setSocket($params['unix_socket']);
138
        }
139
140
        if (isset($params['user'])) {
141
            $dumper->setUserName($params['user']);
142
        }
143
144
        if (isset($params['password'])) {
145
            $dumper->setPassword($params['password']);
146
        }
147
    }
148
149
    protected function backupDatabase(ZipFile $zip, SymfonyStyle $io): void
150
    {
151
        $io->note('Backup database...');
152
153
        //Determine if we use MySQL or SQLite
154
        $connection = $this->entityManager->getConnection();
155
        if ($connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
156
            try {
157
                $io->note('MySQL database detected. Dump DB to SQL using mysqldump...');
158
                $params = $connection->getParams();
159
                $dumper = MySql::create();
160
                $this->configureDumper($params, $dumper);
161
162
                $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump');
163
164
                $dumper->dumpToFile($tmp_file);
165
                $zip->addFile($tmp_file, 'mysql_dump.sql');
166
            } catch (\Exception $e) {
167
                $io->error('Could not dump database: '.$e->getMessage());
168
                $io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!');
169
            }
170
        } elseif ($connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) {
0 ignored issues
show
Bug introduced by
The type Doctrine\DBAL\Platforms\SqlitePlatform was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
171
            $io->note('SQLite database detected. Copy DB file to ZIP...');
172
            $params = $connection->getParams();
173
            $zip->addFile($params['path'], 'var/app.db');
174
        } else {
175
            $io->error('Unknown database platform. Could not backup database!');
176
        }
177
178
179
    }
180
181
    protected function backupConfig(ZipFile $zip, SymfonyStyle $io): void
182
    {
183
        $io->note('Backing up config files...');
184
185
        //Add .env.local file if it exists
186
        $env_local_filepath = $this->project_dir.'/.env.local';
187
        if (file_exists($env_local_filepath)) {
188
            $zip->addFile($env_local_filepath, '.env.local');
189
        } else {
190
            $io->warning('Could not find .env.local file. You maybe use env variables, then you have to backup them manually!!');
191
        }
192
193
        //Add config/parameters.yaml and config/banner.md files
194
        $config_dir = $this->project_dir.'/config';
195
        $zip->addFile($config_dir.'/parameters.yaml', 'config/parameters.yaml');
196
        $zip->addFile($config_dir.'/banner.md', 'config/banner.md');
197
    }
198
199
    protected function backupAttachments(ZipFile $zip, SymfonyStyle $io): void
200
    {
201
        $io->note('Backing up attachments files...');
202
203
        //Add public attachments directory
204
        $attachments_dir = $this->project_dir.'/public/media/';
205
        $zip->addDirRecursive($attachments_dir, 'public/media/', ZipCompressionMethod::DEFLATED);
206
207
        //Add private attachments directory
208
        $attachments_dir = $this->project_dir.'/uploads/';
209
        $zip->addDirRecursive($attachments_dir, 'uploads/', ZipCompressionMethod::DEFLATED);
210
    }
211
}
212