Quoter   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 239
Duplicated Lines 0 %

Importance

Changes 3
Bugs 2 Features 0
Metric Value
eloc 101
dl 0
loc 239
rs 8.4
c 3
b 2
f 0
wmc 50

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B cleanUpTableNames() 0 37 9
A getRawTableName() 0 9 2
A ensureColumnName() 0 8 2
A quoteTableName() 0 21 5
A quoteSql() 0 12 2
A quoteSimpleTableName() 0 11 3
A unquoteParts() 0 11 4
A unquoteSimpleColumnName() 0 11 3
A quoteValue() 0 7 2
A quoteSimpleColumnName() 0 11 4
A unquoteSimpleTableName() 0 11 3
A getTableNameParts() 0 5 1
A ensureNameQuoted() 0 9 3
A quoteColumnName() 0 18 5
A setTablePrefix() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Quoter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Quoter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema;
6
7
use Yiisoft\Db\Exception\InvalidArgumentException;
8
use Yiisoft\Db\Expression\ExpressionInterface;
9
10
use function addcslashes;
11
use function array_slice;
12
use function count;
13
use function explode;
14
use function implode;
15
use function is_string;
16
use function preg_match;
17
use function preg_replace;
18
use function preg_replace_callback;
19
use function str_contains;
20
use function str_replace;
21
use function str_starts_with;
22
use function strrpos;
23
use function substr;
24
25
/**
26
 * The Quoter is a class that's used to quote table and column names for use in SQL statements.
27
 *
28
 * It provides a set of methods for quoting different types of names, such as table names, column names, and schema
29
 * names.
30
 *
31
 * The Quoter class is used by {@see \Yiisoft\Db\QueryBuilder\AbstractQueryBuilder} to quote names.
32
 *
33
 * It's also used by {@see \Yiisoft\Db\Command\AbstractCommand} to quote names in SQL statements before passing them to
34
 * database servers.
35
 */
36
class Quoter implements QuoterInterface
37
{
38
    public function __construct(
39
        /** @psalm-var string[]|string */
40
        private array|string $columnQuoteCharacter,
41
        /** @psalm-var string[]|string */
42
        private array|string $tableQuoteCharacter,
43
        private string $tablePrefix = ''
44
    ) {
45
    }
46
47
    public function cleanUpTableNames(array $tableNames): array
48
    {
49
        $cleanedUpTableNames = [];
50
        $pattern = <<<PATTERN
51
        ~^\s*((?:['"`\[]|{{).*?(?:['"`\]]|}})|\(.*?\)|.*?)(?:\s+(?:as\s+)?((?:['"`\[]|{{).*?(?:['"`\]]|}})|.*?))?\s*$~iux
52
        PATTERN;
53
54
        /** @psalm-var array<array-key, ExpressionInterface|string> $tableNames */
55
        foreach ($tableNames as $alias => $tableName) {
56
            if (is_string($tableName) && !is_string($alias)) {
57
                if (preg_match($pattern, $tableName, $matches)) {
58
                    if (isset($matches[2])) {
59
                        [, $tableName, $alias] = $matches;
60
                    } else {
61
                        $tableName = $alias = $matches[1];
62
                    }
63
                }
64
            }
65
66
            if (!is_string($alias)) {
67
                throw new InvalidArgumentException(
68
                    'To use Expression in from() method, pass it in array format with alias.'
69
                );
70
            }
71
72
            if (is_string($tableName)) {
73
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $this->ensureNameQuoted($tableName);
74
            } elseif ($tableName instanceof ExpressionInterface) {
75
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $tableName;
76
            } else {
77
                throw new InvalidArgumentException(
78
                    'Use ExpressionInterface without cast to string as object of tableName'
79
                );
80
            }
81
        }
82
83
        return $cleanedUpTableNames;
84
    }
85
86
    /**
87
     * Returns the actual name of a given table name.
88
     *
89
     * This method will strip off curly brackets from the given table name and replace the percentage character '%' with
90
     * {@see ConnectionInterface::tablePrefix}.
91
     *
92
     * @param string $name The table name to convert.
93
     *
94
     * @return string The real name of the given table name.
95
     */
96
    public function getRawTableName(string $name): string
97
    {
98
        if (str_contains($name, '{{')) {
99
            $name = preg_replace('/{{(.*?)}}/', '\1', $name);
100
101
            return str_replace('%', $this->tablePrefix, $name);
102
        }
103
104
        return $name;
105
    }
106
107
    public function getTableNameParts(string $name, bool $withColumn = false): array
108
    {
109
        $parts = array_slice(explode('.', $name), -2, 2);
110
111
        return $this->unquoteParts($parts, $withColumn);
112
    }
113
114
    public function ensureNameQuoted(string $name): string
