Failed Conditions
Push — master ( 2fedf2...196d3a )
by Adrien
02:27
created

AbstractDatabase::loadRemoteData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 8
ccs 0
cts 7
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Service;
6
7
use Exception;
8
9
/**
10
 * Tool to reload the entire local database from remote database for a given site
11
 *
12
 * Requirements:
13
 *
14
 * - ssh access to remote server (via ~/.ssh/config)
15
 * - both local and remote sites must be accessible via: /sites/MY_SITE
16
 */
17
abstract class AbstractDatabase
18
{
19
    /**
20
     * Dump data from database on $remote server
21
     */
22
    private static function dumpDataRemotely(string $remote, string $dumpFile): void
23
    {
24
        $sshCmd = <<<STRING
25
                    ssh $remote "cd /sites/$remote/ && php7.4 bin/dump-data.php $dumpFile"
26
STRING;
27
28
        echo "dumping data $dumpFile on $remote...\n";
29
        self::executeLocalCommand($sshCmd);
30
    }
31
32
    /**
33
     * Dump data from database
34
     */
35
    final public static function dumpData(string $dumpFile): void
36
    {
37
        $mysqlArgs = self::getMysqlArgs();
38
39
        echo "dumping $dumpFile...\n";
40
        $dumpCmd = "mysqldump -v $mysqlArgs | sed 's/DEFINER=[^*]*\\*/\\*/g' | gzip > $dumpFile";
41
        self::executeLocalCommand($dumpCmd);
42
    }
43
44
    /**
45
     * Copy a file from $remote
46
     */
47
    private static function copyFile(string $remote, string $dumpFile): void
48
    {
49
        $copyCmd = <<<STRING
50
                    rsync -avz --progress $remote:$dumpFile $dumpFile
51
STRING;
52
53
        echo "copying dump to $dumpFile ...\n";
54
        self::executeLocalCommand($copyCmd);
55
    }
56
57
    /**
58
     * Load SQL dump in local database
59
     */
60
    final public static function loadData(string $dumpFile): void
61
    {
62
        $mysqlArgs = self::getMysqlArgs();
63
        $dumpFile = self::absolutePath($dumpFile);
64
65
        echo "loading dump $dumpFile...\n";
66
        $database = self::getDatabaseName();
67
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine orm:schema-tool:drop --ansi --full-database --force');
68
        self::executeLocalCommand("gunzip -c \"$dumpFile\" | sed  's/ALTER DATABASE `[^`]*`/ALTER DATABASE `$database`/g' | mysql $mysqlArgs");
69
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine-migrations --ansi migrations:migrate --no-interaction');
70
        self::loadTriggers();
71
        self::loadTestUsers();
72
    }
73
74
    private static function getDatabaseName(): string
75
    {
76
        $dbConfig = _em()->getConnection()->getParams();
77
78
        return $dbConfig['dbname'];
79
    }
80
81
    private static function getMysqlArgs(): string
82
    {
83
        $dbConfig = _em()->getConnection()->getParams();
84
85
        $host = $dbConfig['host'] ?? 'localhost';
86
        $username = $dbConfig['user'];
87
        $database = $dbConfig['dbname'];
88
        $password = $dbConfig['password'];
89
        $port = $dbConfig['port'] ?? 3306;
90
91
        // It's possible to have no password at all
92
        $password = $password ? '-p' . $password : '';
93
94
        return "--user=$username $password --host=$host --port=$port $database";
95
    }
96
97
    final public static function loadRemoteData(string $remote): void
98
    {
99
        $dumpFile = "/tmp/$remote." . exec('whoami') . '.backup.sql.gz';
100
        self::dumpDataRemotely($remote, $dumpFile);
101
        self::copyFile($remote, $dumpFile);
102
        self::loadData($dumpFile);
103
104
        echo "database updated\n";
105
    }
106
107
    /**
108
     * Execute a shell command and throw exception if fails
109
     */
110
    final public static function executeLocalCommand(string $command): void
111
    {
112
        $return_var = null;
113
        $fullCommand = "$command 2>&1";
114
        passthru($fullCommand, $return_var);
115
        if ($return_var) {
116
            throw new Exception('FAILED executing: ' . $command);
117
        }
118
    }
119
120
    /**
121
     * Load test data
122
     */
123
    final public static function loadTestData(): void
124
    {
125
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine orm:schema-tool:drop --ansi --full-database --force');
126
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine-migrations migrations:migrate --ansi --no-interaction');
127
        self::loadTriggers();
128
        self::loadTestUsers();
129
        self::importFile('tests/data/fixture.sql');
130
    }
131
132
    /**
133
     * Load triggers
134
     */
135
    final public static function loadTriggers(): void
136
    {
137
        self::importFile('data/triggers.sql');
138
    }
139
140
    /**
141
     * Load test users
142
     */
143
    private static function loadTestUsers(): void
144
    {
145
        self::importFile('tests/data/users.sql');
146
    }
147
148
    /**
149
     * Import a SQL file into DB
150
     *
151
     * This use mysql command, instead of DBAL methods, to allow to see errors if any, and
152
     * also because it seems trigger creation do not work with DBAL for some unclear reasons.
153
     */
154
    final public static function importFile(string $file): void
155
    {
156
        $file = self::absolutePath($file);
157
        $mysqlArgs = self::getMysqlArgs();
158
159
        echo 'importing ' . $file . "\n";
160
161
        $importCommand = "echo 'SET NAMES utf8mb4;' | cat - $file | mysql $mysqlArgs";
162
163
        self::executeLocalCommand($importCommand);
164
    }
165
166
    private static function absolutePath(string $file): string
167
    {
168
        $absolutePath = realpath($file);
169
        if ($absolutePath === false) {
170
            throw new Exception('Cannot find absolute path for file: ' . $file);
171
        }
172
173
        if (!is_readable($absolutePath)) {
174
            throw new Exception("Cannot read dump file \"$absolutePath\"");
175
        }
176
177
        return $absolutePath;
178
    }
179
}
180