UpdateManager::createAuditTable()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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