Completed
Push — master ( c4e3cd...c5cae6 )
by Anton
05:38
created

Quoter::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4286
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
namespace Spiral\Database\Entities;
10
11
/**
12
 * Class responsible for "intelligent" table and column name quoting.
13
 *
14
 * Attention, Quoter does not support string literals at this moment, use FragmentInterface.
15
 */
16
class Quoter
17
{
18
    /**
19
     * Used to detect functions and expression.
20
     *
21
     * @var array
22
     */
23
    private $stops = [")", "(", " "];
24
25
    /**
26
     * Cached list of table aliases used to correctly inject prefixed tables into conditions.
27
     *
28
     * @var array
29
     */
30
    private $aliases = [];
31
32
    /**
33
     * @var PDODriver
34
     */
35
    private $driver = null;
36
37
    /**
38
     * Database prefix.
39
     *
40
     * @var string
41
     */
42
    private $prefix = '';
43
44
    /**
45
     * @param PDODriver $driver Driver needed to correctly quote identifiers and string quotes.
46
     * @param string    $prefix
47
     */
48
    public function __construct(PDODriver $driver, $prefix)
49
    {
50
        $this->driver = $driver;
51
        $this->prefix = $prefix;
52
    }
53
54
    /**
55
     * Query query identifier, if identified stated as table - table prefix must be added.
56
     *
57
     * @param string $identifier Identifier can include simple column operations and functions,
58
     *                           having "." in it will automatically force table prefix to first
59
     *                           value.
60
     * @param bool   $table      Set to true to let quote method know that identified is related
61
     *                           to table name.
62
     * @return mixed|string
63
     */
64
    public function quote($identifier, $table = false)
65
    {
66
        if (preg_match('/ AS /i', $identifier, $matches)) {
67
            list($identifier, $alias) = explode($matches[0], $identifier);
68
69
            return $this->aliasing($identifier, $alias, $table);
70
        }
71
72
        if ($this->hasExpressions($identifier)) {
73
            //Processing complex expression
74
            return $this->expression($identifier);
75
        }
76
77
        if (strpos($identifier, '.') === false) {
78
            //No table/column pair found
79
            return $this->unpaired($identifier, $table);
80
        }
81
82
        //Contain table.column statement
83
        return $this->paired($identifier);
84
    }
85
86
    /**
87
     * Quoting columns and tables in complex expression.
88
     *
89
     * @param string $identifier
90
     * @return mixed
91
     */
92
    protected function expression($identifier)
93
    {
94
        return preg_replace_callback('/([a-z][0-9_a-z\.]*\(?)/i', function ($match) {
95
            $identifier = $match[1];
96
97
            //Function name
98
            if ($this->hasExpressions($identifier)) {
99
                return $identifier;
100
            }
101
102
            return $this->quote($identifier);
103
        }, $identifier);
104
    }
105
106
    /**
107
     * Handle "IDENTIFIER AS ALIAS" expression.
108
     *
109
     * @param string $identifier
110
     * @param string $alias
111
     * @param bool   $table
112
     * @return string
113
     */
114
    protected function aliasing($identifier, $alias, $table)
115
    {
116
        $quoted = $this->quote($identifier, $table) . ' AS ' . $this->driver->identifier($alias);
117
118
        if ($table && strpos($identifier, '.') === false) {
119
            //We have to apply operation post factum to prevent self aliasing (name AS name)
120
            //when db has prefix, expected: prefix_name as name)
121
            $this->aliases[$alias] = $identifier;
122
        }
123
124
        return $quoted;
125
    }
126
127
    /**
128
     * Processing pair of table and column.
129
     *
130
     * @param string $identifier
131
     * @return string
132
     */
133
    protected function paired($identifier)
134
    {
135
        //We expecting only table and column, no database name can be included (due database isolation)
136
        list($table, $column) = explode('.', $identifier);
137
138
        return "{$this->quote($table, true)}.{$this->driver->identifier($column)}";
139
    }
140
141
    /**
142
     * Process unpaired (no . separator) identifier.
143
     *
144
     * @param string $identifier
145
     * @param bool   $table
146
     * @return string
147
     */
148
    protected function unpaired($identifier, $table)
149
    {
150
        if ($table && !isset($this->aliases[$identifier])) {
151
            if (!isset($this->aliases[$this->prefix . $identifier])) {
152
                //Generating our alias
153
                $this->aliases[$this->prefix . $identifier] = $identifier;
154
            }
155
156
            $identifier = $this->prefix . $identifier;
157
        }
158
159
        return $this->driver->identifier($identifier);
160
    }
161
162
    /**
163
     * Check if string has expression markers.
164
     *
165
     * @param string $string
166
     * @return bool
167
     */
168
    protected function hasExpressions($string)
169
    {
170
        foreach ($this->stops as $symbol) {
171
            if (strpos($string, $symbol) !== false) {
172
                return true;
173
            }
174
        }
175
176
        return false;
177
    }
178
179
    /**
180
     * Reset compiler aliases cache.
181
     *
182
     * @return $this
183
     */
184
    public function reset()
185
    {
186
        $this->aliases = [];
187
188
        return $this;
189
    }
190
}