Passed
Push — develop ( cd2c29...4f7ba8 )
by nguereza
02:28
created

Schema::alter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 11
rs 10
cc 2
nc 2
nop 2
1
<?php
2
3
/**
4
 * Platine Database
5
 *
6
 * Platine Database is the abstraction layer using PDO with support of query and schema builder
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Database
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file Schema.php
33
 *
34
 *  The database schema class
35
 *
36
 *  @package    Platine\Database
37
 *  @author Platine Developers Team
38
 *  @copyright  Copyright (c) 2020
39
 *  @license    http://opensource.org/licenses/MIT  MIT License
40
 *  @link   http://www.iacademy.cf
41
 *  @version 1.0.0
42
 *  @filesource
43
 */
44
45
declare(strict_types=1);
46
47
namespace Platine\Database;
48
49
use Platine\Database\Schema\AlterTable;
50
use Platine\Database\Schema\CreateTable;
51
52
/**
53
 * Class Schema
54
 * @package Platine\Database
55
 */
56
class Schema
57
{
58
    /**
59
     * The Connection instance
60
     * @var Connection
61
     */
62
    protected Connection $connection;
63
64
    /**
65
     * The list of tables
66
     * @var array<string, string>
67
     */
68
    protected array $tables = [];
69
70
    /**
71
     * The list of views
72
     * @var array<string, string>
73
     */
74
    protected array $views = [];
75
76
    /**
77
     * The current database name
78
     * @var string
79
     */
80
    protected string $databaseName = '';
81
82
    /**
83
     * The current table columns
84
     * @var array<string, array<string, array<string, string>>>
85
     */
86
    protected array $columns = [];
87
88
    /**
89
     * Class constructor
90
     * @param Connection $connection
91
     */
92
    public function __construct(Connection $connection)
93
    {
94
        $this->connection = $connection;
95
    }
96
97
    /**
98
     *
99
     * @return string
100
     */
101
    public function getDatabaseName(): string
102
    {
103
        if ($this->databaseName === '') {
104
            $driver = $this->connection->getDriver();
105
            $result = $driver->getDatabaseName();
106
107
            if (isset($result['result'])) {
108
                $this->databaseName = $result['result'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $result['result'] can also be of type array. However, the property $databaseName is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
109
            } else {
110
                $this->databaseName = $this->connection->column(
111
                    $result['sql'],
112
                    $result['params']
113
                );
114
            }
115
        }
116
        return $this->databaseName;
117
    }
118
119
    /**
120
     * Check whether the given table exists
121
     * @param string $table
122
     * @param bool $skipCache
123
     * @return bool
124
     */
125
    public function hasTable(string $table, bool $skipCache = false): bool
126
    {
127
        $list = $this->getTables($skipCache);
128
129
        return isset($list[strtolower($table)]);
130
    }
131
132
    /**
133
     * Check whether the given view exists
134
     * @param string $view
135
     * @param bool $skipCache
136
     * @return bool
137
     */
138
    public function hasView(string $view, bool $skipCache = false): bool
139
    {
140
        $list = $this->getViews($skipCache);
141
142
        return isset($list[strtolower($view)]);
143
    }
144
145
    /**
146
     * Return the list of tables for the current database
147
     * @param bool $skipCache whether to use the cached data or not
148
     * @return array<string, string>
149
     */
150
    public function getTables(bool $skipCache = false): array
151
    {
152
        if ($skipCache) {
153
            $this->tables = [];
154
        }
155
156
        if (empty($this->tables)) {
157
            $driver = $this->connection->getDriver();
158
            $databaseName = $this->getDatabaseName();
159
            $sql = $driver->getTables($databaseName);
160
161
            $results = $this->connection
162
                                        ->query($sql['sql'], $sql['params'])
163
                                        ->fetchNum();
164
165
            while ($result = $results->next()) {
166
                $this->tables[strtolower($result[0])] = $result[0];
167
            }
168
        }
169
170
        return $this->tables;
171
    }
172
173
    /**
174
     * Return the list of views for the current database
175
     * @param bool $skipCache whether to use the cached data or not
176
     * @return array<string, string>
177
     */
178
    public function getViews(bool $skipCache = false): array
179
    {
180
        if ($skipCache) {
181
            $this->views = [];
182
        }
183
184
        if (empty($this->views)) {
185
            $driver = $this->connection->getDriver();
186
            $databaseName = $this->getDatabaseName();
187
            $sql = $driver->getViews($databaseName);
188
189
            $results = $this->connection
190
                                        ->query($sql['sql'], $sql['params'])
191
                                        ->fetchNum();
192
193
            while ($result = $results->next()) {
194
                $this->views[strtolower($result[0])] = $result[0];
195
            }
196
        }
197
198
        return $this->views;
199
    }
200
201
    /**
202
     * Return the list of columns for the given table
203
     * @param string $table
204
     * @param bool $skipCache
205
     * @param bool $names whether to return only the columns names
206
     * @return string[]|array<string, array<string, string>>
207
     */
208
    public function getColumns(
209
        string $table,
210
        bool $skipCache = false,
211
        bool $names = true
212
    ) {
213
        if ($skipCache) {
214
            unset($this->columns[$table]);
215
        }
216
217
        if (!$this->hasTable($table, $skipCache)) {
218
            return [];
219
        }
220
221
        if (!isset($this->columns[$table])) {
222
            $driver = $this->connection->getDriver();
223
            $databaseName = $this->getDatabaseName();
224
            $sql = $driver->getColumns($databaseName, $table);
225
226
            $results = $this->connection
227
                                        ->query($sql['sql'], $sql['params'])
228
                                        ->fetchAssoc();
229
230
            /** @var array<string, array<string, string>> $columns */
231
            $columns = [];
232
233
            while (
234
                    /** @var array<string, string>> $col */
235
                    $col = $results->next()
236
            ) {
237
                $columns[$col['name']] = [
238
                    'name' => $col['name'],
239
                    'type' => $col['type'],
240
                ];
241
            }
242
243
            $this->columns[$table] = $columns;
244
        }
245
246
        return $names ? array_keys($this->columns[$table]) : $this->columns[$table];
247
    }
248
249
    /**
250
     * Return the list of columns for the given view
251
     * @param string $view
252
     * @param bool $skipCache
253
     * @param bool $names whether to return only the columns names
254
     * @return string[]|array<string, array<string, string>>
255
     */
256
    public function getViewColumns(
257
        string $view,
258
        bool $skipCache = false,
259
        bool $names = true
260
    ) {
261
        if ($skipCache) {
262
            unset($this->columns[$view]);
263
        }
264
265
        if (!$this->hasView($view, $skipCache)) {
266
            return [];
267
        }
268
269
        if (!isset($this->columns[$view])) {
270
            $driver = $this->connection->getDriver();
271
            $databaseName = $this->getDatabaseName();
272
            $sql = $driver->getViewColumns($databaseName, $view);
273
274
            $results = $this->connection
275
                                        ->query($sql['sql'], $sql['params'])
276
                                        ->fetchAssoc();
277
278
            /** @var array<string, array<string, string>> $columns */
279
            $columns = [];
280
281
            while (
282
                    /** @var array<string, string>> $col */
283
                    $col = $results->next()
284
            ) {
285
                $columns[$col['name']] = [
286
                    'name' => $col['name'],
287
                    'type' => $col['type'],
288
                ];
289
            }
290
291
            $this->columns[$view] = $columns;
292
        }
293
294
        return $names ? array_keys($this->columns[$view]) : $this->columns[$view];
295
    }
296
297
    /**
298
     * Create new table
299
     * @param string $table
300
     * @param callable $callback callback to use to define the field(s) and indexes
301
     * @return void
302
     */
303
    public function create(string $table, callable $callback): void
304
    {
305
        $driver = $this->connection->getDriver();
306
        $schema = new CreateTable($table);
307
        $callback($schema);
308
309
        foreach ($driver->create($schema) as $result) {
310
            $this->connection->exec($result['sql'], $result['params']);
311
        }
312
313
        //clear all tables list
314
        $this->tables = [];
315
    }
316
317
    /**
318
     * Alter table definition
319
     * @param string $table
320
     * @param callable $callback callback to use to add/remove the field(s) or indexes
321
     * @return void
322
     */
323
    public function alter(string $table, callable $callback): void
324
    {
325
        $driver = $this->connection->getDriver();
326
        $schema = new AlterTable($table);
327
        $callback($schema);
328
329
        //clear all columns for this table
330
        unset($this->columns[strtolower($table)]);
331
332
        foreach ($driver->alter($schema) as $result) {
333
            $this->connection->exec($result['sql'], $result['params']);
334
        }
335
    }
336
337
    /**
338
     * Rename the table
339
     * @param string $table
340
     * @param string $newName
341
     * @return void
342
     */
343
    public function renameTable(string $table, string $newName): void
344
    {
345
        $driver = $this->connection->getDriver();
346
        $result = $driver->renameTable($table, $newName);
347
        $this->connection->exec($result['sql'], $result['params']);
348
349
        //clear all columns for this table
350
        unset($this->columns[strtolower($table)]);
351
352
        //clear all tables list
353
        $this->tables = [];
354
    }
355
356
    /**
357
     * Drop the table
358
     * @param string $table
359
     * @return void
360
     */
361
    public function drop(string $table): void
362
    {
363
        $driver = $this->connection->getDriver();
364
        $result = $driver->drop($table);
365
        $this->connection->exec($result['sql'], $result['params']);
366
367
        //clear all columns for this table
368
        unset($this->columns[strtolower($table)]);
369
370
        //clear all tables list
371
        $this->tables = [];
372
    }
373
374
    /**
375
     * Truncate the table
376
     * @param string $table
377
     * @return void
378
     */
379
    public function truncate(string $table): void
380
    {
381
        $driver = $this->connection->getDriver();
382
        $result = $driver->truncate($table);
383
        $this->connection->exec($result['sql'], $result['params']);
384
    }
385
}
386