1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Class DbSqlPrepare |
5
|
|
|
*/ |
6
|
|
|
// TODO - Вааще всё переделать. |
7
|
|
|
// Получение стейтмента по строке - это должен быть статик |
8
|
|
|
// Тогда же должны ребиндится параметры |
9
|
|
|
class DbSqlPrepare { |
10
|
|
|
const COMMENT_PLACEHOLDER = ':__COMMENT__'; |
11
|
|
|
const DDL_REGEXP = '#(\|.+?\b)#im'; |
12
|
|
|
const PARAM_REGEXP = '#(\:.+?\b)#im'; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* SQL text |
16
|
|
|
* |
17
|
|
|
* @var string|DbSqlStatement |
18
|
|
|
*/ |
19
|
|
|
public $query = ''; |
20
|
|
|
/** |
21
|
|
|
* Array of used params :param => value |
22
|
|
|
* Each param would bind with bind_param() function |
23
|
|
|
* Used for DML part of query |
24
|
|
|
* |
25
|
|
|
* @var array $values |
26
|
|
|
*/ |
27
|
|
|
public $values = array(); |
28
|
|
|
/** |
29
|
|
|
* Array of used placeholders |param => value |
30
|
|
|
* Each placeholder would be placed into query with conversion via (string) |
31
|
|
|
* Used for dynamic queries creation for DDL part |
32
|
|
|
* |
33
|
|
|
* @var array $placeholders |
34
|
|
|
*/ |
35
|
|
|
// TODO - make use of it |
36
|
|
|
public $placeholders = array(); |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Comment for query |
40
|
|
|
* Should be quoted with SQL comment quote |
41
|
|
|
* Will be placed after query itself. Used mainly for debug purposes (should be disabled on production servers) |
42
|
|
|
* |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
// TODO - disable comments in SQL via game config |
46
|
|
|
public $comment = ''; |
47
|
|
|
|
48
|
|
|
// Prepared values for query execution |
49
|
|
|
public $queryPrepared = ''; |
50
|
|
|
public $paramsPrepared = array(); |
51
|
|
|
public $valuesPrepared = array(); |
52
|
|
|
public $valueTypesPrepared = ''; |
53
|
|
|
/** |
54
|
|
|
* @var mysqli_stmt $statement |
55
|
|
|
*/ |
56
|
|
|
public $statement; |
57
|
|
|
/** |
58
|
|
|
* Flag that params already bound |
59
|
|
|
* Setting values performed via updating $values property |
60
|
|
|
* |
61
|
|
|
* @var bool $isParamsBound |
62
|
|
|
*/ |
63
|
|
|
protected $isParamsBound = false; |
64
|
|
|
|
65
|
|
|
// /** |
66
|
|
|
// * @var ReflectionMethod |
67
|
|
|
// */ |
68
|
|
|
// public static $bindParamMethod; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @var mysqli_stmt[] |
72
|
|
|
*/ |
73
|
|
|
protected static $statements = array(); |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* DbSqlPrepare constructor. |
77
|
|
|
* |
78
|
|
|
* @param string $query |
79
|
|
|
* @param array $values |
80
|
|
|
*/ |
81
|
1 |
|
public function __construct($query, $values = array()) { |
82
|
|
|
// if(empty(static::$bindParamMethod)) { |
|
|
|
|
83
|
|
|
// $ref = new ReflectionClass('mysqli_stmt'); |
84
|
|
|
// static::$bindParamMethod = $ref->getMethod("bind_param"); |
85
|
|
|
// } |
86
|
|
|
|
87
|
1 |
|
$this->query = trim($query); |
88
|
1 |
|
$this->values = $values; |
89
|
1 |
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @param string $query |
93
|
|
|
* @param array $values |
94
|
|
|
* |
95
|
|
|
* @return static |
96
|
|
|
* |
97
|
|
|
*/ |
98
|
1 |
|
public static function build($query, $values = array()) { |
99
|
1 |
|
return new static($query, $values); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
public function setQuery($query) { |
104
|
|
|
$this->query = $query; |
105
|
|
|
|
106
|
|
|
return $this; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
protected function commentRemove() { |
110
|
|
|
if (!empty($this->values[static::COMMENT_PLACEHOLDER])) { |
111
|
|
|
unset($this->values[static::COMMENT_PLACEHOLDER]); |
112
|
|
|
$this->query = str_replace(static::COMMENT_PLACEHOLDER, '', $this->query); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
protected function commentAdd($comment) { |
117
|
|
|
if (empty($this->values[static::COMMENT_PLACEHOLDER])) { |
118
|
|
|
$this->query .= static::COMMENT_PLACEHOLDER; |
119
|
|
|
} |
120
|
|
|
$this->values[static::COMMENT_PLACEHOLDER] = $comment; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @param string $comment |
125
|
|
|
*/ |
126
|
|
|
public function comment($comment) { |
127
|
|
|
if (empty($comment)) { |
128
|
|
|
$this->commentRemove(); |
129
|
|
|
} else { |
130
|
|
|
$this->commentAdd($comment); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
return $this; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
|
137
|
|
|
// TODO - method to re-set and re-bind values |
138
|
|
|
|
139
|
|
|
|
140
|
|
|
public function compileMySqlI() { |
141
|
|
|
$this->queryPrepared = $this->query; |
|
|
|
|
142
|
|
|
$this->paramsPrepared = array(); |
143
|
|
|
$this->valuesPrepared = array(); |
144
|
|
|
$this->valueTypesPrepared = ''; |
145
|
|
|
|
146
|
|
|
if ($variableCount = preg_match_all(self::PARAM_REGEXP, $this->query, $matches, PREG_PATTERN_ORDER)) { |
|
|
|
|
147
|
|
|
$this->paramsPrepared = $matches[0]; |
148
|
|
|
if (in_array(static::COMMENT_PLACEHOLDER, $this->paramsPrepared)) { |
149
|
|
|
// Removing comment placeholder from statement |
150
|
|
|
$this->queryPrepared = str_replace(static::COMMENT_PLACEHOLDER, DbSqlHelper::quoteComment($this->comment), $this->queryPrepared); |
151
|
|
|
// Removing comment value from values list |
152
|
|
|
$this->paramsPrepared = array_filter($this->paramsPrepared, function ($value) { return $value != DbSqlPrepare::COMMENT_PLACEHOLDER; }); |
153
|
|
|
// TODO - Add comment value directly to statement |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
// Replacing actual param names with '?' - for mysqli_prepare |
157
|
|
|
$this->queryPrepared = preg_replace(self::PARAM_REGEXP, '?', $this->queryPrepared); |
|
|
|
|
158
|
|
|
|
159
|
|
|
// Now filling found params with it values |
160
|
|
|
// We can't use param names as keys 'cause same param can be met twice |
161
|
|
|
// So one key would overwrite another and total number of valuesUsed will be less then actual values used |
162
|
|
|
// TODO - move out of this proc to separate method to allow rebind of params |
163
|
|
|
foreach ($this->paramsPrepared as $key => &$value) { |
164
|
|
|
if (!key_exists($value, $this->values)) { |
165
|
|
|
// Throw exception if not key found in statement values list |
166
|
|
|
throw new Exception('DbSqlPrepare::compileMySqlI() - values array has no match for statement params'); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// Reference need for call mysqli::bind_param later in bindParam() method |
170
|
|
|
$this->valuesPrepared[$key] = &$this->values[$value]; |
171
|
|
|
|
172
|
|
|
// TODO - move out of this proc to separate method and own loop to allow rebind of params |
173
|
|
|
// i corresponding variable has type integer |
174
|
|
|
// d corresponding variable has type double |
175
|
|
|
// s corresponding variable has type string |
176
|
|
|
// b corresponding variable is a blob and will be sent in packets |
177
|
|
|
if (is_int($this->values[$value])) { |
178
|
|
|
$this->valueTypesPrepared .= 'i'; |
179
|
|
|
} elseif (is_double($this->values[$value])) { |
180
|
|
|
$this->valueTypesPrepared .= 'd'; |
181
|
|
|
} else { |
182
|
|
|
$this->valueTypesPrepared .= 's'; |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $this; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @internal param mysqli_stmt $mysqli_stmt |
193
|
|
|
*/ |
194
|
|
|
protected function bindParams() { |
195
|
|
|
if (count($this->valuesPrepared)) { |
196
|
|
|
$params = array_merge(array(&$this->valueTypesPrepared), $this->valuesPrepared); |
197
|
|
|
// static::$bindParamMethod->invokeArgs($this->statement, $params); |
|
|
|
|
198
|
|
|
call_user_func_array(array($this->statement, 'bind_param'), $params); |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* @param db_mysql $db |
204
|
|
|
* |
205
|
|
|
* @return DbSqlPrepare |
206
|
|
|
* @throws Exception |
207
|
|
|
*/ |
208
|
|
|
public function statementGet($db) { |
209
|
|
|
// TODO - к этому моменту плейсхолдеры под DDL уже должны быть заполнены соответствующими значениями |
210
|
|
|
// Надо вынести собственно prepared statement в отдельный объект, что бы здесь остались только манипуляции с полями |
211
|
|
|
$md5 = md5($this->queryPrepared); |
212
|
|
|
|
213
|
|
|
if (empty(static::$statements[$md5])) { |
214
|
|
|
if (!(static::$statements[$md5] = $db->db_prepare($this->queryPrepared))) { |
215
|
|
|
throw new Exception('DbSqlPrepare::statementGet() - can not prepare statement'); |
216
|
|
|
} |
217
|
|
|
$this->statement = static::$statements[$md5]; |
|
|
|
|
218
|
|
|
$this->bindParams(); |
219
|
|
|
} else { |
220
|
|
|
// TODO - вот тут фигня. На самом деле нельзя под один и тот же DbSqlPrepare исползовать разные mysqli_stmt |
221
|
|
|
// С другой стороны - это позволяет реюзать параметры. Так что еще вопрос - фигня ли это.... |
222
|
|
|
$this->statement = static::$statements[$md5]; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $this; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @return $this |
230
|
|
|
*/ |
231
|
|
|
public function execute() { |
232
|
|
|
$this->statement->execute(); |
233
|
|
|
|
234
|
|
|
return $this; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
public function getResult() { |
238
|
|
|
return $this->statement->get_result(); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
public function __toString() { |
242
|
|
|
// $result = str_replace(array_keys($this->tables), $this->tables, $this->query); |
|
|
|
|
243
|
|
|
$result = str_replace(array_keys($this->values), $this->values, $this->query); |
244
|
|
|
|
245
|
|
|
return $result; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
} |
249
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.