Passed
Pull Request — master (#22)
by Maarten
03:11
created

DatabaseSynchronizer::__construct()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 2
nc 3
nop 3
1
<?php
2
3
namespace mtolhuijs\LDS;
4
5
use Illuminate\Support\Facades\DB;
6
use Illuminate\Support\Facades\Schema;
7
use Illuminate\Database\Schema\Blueprint;
8
use Illuminate\Database\ConnectionInterface;
9
use mtolhuijs\LDS\Exceptions\DatabaseConnectionException;
10
11
class DatabaseSynchronizer
12
{
13
    public const DEFAULT_LIMIT = 5000;
14
15
    public $cli;
16
    public $limit = self::DEFAULT_LIMIT;
17
    public $tables;
18
    public $skipTables;
19
    public $from;
20
    public $to;
21
    public $truncate;
22
23
    private $fromDB;
24
    private $toDB;
25
26
    public function __construct(string $from, string $to, $cli = false)
27
    {
28
        $this->from = $from;
29
        $this->to = $to;
30
        $this->cli = $cli;
31
32
        try {
33
            $this->fromDB = DB::connection($this->from);
34
            $this->toDB = DB::connection($this->to);
35
        } catch (\Exception $e) {
36
            throw new DatabaseConnectionException($e->getMessage());
37
        }
38
    }
39
40
    public function setSkipTables(array $skipTables)
41
    {
42
        $this->skipTables = $skipTables;
43
44
        return $this;
45
    }
46
47
    public function setTables(array $tables)
48
    {
49
        $this->tables = $tables;
50
51
        return $this;
52
    }
53
54
    public function setLimit(int $limit)
55
    {
56
        $this->limit = $limit;
57
58
        return $this;
59
    }
60
61
    public function setOptions(array $options)
62
    {
63
        foreach ($options as $option => $value) {
64
            if (! isset($this->{$option})) {
65
                $this->{$option} = $value;
66
            }
67
        }
68
69
        return $this;
70
    }
71
72
    protected function getFromDb(): ConnectionInterface
73
    {
74
        if ($this->fromDB === null) {
75
            $this->fromDB = DB::connection($this->from);
76
        }
77
78
        return $this->fromDB;
79
    }
80
81
    protected function getToDb(): ConnectionInterface
82
    {
83
        if ($this->toDB === null) {
84
            $this->toDB = DB::connection($this->to);
85
        }
86
87
        return $this->toDB;
88
    }
89
90
    public function getTables(): array
91
    {
92
        if (empty($this->tables)) {
93
            $this->tables = $this->getFromDb()->getDoctrineSchemaManager()->listTableNames();
94
        }
95
96
        return array_filter($this->tables, function ($table) {
97
            return ! in_array($table, $this->skipTables, true);
98
        });
99
    }
100
101
    public function run(): void
102
    {
103
        foreach ($this->getTables() as $table) {
104
            $this->feedback(PHP_EOL.PHP_EOL."Table: $table", 'line');
105
106
            if (! Schema::connection($this->from)->hasTable($table)) {
107
                $this->feedback("Table '$table' does not exist in $this->from", 'error');
108
109
                continue;
110
            }
111
112
            $this->syncTable($table);
113
            $this->syncRows($table);
114
        }
115
    }
116
117
    /**
118
     * Check if tables and columns are present
119
     * Create or update them if not.
120
     *
121
     * @param string $table
122
     */
123
    public function syncTable(string $table): void
124
    {
125
        $schema = Schema::connection($this->to);
126
        $columns = Schema::connection($this->from)->getColumnListing($table);
127
128
        if ($schema->hasTable($table)) {
129
            foreach ($columns as $column) {
130
                if ($schema->hasColumn($table, $column)) {
131
                    continue;
132
                }
133
134
                $this->updateTable($table, $column);
135
            }
136
137
            return;
138
        }
139
140
        $this->createTable($table, $columns);
141
    }
142
143
    /**
144
     * Fetch all rows in $this->from and insert or update $this->to.
145
     * @todo need to get the real primary key
146
     * @todo add limit offset setup
147
     * @todo investigate: insert into on duplicate key update
148
     *
149
     * @param string $table
150
     */
151
    public function syncRows(string $table): void
152
    {
153
        $queryColumn = Schema::connection($this->from)->getColumnListing($table)[0];
154
        $pdo = $this->getFromDb()->getPdo();
155
156
        $builder = $this->fromDB->table($table);
157
        $statement = $pdo->prepare($builder->toSql());
158
159
        if (! $statement instanceof  \PDOStatement) {
160
            return;
161
        }
162
163
        $statement->execute($builder->getBindings());
164
        $amount = $statement->rowCount();
165
166
        if ($this->cli) {
167
            if ($amount > 0) {
168
                $this->feedback("Synchronizing '$this->to.$table' rows", 'comment');
169
                $bar = $this->cli->getOutput()->createProgressBar($amount);
170
            } else {
171
                $this->feedback('No rows...', 'comment');
172
            }
173
        }
174
175
        if ($this->truncate) {
176
            $this->getToDb()->table($table)->truncate();
177
        }
178
179
        while ($row = $statement->fetch(\PDO::FETCH_OBJ)) {
180
            $exists = $this->getToDb()->table($table)->where($queryColumn, $row->{$queryColumn})->first();
181
182
            if (! $exists) {
183
                $this->getToDb()->table($table)->insert((array) $row);
184
            } else {
185
                $this->getToDb()->table($table)->where($queryColumn, $row->{$queryColumn})->update((array) $row);
186
            }
187
188
            if (isset($bar)) {
189
                $bar->advance();
190
            }
191
        }
192
193
        if (isset($bar)) {
194
            $bar->finish();
195
        }
196
    }
197
198
    private function createTable(string $table, array $columns): void
199
    {
200
        $this->feedback("Creating '$this->to.$table' table", 'warn');
201
202
        Schema::connection($this->to)->create($table, function (Blueprint $table_bp) use ($table, $columns) {
203
            foreach ($columns as $column) {
204
                $type = Schema::connection($this->from)->getColumnType($table, $column);
205
206
                $table_bp->{$type}($column)->nullable();
207
208
                $this->feedback("Added {$type}('$column')->nullable()");
209
            }
210
        });
211
    }
212
213
    private function updateTable(string $table, string $column): void
214
    {
215
        Schema::connection($this->to)->table($table, function (Blueprint $table_bp) use ($table, $column) {
216
            $type = Schema::connection($this->from)->getColumnType($table, $column);
217
218
            $table_bp->{$type}($column)->nullable();
219
220
            $this->feedback("Added {$type}('$column')->nullable()");
221
        });
222
    }
223
224
    private function feedback(string $msg, $type = 'info'): void
225
    {
226
        if ($this->cli) {
227
            $this->cli->{$type}($msg);
228
        } else {
229
            echo PHP_EOL.$msg.PHP_EOL;
230
        }
231
    }
232
}
233