Passed
Push — develop ( 58ca2f...a1a700 )
by Freddie
09:12
created

Builder::createTable()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
c 1
b 0
f 0
dl 0
loc 41
rs 8.9617
ccs 22
cts 22
cp 1
cc 6
nc 18
nop 1
crap 6
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of FlexPHP.
4
 *
5
 * (c) Freddie Gar <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace FlexPHP\Database;
11
12
use Doctrine\DBAL\Platforms\AbstractPlatform;
13
use Doctrine\DBAL\Schema\Schema as DBALSchema;
14
use Doctrine\DBAL\Schema\SchemaConfig as DBALSchemaConfig;
15
use FlexPHP\Database\Exception\DatabaseValidationException;
16
use FlexPHP\Database\Validations\NameDatabaseValidation;
17
use FlexPHP\Schema\SchemaInterface;
18
19
final class Builder
20
{
21
    public const PLATFORM_MYSQL = 'MySQL';
22
23
    public const PLATFORM_SQLSRV = 'SQLSrv';
24
25
    public const PLATFORM_SQLITE = 'SQLite';
26
27
    /**
28
     * @var string
29
     */
30
    private $platform;
31
32
    /**
33
     * @var AbstractPlatform
34
     */
35
    private $DBALPlatform;
36
37
    /**
38
     * @var DBALSchema
39
     */
40
    private $DBALSchema;
41
42
    /**
43
     * @var array<int, string>
44
     */
45
    private $databases = [];
46
47
    /**
48
     * @var array<int, string>
49
     */
50
    private $users = [];
51
52
    /**
53
     * @var array<int, string>
54
     */
55
    private $tables = [];
56
57
    /**
58
     * @var array<int, string>
59
     */
60
    private $constraints = [];
61
62
    /**
63
     * @var array<int, string>
64
     */
65
    private $primaryTables = [];
66
67
    /**
68
     * @var array<int, string>
69
     */
70
    private $foreignTables = [];
71
72
    /**
73
     * @var array<string, string>
74
     */
75
    private $platformSupport = [
76
        self::PLATFORM_MYSQL => 'MySQL57',
77
        self::PLATFORM_SQLSRV => 'SQLServer2012',
78
        self::PLATFORM_SQLITE => 'Sqlite',
79
    ];
80
81 20
    public function __construct(string $platform)
82
    {
83 20
        if (empty($this->platformSupport[$platform])) {
84 1
            throw new DatabaseValidationException(\sprintf(
85 1
                'Platform %s not supported, try: %s',
86 1
                $platform,
87 1
                \implode(', ', \array_keys($this->platformSupport))
88
            ));
89
        }
90
91 19
        $fqdnPlatform = \sprintf('\Doctrine\DBAL\Platforms\%sPlatform', $this->platformSupport[$platform]);
92
93 19
        $this->platform = $platform;
94 19
        $this->DBALPlatform = new $fqdnPlatform();
95 19
    }
96
97 9
    public function createDatabase(string $name): void
98
    {
99 9
        (new NameDatabaseValidation($name))->validate();
100
101 8
        if ($this->isSQLitePlatform()) {
102 2
            return;
103
        }
104
105 6
        $this->databases[] = $this->DBALPlatform->getCreateDatabaseSQL($name)
106 6
            . ' ' . $this->getCollateDatabase()
107 6
            . ';';
108 6
    }
109
110 4
    public function createDatabaseWithUse(string $name): void
111
    {
112 4
        $this->createDatabase($name);
113
114 4
        if ($this->isMySQLPlatform()) {
115 2
            $this->databases[] = "USE {$name};";
116
        }
117 4
    }
118
119 8
    public function createUser(
120
        string $name,
121
        string $password,
122
        string $host = '',
123
        array $permissions = [],
124
        string $database = '*',
125
        string $table = '*'
126
    ): void {
127 8
        if ($this->isSQLitePlatform()) {
128 2
            return;
129
        }
130
131 6
        $user = new User($name, $password, $host);
132 6
        $user->setPlatform($this->platform);
133
134 6
        $this->users[] = $user->toSqlCreate();
135
136 6
        if (\count($permissions)) {
137 3
            $user->setGrants($permissions, $database, $table);
138 3
            $this->users[] = $user->toSqlPrivileges();
139
        }
140 6
    }
141
142 6
    public function createTable(SchemaInterface $schema): void
143
    {
144 6
        $table = new Table($schema);
145
146 6
        $DBALSchemaConfig = new DBALSchemaConfig();
147 6
        $DBALSchemaConfig->setDefaultTableOptions($table->getOptions());
148
149 6
        $this->DBALSchema = new DBALSchema([], [], $DBALSchemaConfig);
150
151 6
        $DBALTable = $this->DBALSchema->createTable($table->getName());
152
153 6
        $this->primaryTables[] = $table->getName();
154
155 6
        foreach ($table->getColumns() as $column) {
156 6
            $options = $column->getOptions();
157
158 6
            if ($this->isSQLitePlatform()) {
159 1
                unset($options['comment']);
160
            }
161
162 6
            $DBALTable->addColumn($column->getName(), $column->getType(), $options);
163
164 6
            if ($column->isPrimaryKey()) {
165 3
                $DBALTable->setPrimaryKey([$column->getName()]);
166
            }
167
168 6
            if ($column->isForeingKey()) {
169 3
                $fkRel = $schema->fkRelations()[$column->getName()];
170
171 3
                $DBALTable->addForeignKeyConstraint($fkRel['pkTable'], [$fkRel['pkId']], [$fkRel['fkId']]);
172
173 3
                $this->foreignTables[] = $fkRel['pkTable'];
174
            }
175
        }
176
177 6
        $sentences = $this->DBALSchema->toSql($this->DBALPlatform);
178
179 6
        $this->tables[] = $this->getTable($sentences);
180
181 6
        if (\count($sentences) > 1) {
182 4
            $this->constraints[] = $this->getConstraints(\array_slice($sentences, 1));
183
        }
184 6
    }
185
186 18
    public function toSql(): string
187
    {
188 18
        $this->validateLogic();
189
190 17
        $sql = [];
191 17
        $glue = \str_repeat("\n", 2);
192
193 17
        if (\count($this->databases)) {
194 6
            $sql[] = \implode($glue, $this->databases);
195
        }
196
197 17
        if (\count($this->users)) {
198 6
            $sql[] = \implode($glue, $this->users);
199
        }
200
201 17
        if (\count($this->tables)) {
202 5
            $sql[] = \implode($glue, $this->tables);
203
        }
204
205 17
        if (\count($this->constraints)) {
206 3
            $sql[] = \implode($glue, $this->constraints);
207
        }
208
209 17
        return \implode($glue, $sql);
210
    }
211
212 6
    private function getTable(array $sentences): string
213
    {
214 6
        return $this->getPrettyTable($sentences[0]) . ';';
215
    }
216
217 4
    private function getConstraints(array $sentences): string
218
    {
219 4
        return \implode(";\n\n", $sentences) . ';';
220
    }
221
222 6
    private function getCollateDatabase(): string
223
    {
224 6
        $collate = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci';
225
226 6
        if ($this->isSQLSrvPlatform()) {
227 2
            $collate = 'COLLATE latin1_general_100_ci_ai_sc';
228
        }
229
230 6
        return $collate;
231
    }
232
233 4
    private function isMySQLPlatform(): bool
234
    {
235 4
        return $this->platform === self::PLATFORM_MYSQL;
236
    }
237
238 6
    private function isSQLSrvPlatform(): bool
239
    {
240 6
        return $this->platform === self::PLATFORM_SQLSRV;
241
    }
242
243 18
    private function isSQLitePlatform(): bool
244
    {
245 18
        return $this->platform === self::PLATFORM_SQLITE;
246
    }
247
248 6
    private function getPrettyTable(string $sql): string
249
    {
250 6
        $tag = '<columns>';
251 6
        $regExpColumns = "/\((?$tag.*)\)/";
252 6
        $prettySql = $sql;
253
254 6
        \preg_match($regExpColumns, $sql, $matches);
255
256 6
        if (!empty($matches['columns'])) {
257 6
            $columns = $matches['columns'];
258 6
            $table = \str_replace($columns, "\n    $tag\n", $sql);
259
260 6
            $prettySql = \str_replace($tag, \str_replace(', ', ",\n    ", $columns), $table);
261
        }
262
263 6
        return $prettySql;
264
    }
265
266 18
    private function validateLogic(): void
267
    {
268 18
        $undefinedTables = \array_diff($this->foreignTables, $this->primaryTables);
269
270 18
        if (count($undefinedTables) > 0) {
271 1
            throw new DatabaseValidationException(
272 1
                'Tables in foreign key not found: ' . \implode(', ', $undefinedTables)
273
            );
274
        }
275 17
    }
276
}
277