Completed
Branch feature/pre-split (76ded7)
by Anton
03:22
created

AbstractTable::intiSchema()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 8
nop 1
dl 0
loc 16
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\Database\Schemas\Prototypes;
8
9
use Interop\Container\ContainerInterface;
10
use Psr\Log\LoggerAwareInterface;
11
use Spiral\Core\Component;
12
use Spiral\Database\Entities\Driver;
13
use Spiral\Database\Exceptions\SchemaException;
14
use Spiral\Database\Schemas\ColumnInterface;
15
use Spiral\Database\Schemas\IndexInterface;
16
use Spiral\Database\Schemas\ReferenceInterface;
17
use Spiral\Database\Schemas\StateComparator;
18
use Spiral\Database\Schemas\TableInterface;
19
use Spiral\Database\Schemas\TableState;
20
use Spiral\Debug\Traits\LoggerTrait;
21
22
/**
23
 * AbstractTable class used to describe and manage state of specified table. It provides ability to
24
 * get table introspection, update table schema and automatically generate set of diff operations.
25
 *
26
 * Most of table operation like column, index or foreign key creation/altering will be applied when
27
 * save() method will be called.
28
 *
29
 * Column configuration shortcuts:
30
 *
31
 * @method AbstractColumn primary($column)
32
 * @method AbstractColumn bigPrimary($column)
33
 * @method AbstractColumn enum($column, array $values)
34
 * @method AbstractColumn string($column, $length = 255)
35
 * @method AbstractColumn decimal($column, $precision, $scale)
36
 * @method AbstractColumn boolean($column)
37
 * @method AbstractColumn integer($column)
38
 * @method AbstractColumn tinyInteger($column)
39
 * @method AbstractColumn bigInteger($column)
40
 * @method AbstractColumn text($column)
41
 * @method AbstractColumn tinyText($column)
42
 * @method AbstractColumn longText($column)
43
 * @method AbstractColumn double($column)
44
 * @method AbstractColumn float($column)
45
 * @method AbstractColumn datetime($column)
46
 * @method AbstractColumn date($column)
47
 * @method AbstractColumn time($column)
48
 * @method AbstractColumn timestamp($column)
49
 * @method AbstractColumn binary($column)
50
 * @method AbstractColumn tinyBinary($column)
51
 * @method AbstractColumn longBinary($column)
52
 */
