Passed
Pull Request — master (#467)
by Alexander
10:48 queued 08:17
created

AbstractQuoter::unquoteParts()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 4
nc 5
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema;
6
7
use function addcslashes;
8
use function explode;
9
use function implode;
10
use function is_string;
11
use function preg_replace_callback;
12
use function str_contains;
13
use function str_replace;
14
use function str_starts_with;
15
use function strlen;
16
use function strpos;
17
use function strrpos;
18
use function substr;
19
20
/**
21
 * The Quoter is a class that is used to quote table and column names for use in SQL statements. It provides a set of
22
 * methods for quoting different types of names, such as table names, column names, and schema names.
23
 *
24
 * The Quoter class is used by @see \Yiisoft\Db\QueryBuilder\QueryBuilder to quote names that need to be quoted. It is
25
 * also used by @see \Yiisoft\Db\Command\Command to quote names in SQL statements before passing them to database
26
 * servers.
27
 */
28
abstract class AbstractQuoter implements QuoterInterface
29
{
30
    /**
31
     * @psalm-param string[]|string $columnQuoteCharacter The character(s) to be used for quoting column names.
32
     * @psalm-param string[]|string $tableQuoteCharacter The character(s) to be used for quoting table names.
33
     */
34
    public function __construct(
35
        private array|string $columnQuoteCharacter,
36
        private array|string $tableQuoteCharacter,
37
        private string $tablePrefix = ''
38
    ) {
39
    }
40
41
    public function getTableNameParts(string $name, bool $withColumn = false): array
42
    {
43
        $parts = array_slice(explode('.', $name), -2, 2);
44
45
        return $this->unquoteParts($parts, $withColumn);
46
    }
47
48
    public function ensureNameQuoted(string $name): string
49
    {
50
        $name = str_replace(["'", '"', '`', '[', ']'], '', $name);
51
52
        if ($name && !preg_match('/^{{.*}}$/', $name)) {
53
            return '{{' . $name . '}}';
54
        }
55
56
        return $name;
57
    }
58
59
    public function ensureColumnName(string $name): string
60
    {
61
        if (strrpos($name, '.') !== false) {
62
            $parts = explode('.', $name);
63
            $name = $parts[count($parts) - 1];
64
        }
65
66
        return preg_replace('|^\[\[([_\w\-. ]+)\]\]$|', '\1', $name);
67
    }
68
69
    public function quoteColumnName(string $name): string
70
    {
71
        if (str_contains($name, '(') || str_contains($name, '[[')) {
72
            return $name;
73
        }
74
75
        if (($pos = strrpos($name, '.')) !== false) {
76
            $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
77
            $name = substr($name, $pos + 1);
78
        } else {
79
            $prefix = '';
80
        }
81
82
        if (str_contains($name, '{{')) {
83
            return $name;
84
        }
85
86
        return $prefix . $this->quoteSimpleColumnName($name);
87
    }
88
89
    public function quoteSimpleColumnName(string $name): string
90
    {
91
        if (is_string($this->columnQuoteCharacter)) {
92
            $startingCharacter = $endingCharacter = $this->columnQuoteCharacter;
93
        } else {
94
            [$startingCharacter, $endingCharacter] = $this->columnQuoteCharacter;
95
        }
96
97
        return $name === '*' || str_contains($name, $startingCharacter) ? $name : $startingCharacter . $name
98
            . $endingCharacter;
99
    }
100
101
    public function quoteSimpleTableName(string $name): string
102
    {
103
        if (is_string($this->tableQuoteCharacter)) {
104
            $startingCharacter = $endingCharacter = $this->tableQuoteCharacter;
105
        } else {
106
            [$startingCharacter, $endingCharacter] = $this->tableQuoteCharacter;
107
        }
108
109
        return str_contains($name, $startingCharacter) ? $name : $startingCharacter . $name . $endingCharacter;
110
    }
111
112
    public function quoteSql(string $sql): string
113
    {
114
        return preg_replace_callback(
115
            '/({{(%?[\w\-. ]+%?)}}|\\[\\[([\w\-. ]+)]])/',
116
            function ($matches) {
117
                if (isset($matches[3])) {
118
                    return $this->quoteColumnName($matches[3]);
119
                }
120
121
                return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
122
            },
123
            $sql
124
        );
125
    }
126
127
    public function quoteTableName(string $name): string
128
    {
129
        if (str_starts_with($name, '(') && strpos($name, ')') === strlen($name) - 1) {
130
            return $name;
131
        }
132
133
        if (str_contains($name, '{{')) {
134
            return $name;
135
        }
136
137
        if (!str_contains($name, '.')) {
138
            return $this->quoteSimpleTableName($name);
139
        }
140
141
        $parts = $this->getTableNameParts($name);
142
143
        foreach ($parts as $i => $part) {
144
            $parts[$i] = $this->quoteSimpleTableName($part);
145
        }
146
147
        return implode('.', $parts);
148
    }
149
150
    public function quoteValue(mixed $value): mixed
151
    {
152
        if (!is_string($value)) {
153
            return $value;
154
        }
155
156
        return '\'' . str_replace('\'', '\'\'', addcslashes($value, "\000\032")) . '\'';
157
    }
158
159
    public function unquoteSimpleColumnName(string $name): string
160
    {
161
        if (is_string($this->columnQuoteCharacter)) {
162
            $startingCharacter = $this->columnQuoteCharacter;
163
        } else {
164
            $startingCharacter = $this->columnQuoteCharacter[0];
165
        }
166
167
        return !str_contains($name, $startingCharacter) ? $name : substr($name, 1, -1);
168
    }
169
170
    public function unquoteSimpleTableName(string $name): string
171
    {
172
        if (is_string($this->tableQuoteCharacter)) {
173
            $startingCharacter = $this->tableQuoteCharacter;
174
        } else {
175
            $startingCharacter = $this->tableQuoteCharacter[0];
176
        }
177
178
        return !str_contains($name, $startingCharacter) ? $name : substr($name, 1, -1);
179
    }
180
181
    /**
182
     * @param string[] $parts
183
     * @param bool $withColumn
184
     *
185
     * @return string[]
186
     */
187
    protected function unquoteParts(array $parts, bool $withColumn): array
188
    {
189
        $lastKey = count($parts) - 1;
190
191
        foreach ($parts as $k => &$part) {
192
            $part = ($withColumn || $lastKey === $k) ?
193
                $this->unquoteSimpleColumnName($part) :
194
                $this->unquoteSimpleTableName($part);
195
        }
196
197
        return $parts;
198
    }
199
}
200