Passed
Push — master ( 89f24e...8c4dad )
by Sylvain
08:05
created

AbstractDatabase   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 59
c 4
b 0
f 0
dl 0
loc 154
ccs 0
cts 96
cp 0
rs 10
wmc 16

12 Methods

Rating   Name   Duplication   Size   Complexity  
A executeLocalCommand() 0 7 2
A loadRemoteData() 0 8 1
A loadTestUsers() 0 3 1
A loadTriggers() 0 3 1
A loadTestData() 0 7 1
A loadData() 0 12 1
A importFile() 0 10 1
A dumpDataRemotely() 0 8 1
A absolutePath() 0 12 3
A getMysqlArgs() 0 14 2
A dumpData() 0 7 1
A copyFile() 0 8 1
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 accesible 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
    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
    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
67
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine orm:schema-tool:drop --ansi --full-database --force');
68
        self::executeLocalCommand("gunzip -c \"$dumpFile\" | 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 getMysqlArgs(): string
75
    {
76
        $dbConfig = _em()->getConnection()->getParams();
77
78
        $host = $dbConfig['host'] ?? 'localhost';
79
        $username = $dbConfig['user'];
80
        $database = $dbConfig['dbname'];
81
        $password = $dbConfig['password'];
82
        $port = $dbConfig['port'] ?? 3306;
83
84
        // It's possible to have no password at all
85
        $password = $password ? '-p' . $password : '';
86
87
        return "--user=$username $password --host=$host --port=$port $database";
88
    }
89
90
    public static function loadRemoteData(string $remote): void
91
    {
92
        $dumpFile = "/tmp/$remote." . exec('whoami') . '.backup.sql.gz';
93
        self::dumpDataRemotely($remote, $dumpFile);
94
        self::copyFile($remote, $dumpFile);
95
        self::loadData($dumpFile);
96
97
        echo "database updated\n";
98
    }
99
100
    /**
101
     * Execute a shell command and throw exception if fails
102
     */
103
    public static function executeLocalCommand(string $command): void
104
    {
105
        $return_var = null;
106
        $fullCommand = "$command 2>&1";
107
        passthru($fullCommand, $return_var);
108
        if ($return_var) {
109
            throw new Exception('FAILED executing: ' . $command);
110
        }
111
    }
112
113
    /**
114
     * Load test data
115
     */
116
    public static function loadTestData(): void
117
    {
118
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine orm:schema-tool:drop --ansi --full-database --force');
119
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine-migrations migrations:migrate --ansi --no-interaction');
120
        self::loadTriggers();
121
        self::loadTestUsers();
122
        self::importFile('tests/data/fixture.sql');
123
    }
124
125
    /**
126
     * Load triggers
127
     */
128
    public static function loadTriggers(): void
129
    {
130
        self::importFile('data/triggers.sql');
131
    }
132
133
    /**
134
     * Load test users
135
     */
136
    private static function loadTestUsers(): void
137
    {
138
        self::importFile('tests/data/users.sql');
139
    }
140
141
    /**
142
     * Import a SQL file into DB
143
     *
144
     * This use mysql command, instead of DBAL methods, to allow to see errors if any, and
145
     * also because it seems trigger creation do not work with DBAL for some unclear reasons.
146
     */
147
    private static function importFile(string $file): void
148
    {
149
        $file = self::absolutePath($file);
150
        $mysqlArgs = self::getMysqlArgs();
151
152
        echo 'importing ' . $file . "\n";
153
154
        $importCommand = "cat $file | mysql $mysqlArgs";
155
156
        self::executeLocalCommand($importCommand);
157
    }
158
159
    private static function absolutePath(string $file): string
160
    {
161
        $absolutePath = realpath($file);
162
        if ($absolutePath === false) {
163
            throw new Exception('Cannot find absolute path for file: ' . $file);
164
        }
165
166
        if (!is_readable($absolutePath)) {
167
            throw new Exception("Cannot read dump file \"$absolutePath\"");
168
        }
169
170
        return $absolutePath;
171
    }
172
}
173