Passed
Push — master ( c64f36...f7b7ba )
by Maarten
04:15 queued 14s
created

DatabaseSynchronizer::run()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
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
9
class DatabaseSynchronizer
10
{
11
    public $cli;
12
    public $limit = 5000;
13
    public $tables;
14
    public $from;
15
    public $to;
16
17
    private $fromDB;
18
    private $toDB;
19
20
    public function __construct(string $from, string $to, $cli = false)
21
    {
22
        $this->from = $from;
23
        $this->to = $to;
24
25
        if ($cli) {
26
            $this->cli = $cli;
27
        }
28
29
        try {
30
            $this->fromDB = DB::connection($this->from);
31
            $this->toDB = DB::connection($this->to);
32
        } catch (\Exception $e) {
33
            $this->feedback($e->getMessage(), 'error');
34
35
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
36
        }
37
    }
38
39
    protected function getFromDb()
40
    {
41
        if ($this->fromDB === null) {
42
            $this->fromDB = DB::connection($this->from);
43
        }
44
45
        return $this->fromDB;
46
    }
47
48
    protected function getToDb()
49
    {
50
        if ($this->toDB === null) {
51
            $this->toDB = DB::connection($this->to);
52
        }
53
54
        return $this->toDB;
55
    }
56
57
    public function run(): void
58
    {
59
        foreach ($this->getTables() as $table) {
60
            $this->feedback(PHP_EOL.PHP_EOL."Table: $table", 'line');
61
62
            $this->syncTable($table);
63
            $this->syncRows($table);
64
        }
65
    }
66
67
    /**
68
     * Check if tables and columns are present
69
     * Create or update them if not.
70
     *
71
     * @param string $table
72
     */
73
    public function syncTable(string $table): void
74
    {
75
        $schema = Schema::connection($this->to);
76
        $columns = Schema::connection($this->from)->getColumnListing($table);
77
78
        if ($schema->hasTable($table)) {
79
            foreach ($columns as $column) {
80
                if ($schema->hasColumn($table, $column)) {
81
                    continue;
82
                }
83
84
                $this->updateTable($table, $column);
85
            }
86
87
            return;
88
        }
89
90
        $this->createTable($table, $columns);
91
    }
92
93
    /**
94
     * Fetch all rows in $this->from and insert or update $this->to.
95
     * @todo need to get the real primary key
96
     * @todo add limit offset setup
97
     * @todo investigate: insert into on duplicate key update
98
     *
99
     * @param string $table
100
     */
101
    public function syncRows(string $table): void
102
    {
103
        $queryColumn = $this->getFromDb()->getColumnListing($table)[0];
0 ignored issues
show
Bug introduced by
The method getColumnListing() does not exist on Illuminate\Database\ConnectionInterface. ( Ignorable by Annotation )

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

103
        $queryColumn = $this->getFromDb()->/** @scrutinizer ignore-call */ getColumnListing($table)[0];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
104
        $pdo = $this->getFromDb()->getPdo();
105
106
        $builder = $this->fromDB->table($table);
107
        $statement = $pdo->prepare($builder->toSql());
108
109
        if (! $statement instanceof  \PDOStatement) {
110
            return;
111
        }
112
113
        $statement->execute($builder->getBindings());
114
        $amount = $statement->rowCount();
115
116
        if ($this->cli) {
117
            if ($amount > 0) {
118
                $this->feedback("Synchronizing '$this->to.$table' rows", 'comment');
119
                $bar = $this->cli->getOutput()->createProgressBar($amount);
120
            } else {
121
                $this->feedback('No rows...', 'comment');
122
            }
123
        }
124
125
        while ($row = $statement->fetch(\PDO::FETCH_OBJ)) {
126
            $exists = $this->getToDb()->table($table)->where($queryColumn, $row->{$queryColumn})->first();
127
128
            if (! $exists) {
129
                $this->getToDb()->table($table)->insert((array) $row);
130
            } else {
131
                $this->getToDb()->table($table)->where($queryColumn, $row->{$queryColumn})->update((array) $row);
132
            }
133
134
            if (isset($bar)) {
135
                $bar->advance();
136
            }
137
        }
138
139
        if (isset($bar)) {
140
            $bar->finish();
141
        }
142
    }
143
144
    public function getTables(): array
145
    {
146
        if (! empty($this->tables)) {
147
            return $this->tables;
148
        }
149
150
        return $this->getFromDb()->getDoctrineSchemaManager()->listTableNames();
151
    }
152
153
    private function createTable(string $table, array $columns): void
154
    {
155
        $this->feedback("Creating '$this->to.$table' table", 'warn');
156
157
        Schema::connection($this->to)->create($table, function (Blueprint $table_bp) use ($table, $columns) {
158
            foreach ($columns as $column) {
159
                $type = Schema::connection($this->from)->getColumnType($table, $column);
160
161
                $table_bp->{$type}($column)->nullable();
162
163
                $this->feedback("Added {$type}('$column')->nullable()");
164
            }
165
        });
166
    }
167
168
    private function updateTable(string $table, string $column): void
169
    {
170
        Schema::connection($this->to)->table($table, function (Blueprint $table_bp) use ($table, $column) {
171
            $type = Schema::connection($this->from)->getColumnType($table, $column);
172
173
            $table_bp->{$type}($column)->nullable();
174
175
            $this->feedback("Added {$type}('$column')->nullable()");
176
        });
177
    }
178
179
    private function feedback(string $msg, $type = 'info'): void
180
    {
181
        if ($this->cli) {
182
            $this->cli->{$type}($msg);
183
        } else {
184
            echo PHP_EOL.$msg.PHP_EOL;
185
        }
186
    }
187
}
188