Completed
Branch feature/pre-split (42159e)
by Anton
05:36
created

SynchronizationPool::run()   C

Complexity

Conditions 7
Paths 86

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 18
nc 86
nop 1
dl 0
loc 34
rs 6.7272
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\Debug\Traits\LoggerTrait;
17
use Spiral\Support\DFSSorter;
18
19
/**
20
 * Saves multiple linked tables at once but treating their cross dependency.
21
 */
22
class SynchronizationPool extends Component
23
{
24
    use LoggerTrait;
25
26
    /**
27
     * @var AbstractTable[]
28
     */
29
    private $tables = [];
30
31
    /**
32
     * @var Driver[]
33
     */
34
    private $drivers = [];
35
36
    /**
37
     * @param AbstractTable[] $tables
38
     */
39
    public function __construct(array $tables)
40
    {
41
        $this->tables = $tables;
42
43
        $this->collectDrivers();
44
    }
45
46
    /**
47
     * @return AbstractTable[]
48
     */
49
    public function getTables()
50
    {
51
        return $this->tables;
52
    }
53
54
    /**
55
     * List of tables sorted in order of cross dependency.
56
     *
57
     * @return AbstractTable[]
58
     */
59
    public function sortedTables(): array
60
    {
61
        /*
62
         * Tables has to be sorted using topological graph to execute operations in a valid order.
63
         */
64
        $sorter = new DFSSorter();
65
        foreach ($this->tables as $table) {
66
            $sorter->addItem($table->getName(), $table, $table->getDependencies());
67
        }
68
69
        return $sorter->sort();
70
    }
71
72
    /**
73
     * Synchronize tables.
74
     *
75
     * @param LoggerInterface|null $logger
76
     *
77
     * @throws \Exception
78
     * @throws \Throwable
79
     */
80
    public function run(LoggerInterface $logger = null)
81
    {
82
        $this->beginTransaction();
83
84
        try {
85
            //Drop not-needed foreign keys and alter everything else
86
            foreach ($this->sortedTables() as $table) {
87
                if ($table->exists()) {
88
                    $table->save(Behaviour::DROP_FOREIGNS, $logger, false);
89
                }
90
            }
91
92
            //Drop not-needed indexes
93
            foreach ($this->sortedTables() as $table) {
94
                if ($table->exists()) {
95
                    $table->save(Behaviour::DROP_INDEXES, $logger, false);
96
                }
97
            }
98
99
            //Other changes
100
            foreach ($this->sortedTables() as $table) {
101
                $table->save(
102
                    Behaviour::DO_ALL ^ Behaviour::DROP_FOREIGNS ^ Behaviour::DROP_INDEXES,
103
                    $logger,
104
                    false
105
                );
106
            }
107
        } catch (\Throwable $e) {
108
            $this->rollbackTransaction();
109
            throw $e;
110
        }
111
112
        $this->commitTransaction();
113
    }
114
115
    /**
116
     * Begin mass transaction.
117
     */
118
    protected function beginTransaction()
119
    {
120
        $this->logger()->debug('Begin transaction');
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
        $this->logger()->debug('Commit transaction');
133
134
        foreach ($this->drivers as $driver) {
135
            $driver->commitTransaction();
136
        }
137
    }
138
139
    /**
140
     * Roll back mass transaction.
141
     */
142
    protected function rollbackTransaction()
143
    {
144
        $this->logger()->warning('Roll back transaction');
145
146
        foreach ($this->drivers as $driver) {
147
            $driver->rollbackTransaction();
148
        }
149
    }
150
151
    /**
152
     * Collecting all involved drivers.
153
     */
154
    private function collectDrivers()
155
    {
156
        foreach ($this->tables as $table) {
157
            if (!in_array($table->getDriver(), $this->drivers, true)) {
158
                $this->drivers[] = $table->getDriver();
159
            }
160
        }
161
    }
162
}
163