Passed
Push — dbal ( be5115...7a7bb0 )
by Greg
10:42
created

UpdateDatabaseSchema   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 75
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 34
dl 0
loc 75
rs 10
c 0
b 0
f 0
wmc 6

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A process() 0 55 5
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2023 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\Middleware;
21
22
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
23
use Fisharebest\Webtrees\DB;
24
use Fisharebest\Webtrees\DB\WebtreesSchema;
25
use Fisharebest\Webtrees\Services\MigrationService;
26
use Fisharebest\Webtrees\Webtrees;
27
use PDO;
28
use Psr\Http\Message\ResponseInterface;
29
use Psr\Http\Message\ServerRequestInterface;
30
use Psr\Http\Server\MiddlewareInterface;
31
use Psr\Http\Server\RequestHandlerInterface;
32
use Throwable;
33
34
use function array_map;
35
use function implode;
36
use function microtime;
37
use function str_contains;
38
use function usort;
39
40
/**
41
 * Middleware to update the database automatically, after an upgrade.
42
 */
43
class UpdateDatabaseSchema implements MiddlewareInterface
44
{
45
    private MigrationService $migration_service;
46
47
    /**
48
     * @param MigrationService $migration_service
49
     */
50
    public function __construct(MigrationService $migration_service)
51
    {
52
        $this->migration_service = $migration_service;
53
    }
54
55
    /**
56
     * Update the database schema, if necessary.
57
     *
58
     * @param ServerRequestInterface  $request
59
     * @param RequestHandlerInterface $handler
60
     *
61
     * @return ResponseInterface
62
     */
63
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
64
    {
65
        $this->migration_service
66
            ->updateSchema('\Fisharebest\Webtrees\Schema', 'WT_SCHEMA_VERSION', Webtrees::SCHEMA_VERSION);
67
68
        $platform = DB::getDBALConnection()->getDatabasePlatform();
69
        $platform->registerDoctrineTypeMapping(dbType: 'enum', doctrineType: 'string');
70
71
        $schema_manager = DB::getDBALConnection()->createSchemaManager();
72
        $comparator     = $schema_manager->createComparator();
73
        $source         = $schema_manager->introspectSchema();
74
        $target         = WebtreesSchema::schema();
75
76
        // doctrine/dbal 4.0 does not have the concept of "saveSQL"
77
        foreach ($source->getTables() as $table) {
78
            if (!$target->hasTable($table->getName())) {
79
                $source->dropTable($table->getName());
80
            }
81
        }
82
83
        $schema_diff = $comparator->compareSchemas(oldSchema: $source, newSchema: $target);
84
        $queries     = $platform->getAlterSchemaSQL(diff: $schema_diff);
85
86
        // Workaround for https://github.com/doctrine/dbal/issues/6092
87
        $phase = static fn (string $query): int => match (true) {
88
            str_contains(haystack: $query, needle: 'DROP FOREIGN KEY') => 1,
89
            default                                                    => 2,
90
            str_contains(haystack: $query, needle: 'FOREIGN KEY')      => 3,
91
        };
92
        $fn = static fn (string $query1, string $query2): int => $phase(query: $query1) <=> $phase(query: $query2);
93
        usort(array: $queries, callback: $fn);
94
95
        // SQLite, PostgreSQL and SQL-Server all support DDL in transactions
96
        if (DB::getDBALConnection()->getDriver() instanceof AbstractMySQLDriver) {
97
            $queries = [
98
                'SET FOREIGN_KEY_CHECKS := 0',
99
                ...$queries,
100
                'SET FOREIGN_KEY_CHECKS := 1',
101
            ];
102
        } else {
103
            $queries = [
104
                'START TRANSACTION',
105
                ...$queries,
106
                'COMMIT',
107
            ];
108
        }
109
110
111
        foreach ($queries as $query) {
112
            DB::getDBALConnection()->executeStatement(sql: $query);
113
        }
114
115
        //exit;
116
117
        return $handler->handle($request);
118
    }
119
}
120