53
abstract class AbstractTable extends Component implements TableInterface, LoggerAwareInterface
54
{
55
    use LoggerTrait;
56
57
    /**
58
     * Indication that table is exists and current schema is fetched from database.
59
     *
60
     * @var bool
61
     */
62
    private $exists = false;
63
64
    /**
65
     * Database specific tablePrefix. Required for table renames.
66
     *
67
     * @var string
68
     */
69
    private $prefix = '';
70
71
    /**
72
     * @invisible
73
     *
74
     * @var Driver
75
     */
76
    protected $driver = null;
77
78
    /**
79
     * Initial table state.
80
     *
81
     * @invisible
82
     * @var TableState
83
     */
84
    protected $initialState = null;
85
86
    /**
87
     * Currently defined table state.
88
     *
89
     * @invisible
90
     * @var TableState
91
     */
92
    protected $currentState = null;
93
94
    /**
95
     * @param Driver $driver Parent driver.
96
     * @param string $name   Table name, must include table prefix.
97
     * @param string $prefix Database specific table prefix.
98
     */
99
    public function __construct(Driver $driver, string $name, string $prefix)
100
    {
101
        $this->driver = $driver;
102
        $this->prefix = $prefix;
103
104
        //Initializing states
105
        $this->initialState = new TableState($this->prefix . $name);
106
        $this->currentState = new TableState($this->prefix . $name);
107
108
        $this->exists = $this->driver->hasTable($this->getName());
109
110
        if ($this->exists) {
111
            //Initiating table schema
112
            $this->initSchema($this->initialState);
113
        }
114
115
        $this->setState($this->initialState);
116
    }
117
118
    /**
119
     * Get instance of associated driver.
120
     *
121
     * @return Driver
122
     */
123
    public function getDriver(): Driver
124
    {
125
        return $this->driver;
126
    }
127
128
    /**
129
     * @return StateComparator
130
     */
131
    public function getComparator(): StateComparator
132
    {
133
        return new StateComparator($this->initialState, $this->currentState);
134
    }
135
136
    /**
137
     * Check if table schema has been modified since synchronization.
138
     *
139
     * @return bool
140
     */
141
    protected function hasChanges(): bool
142
    {
143
        return $this->getComparator()->hasChanges();
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function exists(): bool
150
    {
151
        return $this->exists;
152
    }
153
154
    /**
155
     * Return database specific table prefix.
156
     *
157
     * @return string
158
     */
159
    public function getPrefix(): string
160
    {
161
        return $this->prefix;
162
    }
163
164
    /**
165
     * Sets table name. Use this function in combination with save to rename table.
166
     *
167
     * @param string $name
168
     *
169
     * @return string Prefixed table name.
170
     */
171
    public function setName(string $name): string
172
    {
173
        $this->currentState->setName($this->prefix . $name);
174
175
        return $this->getName();
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    public function getName(): string
182
    {
183
        return $this->currentState->getName();
184
    }
185
186
    /**
187
     * Set table primary keys. Operation can only be applied for newly created tables. Now every
188
     * database might support compound indexes.
189
     *
190
     * @param array $columns
191
     *
192
     * @return self
193
     *
194
     * @throws SchemaException
195
     */
196
    public function setPrimaryKeys(array $columns): AbstractTable
197
    {
198
        if ($this->exists() && $this->initialState->getPrimaryKeys() != $columns) {
199
            throw new SchemaException('Unable to change primary keys for already exists table');
0 ignored issues
show
Unused Code introduced by
The call to SchemaException::__construct() has too many arguments starting with 'Unable to change primar...r already exists table'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
200
        }
201
202
        //Updating primary keys in current state
203
        $this->currentState->setPrimaryKeys($columns);
204
205
        return $this;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function getPrimaryKeys(): array
212
    {
213
        return $this->currentState->getPrimaryKeys();
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function hasColumn(string $name): bool
220
    {
221
        return $this->currentState->hasColumn($name);
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     *
227
     * @return ColumnInterface[]|AbstractColumn[]
228
     */
229
    public function getColumns(): array
230
    {
231
        return $this->currentState->getColumns();
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function hasIndex(array $columns = []): bool
238
    {
239
        return $this->currentState->hasIndex($columns);
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     *
245
     * @return IndexInterface[]|AbstractIndex[]
246
     */
247
    public function getIndexes(): array
248
    {
249
        return $this->currentState->getIndexes();
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function hasForeign(string $column): bool
256
    {
257
        return $this->currentState->hasForeign($column);
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     *
263
     * @return ReferenceInterface[]|AbstractReference[]
264
     */
265
    public function getForeigns(): array
266
    {
267
        return $this->currentState->getForeigns();
268
    }
269
270
    /**
271
     * {@inheritdoc}
272
     */
273
    public function getDependencies(): array
274
    {
275
        $tables = [];
276
        foreach ($this->currentState->getForeigns() as $foreign) {
277
            $tables[] = $foreign->getForeignTable();
278
        }
279
280
        return $tables;
281
    }
282
283
    //------
284
    //Altering operations
285
    //------
286
287
    /**
288
     * Reset table state to new form.
289
     *
290
     * @param TableState $state Use null to flush table schema.
291
     *
292
     * @return self|$this
293
     */
294
    public function setState(TableState $state = null): AbstractTable
295
    {
296
        $this->currentState = new TableState($this->initialState->getName());
297
298
        if (!empty($state)) {
299
            $this->currentState->setName($state->getName());
300
            $this->currentState->syncState($state);
0 ignored issues
show
Documentation introduced by
$state is of type object<Spiral\Database\Schemas\TableState>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
301
        }
302
303
        return $this;
304
    }
305
306
    /**
307
     * Reset table state to it initial form.
308
     *
309
     * @return self|$this
310
     */
311
    public function resetState(): AbstractTable
312
    {
313
        $this->setState($this->initialState);
314
315
        return $this;
316
    }
317
318
    /**
319
     * Populate table schema with values from database.
320
     *
321
     * @param TableState $state
322
     */
323
    protected function initSchema(TableState $state)
324
    {
325
        foreach ($this->fetchColumns() as $column) {
326
            $state->registerColumn($column);
327
        }
328
329
        foreach ($this->fetchIndexes() as $index) {
330
            $state->registerIndex($index);
331
        }
332
333
        foreach ($this->fetchReferences() as $foreign) {
334
            $state->registerIndex($foreign);
0 ignored issues
show
Documentation introduced by
$foreign is of type object<Spiral\Database\S...mas\ReferenceInterface>, but the function expects a object<Spiral\Database\Schemas\IndexInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
335
        }
336
337
        $state->setPrimaryKeys($this->fetchPrimaryKeys());
338
339
        //DBMS specific initialization can be placed here
340
    }
341
342
    /**
343
     * Fetch index declarations from database.
344
     *
345
     * @return ColumnInterface[]
346
     */
347
    abstract protected function fetchColumns(): array;
348
349
    /**
350
     * Fetch index declarations from database.
351
     *
352
     * @return IndexInterface[]
353
     */
354
    abstract protected function fetchIndexes(): array;
355
356
    /**
357
     * Fetch references declaration from database.
358
     *
359
     * @return ReferenceInterface[]
360
     */
361
    abstract protected function fetchReferences(): array;
362
363
    /**
364
     * Fetch names of primary keys from table.
365
     *
366
     * @return array
367
     */
368
    abstract protected function fetchPrimaryKeys(): array;
369
370
    /**
371
     * @return AbstractColumn|string
372
     */
373
    public function __toString(): string
374
    {
375
        return $this->getName();
376
    }
377
378
    /**
379
     * @return array
380
     */
381
    public function __debugInfo()
382
    {
383
        return [
384
            'name'        => $this->getName(),
385
            'primaryKeys' => $this->getPrimaryKeys(),
386
            'columns'     => array_values($this->getColumns()),
387
            'indexes'     => array_values($this->getIndexes()),
388
            'references'  => array_values($this->getForeigns()),
389
        ];
390
    }
391
392
    /**
393
     * @return ContainerInterface
394
     */
395
    protected function iocContainer()
396
    {
397
        //Falling back to driver specific container
398
        return $this->driver->iocContainer();
0 ignored issues
show
Bug introduced by
The method iocContainer() cannot be called from this context as it is declared protected in class Spiral\Core\Component.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
399
    }
400
}