BulkReplace   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 170
Duplicated Lines 26.47 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
dl 45
loc 170
rs 10
c 0
b 0
f 0
wmc 22
lcom 2
cbo 4

10 Methods

Rating   Name   Duplication   Size   Complexity  
B buildQuery() 30 30 2
A bindValues() 15 15 3
A getReplacedCount() 0 5 1
A getInsertedCount() 0 4 1
A checkIsKeysOnTheBeginning() 0 16 4
A checkKeys() 0 12 2
A getPrimary() 0 4 2
A getUnique() 0 4 2
A fetchPrimary() 0 14 3
A fetchUnique() 0 12 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace League\Database\BulkSql;
4
5
use PDO;
6
use PDOStatement;
7
use League\Database\Traits\BulkSqlTrait;
8
use League\Database\Exceptions\LogicException;
9
10
class BulkReplace extends Base
11
{
12
    use BulkSqlTrait;
13
14
    private $primaryKey = null;
15
16
    private $uniqueKey = null;
17
18
    private $keysChecked = false;
19
20
    const QUERY_TEMPLATE = 'REPLACE INTO %s (%s) VALUES %s';
21
22 View Code Duplication
    protected function buildQuery(): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
23
    {
24
        $this->resetFields();
25
        $this->resetPreparedItems();
26
27
        $this->checkIsKeysOnTheBeginning();
28
29
        $queryParams = $this->iterateOverItems($this->getPreparedItems(), function ($iteration) {
30
            $prepared = array_map(function ($field, $key) use ($iteration) {
31
                if (in_array($field, $this->getIgnoredColumn(), true)) {
32
                    return $this->getPreparedItems()[$iteration][$field] ?? $this->getPreparedItems()[$iteration][$key];
33
                }
34
35
                return ':'.$field.'_'.$iteration;
36
            }, $this->getFields(), array_keys($this->getFields()));
37
38
            return '('.implode(',', $prepared).')';
39
        });
40
41
        $fields = array_map(function ($field) {
42
            return "`{$field}`";
43
        }, $this->getFields());
44
45
        return sprintf(
46
            self::QUERY_TEMPLATE,
47
            $this->getTable(),
48
            implode(',', $fields),
49
            implode(',', $queryParams)
50
        );
51
    }
52
53 View Code Duplication
    protected function bindValues(PDOStatement $statement)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
54
    {
55
        $this->resetFields();
56
        $this->resetPreparedItems();
57
58
        $this->iterateOverItems($this->getPreparedItems(), function ($iteration) use ($statement) {
59
            foreach ($this->getFields() as $key => $field) {
60
                if (in_array($field, $this->getIgnoredColumn(), true)) {
61
                    return;
62
                }
63
                $value = $this->getPreparedItems()[$iteration][$field] ?? $this->getPreparedItems()[$iteration][$key];
64
                $statement->bindValue(':'.$field.'_'.$iteration, $value);
65
            }
66
        });
67
    }
68
69
    public function getReplacedCount()
70
    {
71
        // TODO: If affected same as total count, can be that all columns was replaced. Rethink this logic
72
        return $this->getAffectedCount() - $this->getTotalItemsCount();
73
    }
74
75
    public function getInsertedCount()
76
    {
77
        return $this->getTotalItemsCount() - $this->getReplacedCount();
78
    }
79
80
    /**
81
     * To replace work proper, need to check if PRIMARY KEY or UNIQUE KEY is in the beginning of array
82
     *
83
     * @throws LogicException
84
     */
85
    private function checkIsKeysOnTheBeginning()
86
    {
87
        if ($this->keysChecked) {
88
            return;
89
        }
90
91
        switch (true) {
92
            case $this->checkKeys($this->getPrimary()):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
93
            case $this->checkKeys($this->getUnique()):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
94
                break;
95
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
96
                throw new LogicException('PRIMARY or UNIQUE KEYs should be at the beginning of fields set');
97
        }
98
99
        $this->keysChecked = true;
100
    }
101
102
    /**
103
     * @param array $keys
104
     *
105
     * @return bool     True if on the beginning, False if not
106
     */
107
    private function checkKeys(array $keys) : bool
108
    {
109
        $fields = $this->getFields();
110
        $compare = [];
111
        $count = count($keys);
112
113
        for ($i = 0; $i < $count; $i++) {
114
            $compare[] = array_shift($fields);
115
        }
116
117
        return !array_diff($keys, $compare);
118
    }
119
120
    /**
121
     * Return primary key of the table
122
     *
123
     * @return array
124
     */
125
    private function getPrimary() : array
126
    {
127
        return $this->primaryKey ?: $this->fetchPrimary();
128
    }
129
130
    /**
131
     * Return primary key of the table
132
     *
133
     * @return array
134
     */
135
    private function getUnique() : array
136
    {
137
        return $this->uniqueKey ?: $this->fetchUnique();
138
    }
139
140
    /**
141
     * Fetch primary key of the table
142
     *
143
     * @return array|string
144
     * @throws LogicException
145
     */
146
    private function fetchPrimary()
147
    {
148
        $query = $this->getDbConnection()->query('SHOW KEYS FROM '.$this->getTable().' WHERE Key_name = "PRIMARY"');
149
150
        $keys = array_map(function ($item) {
151
            return $item['Column_name'];
152
        }, $query->fetchAll(PDO::FETCH_ASSOC));
153
154
        if (!count($keys)) {
155
            throw new LogicException('Impossible to use REPLACE proper in Table, that doesn\'t have PRIMARY KEYS');
156
        }
157
158
        return $this->primaryKey = count($keys) == 1 ? $keys[0] : $keys;
159
    }
160
161
    /**
162
     * Fetch unique key of the table
163
     *
164
     * @return array|string
165
     * @throws LogicException
166
     */
167
    private function fetchUnique()
168
    {
169
        $query = $this->getDbConnection()->query(
170
            "SHOW KEYS FROM {$this->getTable()} WHERE Key_name != \"PRIMARY\" AND Non_unique = 0"
171
        );
172
173
        $keys = array_map(function ($item) {
174
            return $item['Column_name'];
175
        }, $query->fetchAll(PDO::FETCH_ASSOC));
176
177
        return $this->uniqueKey = count($keys) == 1 ? $keys[0] : $keys;
178
    }
179
}
180