Builder::createUser()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 20
rs 10
ccs 9
cts 9
cp 1
cc 3
nc 3
nop 6
crap 3
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;
14
use Doctrine\DBAL\Schema\SchemaConfig;
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
    private string $platform;
28
29
    /**
30
     * @var AbstractPlatform
31
     */
32
    private $DBALPlatform;
33
34
    private ?\Doctrine\DBAL\Schema\Schema $DBALSchema = null;
35
36
    /**
37
     * @var array<int, string>
38
     */
39
    private array $databases = [];
40
41
    /**
42
     * @var array<int, string>
43
     */
44
    private array $users = [];
45
46
    /**
47
     * @var array<int, string>
48
     */
49
    private array $tables = [];
50
51
    /**
52
     * @var array<int, string>
53
     */
54
    private array $constraints = [];
55
56
    /**
57
     * @var array<int, string>
58
     */
59
    private array $primaryTables = [];
60
61
    /**
62
     * @var array<int, string>
63
     */
64
    private array $foreignTables = [];
65
66
    /**
67
     * @var array<string, string>
68
     */
69
    private array $platformSupport = [
70
        self::PLATFORM_MYSQL => 'MySQL57',
71
        self::PLATFORM_SQLSRV => 'SQLServer2012',
72
        self::PLATFORM_SQLITE => 'Sqlite',
73
    ];
74
75 20
    public function __construct(string $platform)
76
    {
77 20
        if (empty($this->platformSupport[$platform])) {
78 1
            throw new DatabaseValidationException(\sprintf(
79 1
                'Platform %s not supported, try: %s',
80 1
                $platform,
81 1
                \implode(', ', \array_keys($this->platformSupport))
82
            ));
83
        }
84
85 19
        $fqdnPlatform = \sprintf('\Doctrine\DBAL\Platforms\%sPlatform', $this->platformSupport[$platform]);
86
87 19
        $this->platform = $platform;
88 19
        $this->DBALPlatform = new $fqdnPlatform();
89 19
    }
90
91 9
    public function createDatabase(string $name): void
92
    {
93 9
        (new NameDatabaseValidation($name))->validate();
94
95 8
        if ($this->isSQLitePlatform()) {
96 2
            return;
97
        }
98
99 6
        $this->databases[] = $this->DBALPlatform->getCreateDatabaseSQL($name)
100 6
            . ' ' . $this->getCollateDatabase()
101 6
            . ';';
102 6
    }
103
104 4
    public function createDatabaseWithUse(string $name): void
105
    {
106 4
        $this->createDatabase($name);
107
108 4
        if ($this->isMySQLPlatform()) {
109 2
            $this->databases[] = sprintf('USE %s;', $name);
110
        }
111 4
    }
112
113 8
    public function createUser(
114
        string $name,
115
        string $password,
116
        string $host = '',
117
        array $permissions = [],
118
        string $database = '*',
119
        string $table = '*'
120
    ): void {
121 8
        if ($this->isSQLitePlatform()) {
122 2
            return;
123
        }
124
125 6
        $user = new User($name, $password, $host);
126 6
        $user->setPlatform($this->platform);
127
128 6
        $this->users[] = $user->toSqlCreate();
129
130 6
        if (\count($permissions) > 0) {
131 3
            $user->setGrants($permissions, $database, $table);
132 3
            $this->users[] = $user->toSqlPrivileges();
133
        }
134 6
    }
135
136 6
    public function createTable(SchemaInterface $schema): void
137
    {
138 6
        $table = new Table($schema);
139
140 6
        $DBALSchemaConfig = new SchemaConfig();
141 6
        $DBALSchemaConfig->setDefaultTableOptions($table->getOptions());
142
143 6
        $this->DBALSchema = new Schema([], [], $DBALSchemaConfig);
144
145 6
        $DBALTable = $this->DBALSchema->createTable($table->getName());
146
147 6
        $this->primaryTables[] = $table->getName();
148
149 6
        foreach ($table->getColumns() as $column) {
150 6
            $options = $column->getOptions();
151
152 6
            if ($this->isSQLitePlatform()) {
153 1
                unset($options['comment']);
154
            }
155
156 6
            $DBALTable->addColumn($column->getName(), $column->getType(), $options);
157
158 6
            if ($column->isPrimaryKey()) {
159 3
                $DBALTable->setPrimaryKey([$column->getName()]);
160
            }
161
162 6
            if ($column->isForeingKey()) {
163 3
                $fkRel = $schema->fkRelations()[$column->getName()];
164
165 3
                $DBALTable->addForeignKeyConstraint($fkRel['pkTable'], [$fkRel['pkId']], [$fkRel['fkId']]);
166
167 3
                $this->foreignTables[] = $fkRel['pkTable'];
168
            }
169
        }
170
171 6
        $sentences = $this->DBALSchema->toSql($this->DBALPlatform);
172
173 6
        $this->tables[] = $this->getTable($sentences);
174
175 6
        if (\count($sentences) > 1) {
176 4
            $this->constraints[] = $this->getConstraints(\array_slice($sentences, 1));
177
        }
178 6
    }
179
180 18
    public function toSql(): string
181
    {
182 18
        $this->validateLogic();
183
184 17
        $sql = [];
185 17
        $glue = \str_repeat("\n", 2);
186
187 17
        if (\count($this->databases) > 0) {
188 6
            $sql[] = \implode($glue, $this->databases);
189
        }
190
191 17
        if (\count($this->users) > 0) {
192 6
            $sql[] = \implode($glue, $this->users);
193
        }
194
195 17
        if (\count($this->tables) > 0) {
196 5
            $sql[] = \implode($glue, $this->tables);
197
        }
198
199 17
        if (\count($this->constraints) > 0) {
200 3
            $sql[] = \implode($glue, $this->constraints);
201
        }
202
203 17
        $plain = \implode($glue, $sql);
204
205 17
        if ($plain !== '') {
206 13
            return $plain . "\n";
207
        }
208
209 4
        return '';
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 = sprintf('/\((?%s.*)\)/', $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