Passed
Pull Request — master (#188)
by Damien
03:02
created

UpdateManager   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 90
c 0
b 0
f 0
dl 0
loc 226
rs 10
wmc 29

8 Methods

Rating   Name   Duplication   Size   Complexity  
B createAuditTable() 0 37 7
A __construct() 0 4 1
A processIndices() 0 11 4
A processColumns() 0 21 5
A updateAuditTable() 0 19 2
A updateAuditSchema() 0 20 5
A getConfiguration() 0 3 1
A getUpdateAuditSchemaSql() 0 35 4
1
<?php
2
3
namespace DH\DoctrineAuditBundle\Manager;
4
5
use DH\DoctrineAuditBundle\Configuration;
6
use DH\DoctrineAuditBundle\Exception\UpdateException;
7
use DH\DoctrineAuditBundle\Helper\SchemaHelper;
8
use DH\DoctrineAuditBundle\Reader\Reader;
9
use Doctrine\DBAL\Schema\Column;
10
use Doctrine\DBAL\Schema\Schema;
11
use Doctrine\DBAL\Schema\Table;
12
use Exception;
13
14
class UpdateManager
15
{
16
    /**
17
     * @var TransactionManager
18
     */
19
    private $manager;
20
21
    /**
22
     * @var Reader
23
     */
24
    private $reader;
25
26
    /**
27
     * @param TransactionManager $manager
28
     * @param Reader             $reader
29
     */
30
    public function __construct(TransactionManager $manager, Reader $reader)
31
    {
32
        $this->manager = $manager;
33
        $this->reader = $reader;
34
    }
35
36
    /**
37
     * @return Configuration
38
     */
39
    public function getConfiguration(): Configuration
40
    {
41
        return $this->manager->getConfiguration();
42
    }
43
44
    /**
45
     * @param null|array    $sqls     SQL queries to execute
46
     * @param null|callable $callback Callback executed after each query is run
47
     */
48
    public function updateAuditSchema(?array $sqls = null, ?callable $callback = null): void
49
    {
50
        $auditEntityManager = $this->manager->getConfiguration()->getEntityManager();
51
52
        if (null === $sqls) {
53
            $sqls = $this->getUpdateAuditSchemaSql();
54
        }
55
56
        foreach ($sqls as $index => $sql) {
57
            try {
58
                $statement = $auditEntityManager->getConnection()->prepare($sql);
59
                $statement->execute();
60
61
                if (null !== $callback) {
62
                    $callback([
63
                        'total' => \count($sqls),
64
                        'current' => $index,
65
                    ]);
66
                }
67
            } catch (Exception $e) {
68
                // something bad happened here :/
69
            }
70
        }
71
    }
72
73
    public function getUpdateAuditSchemaSql(): array
74
    {
75
        $readerEntityManager = $this->reader->getEntityManager();
76
        $readerSchemaManager = $readerEntityManager->getConnection()->getSchemaManager();
77
78
        $auditEntityManager = $this->manager->getConfiguration()->getEntityManager();
79
        $auditSchemaManager = $auditEntityManager->getConnection()->getSchemaManager();
80
81
        $auditSchema = $auditSchemaManager->createSchema();
82
        $fromSchema = clone $auditSchema;
83
        $readerSchema = $readerSchemaManager->createSchema();
84
        $tables = $readerSchema->getTables();
85
86
        $entities = $this->reader->getEntities();
87
        foreach ($tables as $table) {
88
            if (\in_array($table->getName(), array_values($entities), true)) {
89
                $auditTablename = preg_replace(
90
                    sprintf('#^([^\.]+\.)?(%s)$#', preg_quote($table->getName(), '#')),
91
                    sprintf(
92
                        '$1%s$2%s',
93
                        preg_quote($this->manager->getConfiguration()->getTablePrefix(), '#'),
94
                        preg_quote($this->manager->getConfiguration()->getTableSuffix(), '#')
95
                    ),
96
                    $table->getName()
97
                );
98
99
                if ($auditSchema->hasTable($auditTablename)) {
100
                    $this->updateAuditTable($auditSchema->getTable($auditTablename), $auditSchema);
101
                } else {
102
                    $this->createAuditTable($table, $auditSchema);
103
                }
104
            }
105
        }
106
107
        return $fromSchema->getMigrateToSql($auditSchema, $auditSchemaManager->getDatabasePlatform());
108
    }
109
110
    /**
111
     * Creates an audit table.
112
     *
113
     * @param Table       $table
114
     * @param null|Schema $schema
115
     *
116
     * @throws \Doctrine\DBAL\DBALException
117
     *
118
     * @return Schema
119
     */
120
    public function createAuditTable(Table $table, ?Schema $schema = null): Schema
121
    {
122
        $entityManager = $this->getConfiguration()->getEntityManager();
123
        $schemaManager = $entityManager->getConnection()->getSchemaManager();
124
        if (null === $schema) {
125
            $schema = $schemaManager->createSchema();
126
        }
127
128
        $auditTablename = preg_replace(
129
            sprintf('#^([^\.]+\.)?(%s)$#', preg_quote($table->getName(), '#')),
130
            sprintf(
131
                '$1%s$2%s',
132
                preg_quote($this->getConfiguration()->getTablePrefix(), '#'),
133
                preg_quote($this->getConfiguration()->getTableSuffix(), '#')
134
            ),
135
            $table->getName()
136
        );
137
138
        if (null !== $auditTablename && !$schema->hasTable($auditTablename)) {
139
            $auditTable = $schema->createTable($auditTablename);
140
141
            // Add columns to audit table
142
            foreach (SchemaHelper::getAuditTableColumns() as $columnName => $struct) {
143
                $auditTable->addColumn($columnName, $struct['type'], $struct['options']);
144
            }
145
146
            // Add indices to audit table
147
            foreach (SchemaHelper::getAuditTableIndices($auditTablename) as $columnName => $struct) {
148
                if ('primary' === $struct['type']) {
149
                    $auditTable->setPrimaryKey([$columnName]);
150
                } else {
151
                    $auditTable->addIndex([$columnName], $struct['name']);
152
                }
153
            }
154
        }
155
156
        return $schema;
157
    }
158
159
    /**
160
     * Ensures an audit table's structure is valid.
161
     *
162
     * @param Table       $table
163
     * @param null|Schema $schema
164
     * @param null|array  $expectedColumns
165
     * @param null|array  $expectedIndices
166
     *
167
     * @throws UpdateException
168
     * @throws \Doctrine\DBAL\Schema\SchemaException
169
     *
170
     * @return Schema
171
     */
172
    public function updateAuditTable(Table $table, ?Schema $schema = null, ?array $expectedColumns = null, ?array $expectedIndices = null): Schema
173
    {
174
        $entityManager = $this->getConfiguration()->getEntityManager();
175
        $schemaManager = $entityManager->getConnection()->getSchemaManager();
176
        if (null === $schema) {
177
            $schema = $schemaManager->createSchema();
178
        }
179
180
        $table = $schema->getTable($table->getName());
181
182
        $columns = $schemaManager->listTableColumns($table->getName());
183
184
        // process columns
185
        $this->processColumns($table, $columns, $expectedColumns ?? SchemaHelper::getAuditTableColumns());
186
187
        // process indices
188
        $this->processIndices($table, $expectedIndices ?? SchemaHelper::getAuditTableIndices($table->getName()));
189
190
        return $schema;
191
    }
192
193
    /**
194
     * @param Table $table
195
     * @param array $columns
196
     * @param array $expectedColumns
197
     */
198
    private function processColumns(Table $table, array $columns, array $expectedColumns): void
199
    {
200
        $processed = [];
201
202
        foreach ($columns as $column) {
203
            if (\array_key_exists($column->getName(), $expectedColumns)) {
204
                // column is part of expected columns
205
                $table->dropColumn($column->getName());
206
                $table->addColumn($column->getName(), $expectedColumns[$column->getName()]['type'], $expectedColumns[$column->getName()]['options']);
207
            } else {
208
                // column is not part of expected columns so it has to be removed
209
                $table->dropColumn($column->getName());
210
            }
211
212
            $processed[] = $column->getName();
213
        }
214
215
        foreach ($expectedColumns as $columnName => $options) {
216
            if (!\in_array($columnName, $processed, true)) {
217
                // expected column in not part of concrete ones so it's a new column, we need to add it
218
                $table->addColumn($columnName, $options['type'], $options['options']);
219
            }
220
        }
221
    }
222
223
    /**
224
     * @param Table $table
225
     * @param array $expectedIndices
226
     *
227
     * @throws \Doctrine\DBAL\Schema\SchemaException
228
     */
229
    private function processIndices(Table $table, array $expectedIndices): void
230
    {
231
        foreach ($expectedIndices as $columnName => $options) {
232
            if ('primary' === $options['type']) {
233
                $table->dropPrimaryKey();
234
                $table->setPrimaryKey([$columnName]);
235
            } else {
236
                if ($table->hasIndex($options['name'])) {
237
                    $table->dropIndex($options['name']);
238
                }
239
                $table->addIndex([$columnName], $options['name']);
240
            }
241
        }
242
    }
243
}
244