115
    {
116
        $name = str_replace(["'", '"', '`', '[', ']'], '', $name);
117
118
        if ($name && !preg_match('/^{{.*}}$/', $name)) {
119
            return '{{' . $name . '}}';
120
        }
121
122
        return $name;
123
    }
124
125
    public function ensureColumnName(string $name): string
126
    {
127
        if (strrpos($name, '.') !== false) {
128
            $parts = explode('.', $name);
129
            $name = $parts[count($parts) - 1];
130
        }
131
132
        return preg_replace('|^\[\[([\w\-. ]+)]]$|', '\1', $name);
133
    }
134
135
    public function quoteColumnName(string $name): string
136
    {
137
        if (str_contains($name, '(') || str_contains($name, '[[')) {
138
            return $name;
139
        }
140
141
        if (($pos = strrpos($name, '.')) !== false) {
142
            $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
143
            $name = substr($name, $pos + 1);
144
        } else {
145
            $prefix = '';
146
        }
147
148
        if (str_contains($name, '{{')) {
149
            return $name;
150
        }
151
152
        return $prefix . $this->quoteSimpleColumnName($name);
153
    }
154
155
    public function quoteSimpleColumnName(string $name): string
156
    {
157
        if (is_string($this->columnQuoteCharacter)) {
158
            $startingCharacter = $endingCharacter = $this->columnQuoteCharacter;
159
        } else {
160
            [$startingCharacter, $endingCharacter] = $this->columnQuoteCharacter;
161
        }
162
163
        return $name === '*' || str_starts_with($name, $startingCharacter)
164
            ? $name
165
            : $startingCharacter . $name . $endingCharacter;
166
    }
167
168
    public function quoteSimpleTableName(string $name): string
169
    {
170
        if (is_string($this->tableQuoteCharacter)) {
171
            $startingCharacter = $endingCharacter = $this->tableQuoteCharacter;
172
        } else {
173
            [$startingCharacter, $endingCharacter] = $this->tableQuoteCharacter;
174
        }
175
176
        return str_starts_with($name, $startingCharacter)
177
            ? $name
178
            : $startingCharacter . $name . $endingCharacter;
179
    }
180
181
    public function quoteSql(string $sql): string
182
    {
183
        return preg_replace_callback(
184
            '/({{(%?[\w\-. ]+)%?}}|\\[\\[([\w\-. ]+)]])/',
185
            function ($matches) {
186
                if (isset($matches[3])) {
187
                    return $this->quoteColumnName($matches[3]);
188
                }
189
190
                return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
191
            },
192
            $sql
193
        );
194
    }
195
196
    public function quoteTableName(string $name): string
197
    {
198
        if (str_starts_with($name, '(')) {
199
            return $name;
200
        }
201
202
        if (str_contains($name, '{{')) {
203
            return $name;
204
        }
205
206
        if (!str_contains($name, '.')) {
207
            return $this->quoteSimpleTableName($name);
208
        }
209
210
        $parts = $this->getTableNameParts($name);
211
212
        foreach ($parts as $i => $part) {
213
            $parts[$i] = $this->quoteSimpleTableName($part);
214
        }
215
216
        return implode('.', $parts);
217
    }
218
219
    public function quoteValue(mixed $value): mixed
220
    {
221
        if (!is_string($value)) {
222
            return $value;
223
        }
224
225
        return "'" . str_replace("'", "''", addcslashes($value, "\000\032")) . "'";
226
    }
227
228
    public function setTablePrefix(string $value): void
229
    {
230
        $this->tablePrefix = $value;
231
    }
232
233
    public function unquoteSimpleColumnName(string $name): string
234
    {
235
        if (is_string($this->columnQuoteCharacter)) {
236
            $startingCharacter = $this->columnQuoteCharacter;
237
        } else {
238
            $startingCharacter = $this->columnQuoteCharacter[0];
239
        }
240
241
        return !str_starts_with($name, $startingCharacter)
242
            ? $name
243
            : substr($name, 1, -1);
244
    }
245
246
    public function unquoteSimpleTableName(string $name): string
247
    {
248
        if (is_string($this->tableQuoteCharacter)) {
249
            $startingCharacter = $this->tableQuoteCharacter;
250
        } else {
251
            $startingCharacter = $this->tableQuoteCharacter[0];
252
        }
253
254
        return !str_starts_with($name, $startingCharacter)
255
            ? $name
256
            : substr($name, 1, -1);
257
    }
258
259
    /**
260
     * @psalm-param string[] $parts Parts of table name
261
     *
262
     * @psalm-return string[]
263
     */
264
    protected function unquoteParts(array $parts, bool $withColumn): array
265
    {
266
        $lastKey = count($parts) - 1;
267
268
        foreach ($parts as $k => &$part) {
269
            $part = ($withColumn && $lastKey === $k) ?
270
                $this->unquoteSimpleColumnName($part) :
271
                $this->unquoteSimpleTableName($part);
272
        }
273
274
        return $parts;
275
    }
276
}
277