Passed
Pull Request — 2.x (#105)
by Maxim
17:54
created

MySQLHandler::afterSync()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 4
nc 4
nop 1
dl 0
loc 6
ccs 0
cts 0
cp 0
crap 30
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver\MySQL;
13
14
use PDO;
15
use Cycle\Database\Driver\Handler;
16
use Cycle\Database\Driver\MySQL\Exception\MySQLException;
17
use Cycle\Database\Driver\MySQL\Schema\MySQLTable;
18
use Cycle\Database\Exception\HandlerException;
19
use Cycle\Database\Exception\StatementException;
20
use Cycle\Database\Exception\SchemaException;
21
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Cycle\Database\Schema\AbstractForeignKey;
23
use Cycle\Database\Schema\AbstractIndex;
24
use Cycle\Database\Schema\AbstractTable;
25
26
class MySQLHandler extends Handler
27
{
28
    /**
29 484
     * @psalm-param non-empty-string $table
30
     */
31 484
    public function getSchema(string $table, string $prefix = null): AbstractTable
32
    {
33
        return new MySQLTable($this->driver, $table, $prefix ?? '');
0 ignored issues
show
Bug introduced by
It seems like $this->driver can also be of type null; however, parameter $driver of Cycle\Database\Driver\My...SQLTable::__construct() does only seem to accept Cycle\Database\Driver\DriverInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

33
        return new MySQLTable(/** @scrutinizer ignore-type */ $this->driver, $table, $prefix ?? '');
Loading history...
34 906
    }
35
36 906
    public function getTableNames(string $prefix = ''): array
37 906
    {
38 446
        $result = [];
39 2
        foreach ($this->driver->query('SHOW TABLES')->fetchAll(PDO::FETCH_NUM) as $row) {
0 ignored issues
show
Bug introduced by
The method query() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

39
        foreach ($this->driver->/** @scrutinizer ignore-call */ query('SHOW TABLES')->fetchAll(PDO::FETCH_NUM) as $row) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
40
            if ($prefix !== '' && !str_starts_with($row[0], $prefix)) {
41
                continue;
42 446
            }
43
44
            $result[] = $row[0];
45 906
        }
46
47
        return $result;
48
    }
49
50
    /**
51 484
     * @psalm-param non-empty-string $table
52
     */
53 484
    public function hasTable(string $table): bool
54
    {
55 484
        $query = 'SELECT COUNT(*) FROM `information_schema`.`tables` WHERE `table_schema` = ? AND `table_name` = ?';
56
57 484
        return (bool)$this->driver->query(
58 484
            $query,
59
            [$this->driver->getSource(), $table]
0 ignored issues
show
Bug introduced by
The method getSource() does not exist on Cycle\Database\Driver\DriverInterface. It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

59
            [$this->driver->/** @scrutinizer ignore-call */ getSource(), $table]
Loading history...
60
        )->fetchColumn();
61 4
    }
62
63 4
    public function eraseTable(AbstractTable $table): void
64 4
    {
65
        $this->driver->execute(
66 2
            "TRUNCATE TABLE {$this->driver->identifier($table->getFullName())}"
0 ignored issues
show
Bug introduced by
The method identifier() does not exist on Cycle\Database\Driver\DriverInterface. It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
            "TRUNCATE TABLE {$this->driver->/** @scrutinizer ignore-call */ identifier($table->getFullName())}"
Loading history...
67
        );
68 38
    }
69
70
    public function alterColumn(
71
        AbstractTable $table,
72
        AbstractColumn $initial,
73 38
        AbstractColumn $column
74 38
    ): void {
75 2
        $this->run(
76
            "ALTER TABLE {$this->identify($table)}
77
                    CHANGE {$this->identify($initial)} {$column->sqlStatement($this->driver)}"
78
        );
79
    }
80
81 38
    public function dropIndex(AbstractTable $table, AbstractIndex $index): void
82 38
    {
83 38
        $this->run(
84
            "DROP INDEX {$this->identify($index)} ON {$this->identify($table)}"
85
        );
86
    }
87 38
88
    public function alterIndex(AbstractTable $table, AbstractIndex $initial, AbstractIndex $index): void
89
    {
90 38
        $this->run(
91
            "ALTER TABLE {$this->identify($table)}
92 10
                    DROP INDEX  {$this->identify($initial)},
93
                    ADD {$index->sqlStatement($this->driver, false)}"
0 ignored issues
show
Bug introduced by
It seems like $this->driver can also be of type null; however, parameter $driver of Cycle\Database\Schema\Ab...ctIndex::sqlStatement() does only seem to accept Cycle\Database\Driver\DriverInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

93
                    ADD {$index->sqlStatement(/** @scrutinizer ignore-type */ $this->driver, false)}"
Loading history...
94 10
        );
95 10
    }
96
97 10
    public function dropForeignKey(AbstractTable $table, AbstractForeignKey $foreignKey): void
98
    {
99 2
        $this->run(
100
            "ALTER TABLE {$this->identify($table)} DROP FOREIGN KEY {$this->identify($foreignKey)}"
101 2
        );
102 2
    }
103 2
104 2
    public function isForeignKeyExists(AbstractTable $table, AbstractForeignKey $foreignKey): bool
105
    {
106 2
        try {
107
            $count = $this->driver->query(
108 56
                'SELECT count(`constraint_name`) as count FROM `information_schema`.`key_column_usage`
109
                WHERE `constraint_name` = ? AND `table_schema` = ? AND `table_name` = ?',
110 56
                [$foreignKey->getName(), $this->driver->getSource(), $table->getName()]
111 56
            )->fetchAll();
112
113 56
            return (int) $count[0]['count'] === 1;
114
        } catch (StatementException $e) {
115
            throw new HandlerException($e);
116
        }
117
    }
118
119
    /**
120 478
     * Get statement needed to create table.
121
     *
122 478
     * @throws SchemaException
123
     */
124 478
    protected function createStatement(AbstractTable $table): string
125
    {
126
        $table instanceof MySQLTable or throw new SchemaException('MySQLHandler can process only MySQL tables');
127
128
        return parent::createStatement($table) . " ENGINE {$table->getEngine()}";
0 ignored issues
show
Bug introduced by
The method getEngine() does not exist on Cycle\Database\Schema\AbstractTable. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

128
        return parent::createStatement($table) . " ENGINE {$table->/** @scrutinizer ignore-call */ getEngine()}";
Loading history...
129
    }
130 478
131
    /**
132
     * @throws MySQLException
133 478
     */
134
    protected function assertValid(AbstractColumn $column): void
135 478
    {
136
        if (
137
            $column->getDefaultValue() !== null
138
            && \in_array(
139 4
                $column->getAbstractType(),
140 4
                ['text', 'tinyText', 'longText', 'blob', 'tinyBlob', 'longBlob']
141
            )
142
        ) {
143 474
            throw new MySQLException(
144
                "Column {$column} of type text/blob can not have non empty default value"
145
            );
146
        }
147
    }
148
149
    /**
150
     * @param AbstractTable[] $tables
151
     */
152
    public function beforeSync(array $tables): void
153
    {
154
        foreach ($tables as $table) {
155
            foreach ($table->getForeignKeys() as $foreignKey) {
156
                if ($this->isForeignKeyExists($table, $foreignKey)) {
157
                    $this->dropForeignKey($table, $foreignKey);
158
                }
159
            }
160
        }
161
    }
162
163
    /**
164
     * @param AbstractTable[] $tables
165
     */
166
    public function afterSync(array $tables): void
167
    {
168
        foreach ($tables as $table) {
169
            foreach ($table->getForeignKeys() as $foreignKey) {
170
                if ($table->exists() && !$this->isForeignKeyExists($table, $foreignKey)) {
171
                    $this->createForeignKey($table, $foreignKey);
172
                }
173
            }
174
        }
175
    }
176
}
177