Completed
Push — resets ( d2f77b )
by Paul
02:06
created

Quoter::quoteNamesIn()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3
Metric Value
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 8
nc 3
nop 1
crap 3
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\SqlQuery;
10
11
/**
12
 *
13
 * A quoting mechanism for identifier names (not values).
14
 *
15
 * @package Aura.SqlQuery
16
 *
17
 */
18
class Quoter
19
{
20
    /**
21
     *
22
     * The prefix to use when quoting identifier names.
23
     *
24
     * @var string
25
     *
26
     */
27
    protected $quote_name_prefix = '"';
28
29
    /**
30
     *
31
     * The suffix to use when quoting identifier names.
32
     *
33
     * @var string
34
     *
35
     */
36
    protected $quote_name_suffix = '"';
37
38
    /**
39
     *
40
     * Constructor.
41
     *
42
     * @param string $quote_name_prefix The prefix to use when quoting
43
     * identifier names.
44
     *
45
     * @param string $quote_name_suffix The suffix to use when quoting
46
     * identifier names.
47
     *
48
     */
49 389
    public function __construct($quote_name_prefix, $quote_name_suffix)
50
    {
51 389
        $this->quote_name_prefix = $quote_name_prefix;
52 389
        $this->quote_name_suffix = $quote_name_suffix;
53 389
    }
54
55
    /**
56
     *
57
     * Returns the prefix to use when quoting identifier names.
58
     *
59
     * @return string
60
     *
61
     */
62 241
    public function getQuoteNamePrefix()
63
    {
64 241
        return $this->quote_name_prefix;
65
    }
66
67
    /**
68
     *
69
     * Returns the suffix to use when quoting identifier names.
70
     *
71
     * @return string
72
     *
73
     */
74 241
    public function getQuoteNameSuffix()
75
    {
76 241
        return $this->quote_name_suffix;
77
    }
78
79
    /**
80
     *
81
     * Quotes a single identifier name (table, table alias, table column,
82
     * index, sequence).
83
     *
84
     * If the name contains `' AS '`, this method will separately quote the
85
     * parts before and after the `' AS '`.
86
     *
87
     * If the name contains a space, this method will separately quote the
88
     * parts before and after the space.
89
     *
90
     * If the name contains a dot, this method will separately quote the
91
     * parts before and after the dot.
92
     *
93
     * @param string $spec The identifier name to quote.
94
     *
95
     * @return string|array The quoted identifier name.
96
     *
97
     * @see replaceName()
98
     *
99
     * @see quoteNameWithSeparator()
100
     *
101
     */
102 212
    public function quoteName($spec)
103
    {
104 212
        $spec = trim($spec);
105 212
        $seps = array(' AS ', ' ', '.');
106 212
        foreach ($seps as $sep) {
107 212
            $pos = strripos($spec, $sep);
108 212
            if ($pos) {
109 26
                return $this->quoteNameWithSeparator($spec, $sep, $pos);
110
            }
111 212
        }
112 212
        return $this->replaceName($spec);
113
    }
114
115
    /**
116
     *
117
     * Quotes an identifier that has a separator.
118
     *
119
     * @param string $spec The identifier name to quote.
120
     *
121
     * @param string $sep The separator, typically a dot or space.
122
     *
123
     * @param int $pos The position of the separator.
124
     *
125
     * @return string The quoted identifier name.
126
     *
127
     */
128 26
    protected function quoteNameWithSeparator($spec, $sep, $pos)
129
    {
130 26
        $len = strlen($sep);
131 26
        $part1 = $this->quoteName(substr($spec, 0, $pos));
132 26
        $part2 = $this->replaceName(substr($spec, $pos + $len));
133 26
        return "{$part1}{$sep}{$part2}";
134
    }
135
136
    /**
137
     *
138
     * Quotes all fully-qualified identifier names ("table.col") in a string,
139
     * typically an SQL snippet for a SELECT clause.
140
     *
141
     * Does not quote identifier names that are string literals (i.e., inside
142
     * single or double quotes).
143
     *
144
     * Looks for a trailing ' AS alias' and quotes the alias as well.
145
     *
146
     * @param string $text The string in which to quote fully-qualified
147
     * identifier names to quote.
148
     *
149
     * @return string|array The string with names quoted in it.
150
     *
151
     * @see replaceNamesIn()
152
     *
153
     */
154 228
    public function quoteNamesIn($text)
155
    {
156 228
        $list = $this->getListForQuoteNamesIn($text);
157 228
        $last = count($list) - 1;
158 228
        $text = null;
159 228
        foreach ($list as $key => $val) {
160
            // skip elements 2, 5, 8, 11, etc. as artifacts of the back-
161
            // referenced split; these are the trailing/ending quote
162
            // portions, and already included in the previous element.
163
            // this is the same as skipping every third element from zero.
164 228
            if (($key+1) % 3) {
165 228
                $text .= $this->quoteNamesInLoop($val, $key == $last);
166 228
            }
167 228
        }
168 228
        return $text;
169
    }
170
171
    /**
172
     *
173
     * Returns a list of candidate elements for quoting.
174
     *
175
     * @param string $text The text to split into quoting candidates.
176
     *
177
     * @return array
178
     *
179
     */
180 228
    protected function getListForQuoteNamesIn($text)
181
    {
182
        // look for ', ", \', or \" in the string.
183
        // match closing quotes against the same number of opening quotes.
184 228
        $apos = "'";
185 228
        $quot = '"';
186 228
        return preg_split(
187 228
            "/(($apos+|$quot+|\\$apos+|\\$quot+).*?\\2)/",
188 228
            $text,
189 228
            -1,
190
            PREG_SPLIT_DELIM_CAPTURE
191 228
        );
192
    }
193
194
    /**
195
     *
196
     * The in-loop functionality for quoting identifier names.
197
     *
198
     * @param string $val The name to be quoted.
199
     *
200
     * @param bool $is_last Is this the last loop?
201
     *
202
     * @return string The quoted name.
203
     *
204
     */
205 228
    protected function quoteNamesInLoop($val, $is_last)
206
    {
207 228
        if ($is_last) {
208 228
            return $this->replaceNamesAndAliasIn($val);
209
        }
210 25
        return $this->replaceNamesIn($val);
211
    }
212
213
    /**
214
     *
215
     * Replaces the names and alias in a string.
216
     *
217
     * @param string $val The name to be quoted.
218
     *
219
     * @return string The quoted name.
220
     *
221
     */
222 228
    protected function replaceNamesAndAliasIn($val)
223
    {
224 228
        $quoted = $this->replaceNamesIn($val);
225 228
        $pos = strripos($quoted, ' AS ');
226 228
        if ($pos) {
227 11
            $alias = $this->replaceName(substr($quoted, $pos + 4));
228 11
            $quoted = substr($quoted, 0, $pos) . " AS $alias";
229 11
        }
230 228
        return $quoted;
231
    }
232
233
    /**
234
     *
235
     * Quotes an identifier name (table, index, etc); ignores empty values and
236
     * values of '*'.
237
     *
238
     * @param string $name The identifier name to quote.
239
     *
240
     * @return string The quoted identifier name.
241
     *
242
     * @see quoteName()
243
     *
244
     */
245 223
    protected function replaceName($name)
246
    {
247 223
        $name = trim($name);
248 223
        if ($name == '*') {
249 1
            return $name;
250
        }
251
252 223
        return $this->quote_name_prefix
253
             . $name
254 223
             . $this->quote_name_suffix;
255
    }
256
257
    /**
258
     *
259
     * Quotes all fully-qualified identifier names ("table.col") in a string.
260
     *
261
     * @param string $text The string in which to quote fully-qualified
262
     * identifier names to quote.
263
     *
264
     * @return string|array The string with names quoted in it.
265
     *
266
     * @see quoteNamesIn()
267
     *
268
     */
269 228
    protected function replaceNamesIn($text)
270
    {
271 228
        $is_string_literal = strpos($text, "'") !== false
272 228
                        || strpos($text, '"') !== false;
273 228
        if ($is_string_literal) {
274 25
            return $text;
275
        }
276
277 228
        $word = "[a-z_][a-z0-9_]*";
278
279 228
        $find = "/(\\b)($word)\\.($word)(\\b)/i";
280
281
        $repl = '$1'
282 228
              . $this->quote_name_prefix
283 228
              . '$2'
284 228
              . $this->quote_name_suffix
285 228
              . '.'
286 228
              . $this->quote_name_prefix
287 228
              . '$3'
288 228
              . $this->quote_name_suffix
289 228
              . '$4'
290 228
              ;
291
292 228
        $text = preg_replace($find, $repl, $text);
293
294 228
        return $text;
295
    }
296
297
}
298