Issues (21)

src/Schema.php (1 issue)

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