Completed
Branch feature/pre-split (4ff102)
by Anton
03:27
created

SynchronizationPool::runChanges()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Database\Helpers;
10
11
use Psr\Log\LoggerInterface;
12
use Spiral\Core\Component;
13
use Spiral\Database\Entities\AbstractHandler as Behaviour;
14
use Spiral\Database\Entities\Driver;
15
use Spiral\Database\Schemas\Prototypes\AbstractTable;
16
use Spiral\Support\DFSSorter;
17
18
/**
19
 * Saves multiple linked tables at once but treating their cross dependency.
20
 *
21
 * Attention, not every DBMS support transactional schema manipulations!
22
 */
23
class SynchronizationPool extends Component
24
{
25
    /**
26
     * @var AbstractTable[]
27
     */
28
    private $tables = [];
29
30
    /**
31
     * @var Driver[]
32
     */
33
    private $drivers = [];
34
35
    /**
36
     * @param AbstractTable[] $tables
37
     */
38
    public function __construct(array $tables)
39
    {
40
        $this->tables = $tables;
41
42
        $this->collectDrivers();
43
    }
44
45
    /**
46
     * @return AbstractTable[]
47
     */
48
    public function getTables()
49
    {
50
        return $this->tables;
51
    }
52
53
    /**
54
     * List of tables sorted in order of cross dependency.
55
     *
56
     * @return AbstractTable[]
57
     */
58
    public function sortedTables(): array
59
    {
60
        /*
61
         * Tables has to be sorted using topological graph to execute operations in a valid order.
62
         */
63
        $sorter = new DFSSorter();
64
        foreach ($this->tables as $table) {
65
            $sorter->addItem($table->getName(), $table, $table->getDependencies());
66
        }
67
68
        return $sorter->sort();
69
    }
70
71
    /**
72
     * Synchronize tables.
73
     *
74
     * @param LoggerInterface|null $logger
75
     *
76
     * @throws \Exception
77
     * @throws \Throwable
78
     */
79
    public function run(LoggerInterface $logger = null)
80
    {
81
        $hasChanges = false;
82
        foreach ($this->tables as $table) {
83
            //todo: test drop
84
            if ($table->getComparator()->hasChanges() || $table->getStatus() == AbstractTable::STATUS_DECLARED_DROPPED) {
85
                $hasChanges = true;
86
                break;
87
            }
88
        }
89
90
        if (!$hasChanges) {
91
            //Nothing to do
92
            return;
93
        }
94
95
        $this->beginTransaction();
96
97
        try {
98
            //Drop not-needed foreign keys and alter everything else
99
            $this->dropForeigns($logger);
100
101
            //Drop not-needed indexes
102
            $this->dropIndexes($logger);
103
104
            //Other changes [NEW TABLES WILL BE CREATED HERE!]
105
            foreach ($this->runChanges($logger) as $table) {
106
                $table->save(Behaviour::CREATE_FOREIGNS, $logger, true);
107
            }
108
109
        } catch (\Throwable $e) {
110
            $this->rollbackTransaction();
111
            throw $e;
112
        }
113
114
        $this->commitTransaction();
115
    }
116
117
    /**
118
     * Begin mass transaction.
119
     */
120
    protected function beginTransaction()
121
    {
122
        foreach ($this->drivers as $driver) {
123
            $driver->beginTransaction();
124
        }
125
    }
126
127
    /**
128
     * Commit mass transaction.
129
     */
130
    protected function commitTransaction()
131
    {
132
        foreach ($this->drivers as $driver) {
133
            /**
134
             * @var Driver $driver
135
             */
136
            $driver->commitTransaction();
137
        }
138
    }
139
140
    /**
141
     * Roll back mass transaction.
142
     */
143
    protected function rollbackTransaction()
144
    {
145
        foreach (array_reverse($this->drivers) as $driver) {
146
            /**
147
             * @var Driver $driver
148
             */
149
            $driver->rollbackTransaction();
150
        }
151
    }
152
153
    /**
154
     * @param LoggerInterface|null $logger
155
     */
156
    protected function dropForeigns(LoggerInterface $logger = null)
157
    {
158
        foreach ($this->sortedTables() as $table) {
159
            if ($table->exists()) {
160
                $table->save(Behaviour::DROP_FOREIGNS, $logger, false);
161
            }
162
        }
163
    }
164
165
    /**
166
     * @param LoggerInterface|null $logger
167
     */
168
    protected function dropIndexes(LoggerInterface $logger = null)
169
    {
170
        foreach ($this->sortedTables() as $table) {
171
            if ($table->exists()) {
172
                $table->save(Behaviour::DROP_INDEXES, $logger, false);
173
            }
174
        }
175
    }
176
177
    /**
178
     * @param LoggerInterface|null $logger
179
     *
180
     * @return AbstractTable[] Created or updated tables.
181
     */
182
    protected function runChanges(LoggerInterface $logger = null): array
183
    {
184
        $tables = [];
185
        foreach ($this->sortedTables() as $table) {
186
            if ($table->getStatus() == AbstractTable::STATUS_DECLARED_DROPPED) {
187
                $table->save(Behaviour::DO_DROP, $logger);
188
            } else {
189
                $tables[] = $table;
190
                $table->save(
191
                    Behaviour::DO_ALL ^ Behaviour::DROP_FOREIGNS ^ Behaviour::DROP_INDEXES ^ Behaviour::CREATE_FOREIGNS,
192
                    $logger
193
                );
194
            }
195
        }
196
197
        return $tables;
198
    }
199
200
    /**
201
     * Collecting all involved drivers.
202
     */
203
    private function collectDrivers()
204
    {
205
        foreach ($this->tables as $table) {
206
            if (!in_array($table->getDriver(), $this->drivers, true)) {
207
                $this->drivers[] = $table->getDriver();
208
            }
209
        }
210
    }
211
}
212