Skip to content

Commit 4e952e9

Browse files
committed
refactor: simplified Backup class
1 parent b508adb commit 4e952e9

File tree

8 files changed

+416
-115
lines changed

8 files changed

+416
-115
lines changed

phpmyfaq/src/phpMyFAQ/Administration/Backup.php

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@
2020
namespace phpMyFAQ\Administration;
2121

2222
use DateTimeImmutable;
23+
use phpMyFAQ\Administration\Backup\BackupExecuteResult;
24+
use phpMyFAQ\Administration\Backup\BackupExportResult;
25+
use phpMyFAQ\Administration\Backup\BackupParseResult;
26+
use phpMyFAQ\Administration\Backup\BackupRepository;
2327
use phpMyFAQ\Configuration;
2428
use phpMyFAQ\Core\Exception;
2529
use phpMyFAQ\Database;
2630
use phpMyFAQ\Database\DatabaseHelper;
2731
use phpMyFAQ\Enums\BackupType;
32+
use phpMyFAQ\Strings;
2833
use RecursiveDirectoryIterator;
2934
use RecursiveIteratorIterator;
3035
use SodiumException;
@@ -64,10 +69,13 @@ public function getLastBackupInfo(): array
6469

6570
if ($lastBackup !== null && isset($lastBackup->created)) {
6671
$createdRaw = (string) $lastBackup->created;
67-
$createdDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $createdRaw) ?: null;
72+
$createdDate = DateTimeImmutable::createFromFormat(
73+
format: 'Y-m-d H:i:s',
74+
datetime: $createdRaw,
75+
) ?: null;
6876
if ($createdDate !== null) {
69-
$lastBackupDateFormatted = $createdDate->format('Y-m-d H:i:s');
70-
$threshold = new DateTimeImmutable('-30 days');
77+
$lastBackupDateFormatted = $createdDate->format(format: 'Y-m-d H:i:s');
78+
$threshold = new DateTimeImmutable(datetime: '-30 days');
7179
$isBackupOlderThan30Days = $createdDate < $threshold;
7280
} else {
7381
$isBackupOlderThan30Days = true;
@@ -196,7 +204,15 @@ public function generateBackupQueries(string $tableNames): string
196204
private function getBackupHeader(string $tableNames): array
197205
{
198206
return [
199-
sprintf('-- pmf%s: %s', substr($this->configuration->getVersion(), 0, 3), $tableNames),
207+
sprintf(
208+
'-- pmf%s: %s',
209+
substr(
210+
string: $this->configuration->getVersion(),
211+
offset: 0,
212+
length: 3,
213+
),
214+
$tableNames,
215+
),
200216
'-- DO NOT REMOVE THE FIRST LINE!',
201217
'-- pmftableprefix: ' . Database::getTablePrefix(),
202218
'-- DO NOT REMOVE THE LINES ABOVE!',
@@ -238,6 +254,157 @@ public function createContentFolderBackup(): string
238254
return $zipFile;
239255
}
240256

257+
/**
258+
* Creates a backup for the given type and returns filename + content.
259+
*
260+
* @throws SodiumException
261+
* @throws \Exception
262+
*
263+
*/
264+
public function export(BackupType $type): BackupExportResult
265+
{
266+
$tableNames = $this->getBackupTableNames($type);
267+
268+
$backupContent = $this->generateBackupQueries($tableNames);
269+
270+
$fileName = $this->createBackup($type->value, $backupContent);
271+
272+
return new BackupExportResult($fileName, $backupContent);
273+
}
274+
275+
/**
276+
* Parses a backup file, checks the version and creates SQL queries + table prefix.
277+
*
278+
* @throws Exception
279+
*/
280+
public function parseBackupFile(string $filePath, string $currentVersion): BackupParseResult
281+
{
282+
$handle = fopen($filePath, mode: 'r');
283+
if (false === $handle) {
284+
throw new Exception(message: sprintf('Cannot open backup file "%s".', $filePath));
285+
}
286+
287+
$firstLine = fgets($handle, length: 65536);
288+
if (false === $firstLine) {
289+
fclose($handle);
290+
throw new Exception(message: 'Empty backup file.');
291+
}
292+
293+
$versionFound = Strings::substr(
294+
string: $firstLine,
295+
start: 0,
296+
length: 9,
297+
);
298+
299+
$versionExpected = '-- pmf'
300+
. substr(
301+
string: $currentVersion,
302+
offset: 0,
303+
length: 3,
304+
);
305+
306+
// Tabellen aus der ersten Zeile extrahieren
307+
$tablesLine = trim(Strings::substr(
308+
string: $firstLine,
309+
start: 11,
310+
));
311+
$tables = explode(
312+
separator: ' ',
313+
string: $tablesLine,
314+
);
315+
316+
$queries = [];
317+
foreach ($tables as $tableName) {
318+
if ('' === $tableName) {
319+
continue;
320+
}
321+
322+
$queries[] = sprintf('DELETE FROM %s', $tableName);
323+
}
324+
325+
$tablePrefix = '';
326+
327+
while ($line = fgets($handle, length: 65536)) {
328+
$line = trim($line);
329+
$backupPrefixPattern = '-- pmftableprefix:';
330+
$backupPrefixPatternLength = Strings::strlen($backupPrefixPattern);
331+
332+
if (
333+
Strings::substr(
334+
string: $line,
335+
start: 0,
336+
length: $backupPrefixPatternLength,
337+
) === $backupPrefixPattern
338+
) {
339+
$tablePrefix = trim(Strings::substr($line, $backupPrefixPatternLength));
340+
341+
continue;
342+
}
343+
344+
if (
345+
Strings::substr(
346+
string: $line,
347+
start: 0,
348+
length: 2,
349+
) !== '--'
350+
&& $line !== ''
351+
) {
352+
$queries[] = trim(Strings::substr(
353+
string: $line,
354+
start: 0,
355+
length: -1,
356+
));
357+
}
358+
}
359+
360+
fclose($handle);
361+
362+
$versionMatches = $versionFound === $versionExpected;
363+
364+
return new BackupParseResult(
365+
versionMatches: $versionMatches,
366+
versionFound: $versionFound,
367+
versionExpected: $versionExpected,
368+
queries: $queries,
369+
tablePrefix: $tablePrefix,
370+
);
371+
}
372+
373+
/**
374+
* Executes the given backup queries with the correct table prefix.
375+
*/
376+
public function executeBackupQueries(array $queries, string $tablePrefix): BackupExecuteResult
377+
{
378+
$db = $this->configuration->getDb();
379+
380+
$ok = 0;
381+
$failed = 0;
382+
$lastErrorQuery = null;
383+
$lastErrorReason = null;
384+
385+
foreach ($queries as $query) {
386+
$alignedQuery = $this->databaseHelper::alignTablePrefix($query, $tablePrefix, Database::getTablePrefix());
387+
388+
$result = $db->query($alignedQuery);
389+
if (!$result) {
390+
++$failed;
391+
$lastErrorQuery = $alignedQuery;
392+
$lastErrorReason = $db->error();
393+
394+
continue;
395+
}
396+
397+
++$ok;
398+
}
399+
400+
return new BackupExecuteResult(
401+
queriesOk: $ok,
402+
queriesFailed: $failed,
403+
lastErrorQuery: $lastErrorQuery,
404+
lastErrorReason: $lastErrorReason,
405+
);
406+
}
407+
241408
private function getRepository(): BackupRepository
242409
{
243410
return $this->repository;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/**
4+
* Backup execute result DTO.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2025-12-01
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace phpMyFAQ\Administration\Backup;
21+
22+
final readonly class BackupExecuteResult
23+
{
24+
public function __construct(
25+
public int $queriesOk,
26+
public int $queriesFailed,
27+
public ?string $lastErrorQuery = null,
28+
public ?string $lastErrorReason = null,
29+
) {
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/**
4+
* Backup export result DTO.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2025-12-01
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace phpMyFAQ\Administration\Backup;
21+
22+
final readonly class BackupExportResult
23+
{
24+
public function __construct(
25+
public string $fileName,
26+
public string $content,
27+
) {
28+
}
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/**
4+
* Backup parsed result DTO.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2025-12-01
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace phpMyFAQ\Administration\Backup;
21+
22+
final readonly class BackupParseResult
23+
{
24+
/**
25+
* @param string[] $queries
26+
*/
27+
public function __construct(
28+
public bool $versionMatches,
29+
public string $versionFound,
30+
public string $versionExpected,
31+
public array $queries,
32+
public string $tablePrefix,
33+
) {
34+
}
35+
}

phpmyfaq/src/phpMyFAQ/Administration/BackupRepository.php renamed to phpmyfaq/src/phpMyFAQ/Administration/Backup/BackupRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
declare(strict_types=1);
1919

20-
namespace phpMyFAQ\Administration;
20+
namespace phpMyFAQ\Administration\Backup;
2121

2222
use phpMyFAQ\Configuration;
2323
use phpMyFAQ\Database;

0 commit comments

Comments
 (0)