Failed Conditions
Push — master ( 4438ef...2738ac )
by Adrien
02:29
created

AbstractDatabase::absolutePath()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 12
ccs 0
cts 10
cp 0
crap 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Service;
6
7
/**
8
 * Tool to reload the entire local database from remote database for a given site
9
 *
10
 * Requirements:
11
 *
12
 * - ssh access to remote server (via ~/.ssh/config)
13
 * - both local and remote sites must be accesible via: /sites/MY_SITE
14
 * - both local and remote config/autoload/local.php files must contains the database connection info
15
 */
16
abstract class AbstractDatabase
17
{
18
    /**
19
     * Dump data from database on $remote server
20
     */
21
    private static function dumpDataRemotely(string $remote, string $dumpFile): void
22
    {
23
        $sshCmd = <<<STRING
24
        ssh $remote "cd /sites/$remote/ && php7.4 bin/dump-data.php $dumpFile"
25
STRING;
26
27
        echo "dumping data $dumpFile on $remote...\n";
28
        self::executeLocalCommand($sshCmd);
29
    }
30
31
    /**
32
     * Dump data from database
33
     */
34
    public static function dumpData(string $dumpFile): void
35
    {
36
        $config = require 'config/autoload/local.php';
37
        $dbConfig = $config['doctrine']['connection']['orm_default']['params'];
38
        $host = $dbConfig['host'];
39
        $username = $dbConfig['user'];
40
        $database = $dbConfig['dbname'];
41
        $password = $dbConfig['password'];
42
43
        echo "dumping $dumpFile...\n";
44
        $dumpCmd = "mysqldump -v --user=$username --password=$password --host=$host $database | sed 's/DEFINER=[^*]*\\*/\\*/g' | gzip > $dumpFile";
45
        self::executeLocalCommand($dumpCmd);
46
    }
47
48
    /**
49
     * Copy a file from $remote
50
     */
51
    private static function copyFile(string $remote, string $dumpFile): void
52
    {
53
        $copyCmd = <<<STRING
54
        rsync -avz --progress $remote:$dumpFile $dumpFile
55
STRING;
56
57
        echo "copying dump to $dumpFile ...\n";
58
        self::executeLocalCommand($copyCmd);
59
    }
60
61
    /**
62
     * Load SQL dump in local database
63
     */
64
    public static function loadData(string $dumpFile): void
65
    {
66
        $config = require 'config/autoload/local.php';
67
        $dbConfig = $config['doctrine']['connection']['orm_default']['params'];
68
        $host = $dbConfig['host'];
69
        $username = $dbConfig['user'];
70
        $database = $dbConfig['dbname'];
71
        $password = $dbConfig['password'];
72
73
        $dumpFile = self::absolutePath($dumpFile);
74
75
        echo "loading dump $dumpFile...\n";
76
77
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine orm:schema-tool:drop --ansi --full-database --force');
78
        self::executeLocalCommand("gunzip -c \"$dumpFile\" | mysql --user=$username --password=$password --host=$host $database");
79
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine-migrations --ansi migrations:migrate --no-interaction');
80
        self::loadTriggers();
81
        self::loadTestUsers();
82
    }
83
84
    public static function loadRemoteData(string $remote): void
85
    {
86
        $dumpFile = "/tmp/$remote." . exec('whoami') . '.backup.sql.gz';
87
        self::dumpDataRemotely($remote, $dumpFile);
88
        self::copyFile($remote, $dumpFile);
89
        self::loadData($dumpFile);
90
91
        echo "database updated\n";
92
    }
93
94
    /**
95
     * Execute a shell command and throw exception if fails
96
     */
97
    public static function executeLocalCommand(string $command): void
98
    {
99
        $return_var = null;
100
        $fullCommand = "$command 2>&1";
101
        passthru($fullCommand, $return_var);
102
        if ($return_var) {
103
            throw new \Exception('FAILED executing: ' . $command);
104
        }
105
    }
106
107
    /**
108
     * Load test data
109
     */
110
    public static function loadTestData(): void
111
    {
112
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine orm:schema-tool:drop --ansi --full-database --force');
113
        self::executeLocalCommand(PHP_BINARY . ' ./vendor/bin/doctrine-migrations migrations:migrate --ansi --no-interaction');
114
        self::loadTriggers();
115
        self::loadTestUsers();
116
        self::importFile('tests/data/fixture.sql');
117
    }
118
119
    /**
120
     * Load triggers
121
     */
122
    public static function loadTriggers(): void
123
    {
124
        self::importFile('data/triggers.sql');
125
    }
126
127
    /**
128
     * Load test users
129
     */
130
    private static function loadTestUsers(): void
131
    {
132
        self::importFile('tests/data/users.sql');
133
    }
134
135
    /**
136
     * Import a SQL file into DB
137
     *
138
     * This use mysql command, instead of DBAL methods, to allow to see errors if any, and
139
     * also because it seems trigger creation do not work with DBAL for some unclear reasons.
140
     */
141
    private static function importFile(string $file): void
142
    {
143
        $file = self::absolutePath($file);
144
145
        echo 'importing ' . $file . "\n";
146
        $connection = _em()->getConnection();
147
        $database = $connection->getDatabase();
148
        $username = $connection->getUsername();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::getUsername() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

148
        $username = /** @scrutinizer ignore-deprecated */ $connection->getUsername();
Loading history...
149
        $password = empty($connection->getPassword()) ? '' : '-p' . $connection->getPassword();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::getPassword() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

149
        $password = empty(/** @scrutinizer ignore-deprecated */ $connection->getPassword()) ? '' : '-p' . $connection->getPassword();
Loading history...
150
151
        $importCommand = "cat $file | mysql -u $username $password $database";
152
153
        self::executeLocalCommand($importCommand);
154
    }
155
156
    private static function absolutePath(string $file): string
157
    {
158
        $absolutePath = realpath($file);
159
        if ($absolutePath === false) {
160
            throw new \Exception('Cannot find absolute path for file: ' . $file);
161
        }
162
163
        if (!is_readable($absolutePath)) {
164
            throw new \Exception("Cannot read dump file \"$absolutePath\"");
165
        }
166
167
        return $absolutePath;
168
    }
169
}
170