Completed
Push — master ( ed54f6...6ff9a3 )
by Oleg
10s
created

DBALMysqlResource::getParsedCondition()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 35
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 27
cts 27
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 29
nc 8
nop 2
crap 8
1
<?php
2
3
namespace Maketok\DataMigration\Storage\Db;
4
5
use Doctrine\DBAL\Driver as DriverInterface;
6
use Doctrine\DBAL\Driver\PDOMySql\Driver;
7
use Doctrine\DBAL\Schema\Schema;
8
use Maketok\DataMigration\Storage\Exception\ParsingException;
9
10
class DBALMysqlResource extends AbstractDBALResource
11
{
12
    /**
13
     * @var DriverInterface
14
     */
15
    private $driver;
16
17
    /**
18
     * {@inheritdoc}
19
     */
20 31
    protected function getDriverOptions()
21
    {
22
        return [
23 31
            \PDO::MYSQL_ATTR_LOCAL_INFILE => true
24
        ];
25
    }
26
27
    /**
28
     * {@inheritdoc}
29
     */
30 2
    public function deleteUsingTempPK($deleteTable, $tmpTable, $primaryKey = 'id')
31
    {
32 2
        $sql = $this->getDeleteUsingTempPkSql($deleteTable, $tmpTable, $primaryKey);
33 2
        return $this->connection->executeUpdate($sql);
34
    }
35
36
    /**
37
     * @param string $deleteTable
38
     * @param string $tmpTable
39
     * @param string|string[] $primaryKey
40
     * @return string
41
     */
42 3
    public function getDeleteUsingTempPkSql($deleteTable, $tmpTable, $primaryKey)
43
    {
44 3
        $deleteTable = $this->connection->quoteIdentifier($deleteTable);
45 3
        $tmpTable = $this->connection->quoteIdentifier($tmpTable);
46 3
        if (!is_array($primaryKey)) {
47 2
            $primaryKey = [$primaryKey];
48
        }
49 3
        $primaryKey = array_map([$this->connection, 'quoteIdentifier'], $primaryKey);
50 3
        $conditionParts = [];
51 3
        foreach ($primaryKey as $key) {
52 3
            $conditionParts[] = "`main_table`.$key=`tmp_table`.$key";
53
        }
54 3
        $condition = implode('AND', $conditionParts);
55
        return <<<MYSQL
56 3
DELETE main_table FROM $deleteTable AS main_table
57 3
JOIN $tmpTable AS tmp_table ON $condition
58
MYSQL;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     * @param string $delimiter
64
     * @param string $enclosure
65
     * @param string $escape
66
     * @param string $termination
67
     * @param bool $optionallyEnclosed
68
     */
69 6
    public function loadData(
0 ignored issues
show
Complexity introduced by
This method has 10 parameters which exceeds the configured maximum of 10.

A high number of parameters is generally an indication that you should consider creating a dedicated object for the parameters.

Let’s take a look at an example:

<?php

class SomeClass
{
    public function doSomething(A $a, B $b, C $c, D $d, E $e, F $f)
    {
        // ..
    }
}

class AnotherClass
{
    public function doSomething(A $a, B $b, C $c, D $d, E $e, F $f)
    {
        // ...
    }
}

could be refactored to:

class Context
{
    private $a;
    private $b;
    private $c;
    private $d;
    private $e;
    private $f;

    public function __construct(A $a, B $b, C $c, D $d, E $e, F $f)
    {
        // ...
    }
}

class SomeClass
{
    public function doSomething(Context $context)
    {
        // ...
    }
}

class AnotherClass
{
    public function doSomething(Context $context)
    {
        // ...
    }
}
Loading history...
70
        $table,
71
        $file,
72
        $local = false,
73
        array $columns = [],
74
        array $set = [],
75
        $delimiter = ",",
76
        $enclosure = '"',
77
        $escape = '\\',
78
        $termination = '\n',
79
        $optionallyEnclosed = true
80
    ) {
81 6
        $sql = $this->getLoadDataSql($table, $file, $local, $columns, $set, $delimiter, $enclosure,
82 6
            $escape, $termination, $optionallyEnclosed);
83 6
        return $this->connection->executeUpdate($sql);
84
    }
85
86
    /**
87
     * @param string $table
88
     * @param string $file
89
     * @param bool|false $local
90
     * @param array $columns
91
     * @param array $set
92
     * @param string $delimiter
93
     * @param string $enclosure
94
     * @param string $escape
95
     * @param string $termination
96
     * @param bool|true $optionallyEnclosed
97
     * @return string
98
     */
99 7
    public function getLoadDataSql(
0 ignored issues
show
Complexity introduced by
This method has 10 parameters which exceeds the configured maximum of 10.

A high number of parameters is generally an indication that you should consider creating a dedicated object for the parameters.

Let’s take a look at an example:

<?php

class SomeClass
{
    public function doSomething(A $a, B $b, C $c, D $d, E $e, F $f)
    {
        // ..
    }
}

class AnotherClass
{
    public function doSomething(A $a, B $b, C $c, D $d, E $e, F $f)
    {
        // ...
    }
}

could be refactored to:

class Context
{
    private $a;
    private $b;
    private $c;
    private $d;
    private $e;
    private $f;

    public function __construct(A $a, B $b, C $c, D $d, E $e, F $f)
    {
        // ...
    }
}

class SomeClass
{
    public function doSomething(Context $context)
    {
        // ...
    }
}

class AnotherClass
{
    public function doSomething(Context $context)
    {
        // ...
    }
}
Loading history...
100
        $table,
101
        $file,
102
        $local = false,
103
        array $columns = [],
104
        array $set = [],
105
        $delimiter = ",",
106
        $enclosure = '"',
107
        $escape = "\\",
108
        $termination = '\n',
109
        $optionallyEnclosed = true
110
    ) {
111 7
        $localKey = $local ? 'LOCAL' : '';
112 7
        $table = $this->connection->quoteIdentifier($table);
113 7
        $optionalKey = $optionallyEnclosed ? 'OPTIONALLY' : '';
114 7
        if (!empty($columns)) {
115 1
            $columns = '(' . implode(',', $columns) . ')';
116
        } else {
117 6
            $columns = '';
118
        }
119 7
        if (!empty($set)) {
120 1
            $setParts = [];
121 1
            foreach ($set as $key => $val) {
122 1
                $setParts[] = "$key=$val";
123
            }
124 1
            $set = 'SET ' . implode(',', $setParts);
125
        } else {
126 6
            $set = '';
127
        }
128 7
        $escape = $this->connection->quote($escape);
129
        return <<<MYSQL
130 7
LOAD DATA $localKey INFILE '$file'
131 7
INTO TABLE $table
132
FIELDS
133 7
    TERMINATED BY '$delimiter'
134 7
    $optionalKey ENCLOSED BY '$enclosure'
135 7
    ESCAPED BY $escape
136
LINES
137 7
    TERMINATED BY '$termination'
138 7
$columns
139 7
$set
140
MYSQL;
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 9
    public function move(
147
        $fromTable,
148
        $toTable,
149
        array $columns = [],
150
        array $conditions = [],
151
        array $orderBy = [],
152
        $dir = 'ASC'
153
    ) {
154 9
        $sql = $this->getMoveSql($fromTable, $toTable, $columns, $conditions, $orderBy, $dir);
155 9
        return $this->connection->executeUpdate($sql);
156
    }
157
158
    /**
159
     * @param string $fromTable
160
     * @param string $toTable
161
     * @param array $columns
162
     * @param array $conditions
163
     * @param array $orderBy
164
     * @param string $dir
165
     * @return string
166
     */
167 18
    public function getMoveSql(
168
        $fromTable,
169
        $toTable,
170
        array $columns = [],
171
        array $conditions = [],
172
        array $orderBy = [],
173
        $dir = 'ASC'
174
    ) {
175 18
        $selectColumns = '*';
176 18
        $onDuplicate = '';
177 18
        $fromTable = $this->connection->quoteIdentifier($fromTable);
178 18
        $toTable = $this->connection->quoteIdentifier($toTable);
179 18
        if (!empty($columns)) {
180 18
            $columns = array_map([$this->connection, 'quoteIdentifier'], $columns);
181 18
            $selectColumns = implode(',', $columns);
182 18
            $duplicateParts = array_map(function ($var) {
183 18
                return "$var=VALUES($var)";
184 18
            }, $columns);
185 18
            $columns = '(' . $selectColumns . ')';
186 18
            $onDuplicate = 'ON DUPLICATE KEY UPDATE ' . implode(',', $duplicateParts);
187
        } else {
188
            $columns = '';
189
        }
190 18
        if (!empty($conditions)) {
191 10
            $conditionParts = [];
192 10
            foreach ($conditions as $key => $val) {
193 10
                $key = $this->connection->quoteIdentifier($key);
194 10
                if (is_array($val)) {
195 9
                    $conditionParts[] = $this->getParsedCondition("$fromTable.$key", $val);
196
                } else {
197 1
                    $val = $this->connection->quote($val);
198 6
                    $conditionParts[] = "$fromTable.$key=$val";
199
                }
200
            }
201 6
            $conditions = 'WHERE ' . implode('AND', $conditionParts);
202
        } else {
203 8
            $conditions = '';
204
        }
205 14
        if (!empty($orderBy)) {
206 8
            $orderBy = array_map([$this->connection, 'quoteIdentifier'], $orderBy);
207 8
            $orderBy = implode(',', $orderBy);
208
        } else {
209 6
            $orderBy = 'NULL';
210
        }
211
        return <<<MYSQL
212 14
INSERT INTO $toTable $columns
213 14
SELECT $selectColumns FROM $fromTable
214 14
$conditions
215 14
ORDER BY $orderBy $dir
216 14
$onDuplicate
217
MYSQL;
218
    }
219
220
    /**
221
     * Return valid condition
222
     * @param $column
223
     * @param array $value
224
     * @throws ParsingException
225
     * @return string
226
     */
227 9
    protected function getParsedCondition($column, array $value)
228
    {
229 9
        if (count($value) > 1) {
230 1
            throw new ParsingException("Condition should contain only 1 element");
231
        }
232 8
        $operation = key($value);
233 8
        $actualValue = current($value);
234
        switch ($operation) {
235 8
            case 'neq':
236 1
                $string = "$column<>{$this->connection->quote($actualValue)}";
237 1
                break;
238 7
            case 'eq':
239 1
                $string = "$column={$this->connection->quote($actualValue)}";
240 1
                break;
241 6
            case 'in':
242 2
                if (!is_array($actualValue)) {
243 1
                    throw new ParsingException("Can not use 'in' operation with non array.");
244
                }
245 1
                $actualValue = array_map([$this->connection, 'quote'], $actualValue);
246 1
                $glued = implode(',', $actualValue);
247 1
                $string = "$column in ($glued)";
248 1
                break;
249 4
            case 'nin':
250 3
                if (!is_array($actualValue)) {
251 1
                    throw new ParsingException("Can not use 'nin' operation with non array.");
252
                }
253 2
                $actualValue = array_map([$this->connection, 'quote'], $actualValue);
254 2
                $glued = implode(',', $actualValue);
255 2
                $string = "$column not in ($glued)";
256 2
                break;
257
            default:
258 1
                throw new ParsingException(sprintf("Could not resolve condition operation %s.", $operation));
259
        }
260 5
        return $string;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266 3
    public function dumpData($table, array $columns = [], $limit = 1000, $offset = 0)
267
    {
268 3
        $sql = $this->getDumpDataSql($table, $columns);
269 3
        $stmt = $this->connection->prepare($sql);
270 3
        $stmt->bindValue(1, $limit, \PDO::PARAM_INT);
271 3
        $stmt->bindValue(2, $offset, \PDO::PARAM_INT);
272 3
        $res = $stmt->execute();
273 3
        if ($res === false) {
274
            return false;
275
        }
276 3
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
277
    }
278
279
    /**
280
     * @param string $table
281
     * @param array $columns
282
     * @return string
283
     */
284 4
    public function getDumpDataSql($table, array $columns = [])
285
    {
286 4
        $table = $this->connection->quoteIdentifier($table);
287 4
        if (!empty($columns)) {
288 4
            $columns = array_map([$this->connection, 'quoteIdentifier'], $columns);
289 4
            $columns = implode(',', $columns);
290
        } else {
291 1
            $columns = '*';
292
        }
293
        return <<<MYSQL
294 4
SELECT $columns FROM $table
295
LIMIT ? OFFSET ?
296
MYSQL;
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302 11
    public function createTmpTable($name, array $columns)
303
    {
304 11
        $sql = $this->getCreateTableSql($name, $columns);
305 11
        foreach ($sql as $directive) {
306 11
            $this->connection->executeUpdate($directive);
307
        }
308 11
        return true;
309
    }
310
311
    /**
312
     * @param string $name
313
     * @param array $columns
314
     * @return array
315
     * @throws \Doctrine\DBAL\DBALException
316
     */
317 12
    public function getCreateTableSql($name, array $columns)
318
    {
319 12
        $schema = new Schema();
320 12
        $table = $schema->createTable($name);
321 12
        foreach ($columns as $column => $definition) {
322 12
            if (is_array($definition)) {
323 8
                $type = array_shift($definition);
324 8
                $options = array_shift($definition);
325
            } else {
326 5
                $type = $definition;
327 5
                $options = [];
328
            }
329 12
            $table->addColumn($column, $type, $options);
330
        }
331 12
        if (!$this->config['db_debug']) {
332 12
            $table->addOption('temporary', true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

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...
333
        }
334 12
        return $this->connection->getDatabasePlatform()->getCreateTableSQL($table);
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340 31
    protected function getDriver()
341
    {
342 31
        if (is_null($this->driver)) {
343 31
            $driver = $this->config['db_driver'];
344 31
            if ($driver && $driver instanceof DriverInterface) {
345 13
                $this->driver = $driver;
346
            } else {
347 18
                $this->driver = new Driver();
348
            }
349
        }
350 31
        return $this->driver;
351
    }
352
}
353