11<?php
2+
23namespace Intersvyaz \SqlParser ;
34
5+ /**
6+ * Разбор текста запроса.
7+ * Получение текста по имени файла sql, парсинг специальных комментариев с именами переменных вида
8+ * /*name AND t.name = :name...
9+ * или
10+ * --*name AND t.name = :name
11+ * Замена комментариев-массивов на строку переменных с уникальными именами:
12+ * --*names AND t.name IN (:@names)
13+ * =>
14+ * AND t.name IN (:names_1, :names_2, :names_3, ...)
15+ */
416class Parser
517{
6-
7- /**
8- * @var string Текст sql запроса, который надо преобразовать
9- */
18+ /** @var string Текст sql запроса, который надо преобразовать, либо имя файла с запросом */
1019 private $ sql ;
11- /**
12- * @var array параметры, влияющие на парсинг sql запроса
13- */
20+ /** @var array Параметры, влияющие на парсинг sql запроса */
1421 private $ params ;
15- /**
16- * @var array "упрощённый" список параметров, для кеширования
17- */
22+ /** @var array "Упрощённый" список параметров (для кеширования) */
1823 private $ simplifiedParams ;
1924
2025 /**
@@ -34,7 +39,8 @@ public function __construct($sql, $params = [])
3439 }
3540
3641 /**
37- * @return string готовый sql запрос
42+ * Готовый sql запрос
43+ * @return string
3844 */
3945 public function getSql ()
4046 {
@@ -50,7 +56,8 @@ public function __toString()
5056 }
5157
5258 /**
53- * @return array "упрощённый" список параметров
59+ * "Упрощённый" список параметров
60+ * @return array
5461 */
5562 public function getSimplifiedParams ()
5663 {
@@ -66,7 +73,7 @@ public function getSimplifiedParams()
6673 * @param array $params Параметры построения запроса.
6774 * @return array
6875 */
69- private function simplifyParams ($ params )
76+ private function simplifyParams (array $ params )
7077 {
7178 if (empty ($ params )) {
7279 return $ params ;
@@ -75,6 +82,7 @@ private function simplifyParams($params)
7582 $ newParams = [];
7683 foreach ($ params as $ key => $ value ) {
7784 $ key = ': ' . ltrim ($ key , ': ' );
85+
7886 if (is_array ($ value )) {
7987 if (!isset ($ value ['bind ' ])) {
8088 $ value ['bind ' ] = true ;
@@ -93,9 +101,8 @@ private function simplifyParams($params)
93101 }
94102 }
95103 } elseif ($ value ['bind ' ] === 'tuple ' ) {
96-
97104 if (isset ($ value [0 ]) && is_array ($ value [0 ])) {
98- //скинем индексы
105+ // Скинем индексы
99106 $ value [0 ] = array_values ($ value [0 ]);
100107
101108 foreach ($ value [0 ] as $ valKey => $ valVal ) {
@@ -127,9 +134,17 @@ private function simplifyParams($params)
127134 */
128135 private function parseSql ()
129136 {
137+ $ matches = null ;
138+
130139 // Разбор многострочных комментариев
131- if (preg_match_all ('#/\*([\w|]+)(.+?)\*/#s ' , $ this ->sql , $ matches )) {
140+ // ВАЖНО: "(.*?)" а не "(.+?)" на случай, если просто написали код такого типа:
141+ // WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
142+ // В случае "+" как комментарий будет распознана строка, включающая код:
143+ // "*/ THEN 24 /*FLT_MANT_DIG"
144+ // который в итоге пропадет потом из результирующей строки запроса.
145+ if (preg_match_all ('#/\*([\w|]+)(.*?)\*/#s ' , $ this ->sql , $ matches )) {
132146 $ count = count ($ matches [0 ]);
147+
133148 for ($ i = 0 ; $ i < $ count ; $ i ++) {
134149 $ this ->replaceComment ($ matches [0 ][$ i ], $ matches [2 ][$ i ], $ matches [1 ][$ i ]);
135150 }
@@ -139,6 +154,7 @@ private function parseSql()
139154 while (true ) {
140155 if (preg_match_all ('#--\*([\w|]+)(.+)# ' , $ this ->sql , $ matches )) {
141156 $ count = count ($ matches [0 ]);
157+
142158 for ($ i = 0 ; $ i < $ count ; $ i ++) {
143159 $ this ->replaceComment ($ matches [0 ][$ i ], $ matches [2 ][$ i ], $ matches [1 ][$ i ]);
144160 }
@@ -147,9 +163,10 @@ private function parseSql()
147163 }
148164 }
149165
150- // разбор переменных - массивов, которые находились изначально вне комментариев
166+ // Разбор переменных- массивов, которые находились изначально вне комментариев
151167 if (preg_match_all ('#:@(\w+)# ' , $ this ->sql , $ matches )) {
152168 $ count = count ($ matches [0 ]);
169+
153170 for ($ i = 0 ; $ i < $ count ; $ i ++) {
154171 $ this ->replaceComment ($ matches [0 ][$ i ], $ matches [0 ][$ i ], $ matches [1 ][$ i ], false );
155172 }
@@ -159,15 +176,15 @@ private function parseSql()
159176 }
160177
161178 /**
162- * Заменяем коментарий или некоторую другую подстроку в запросе на соответствующе преобразованный блок или удаляем,
179+ * Заменяем комментарий или некоторую другую подстроку в запросе на соответствующе преобразованный блок или удаляем,
163180 * если указан соответствующий параметр (делается по умолчанию - для комментариев).
164- * Используется также для замены параметра-массива - :@<param_name> не помещенного в комментарий, но только если такой
165- * параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый.
181+ * Используется также для замены параметра-массива - :@<param_name> не помещенного в комментарий, но только если
182+ * такой параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый.
166183 * Либо можно переименовать функцию.
167- * @param string $comment Заменямый комментарий.
184+ * @param string $comment Заменяемый комментарий.
168185 * @param string $queryInComment Текст внутри комментария.
169186 * @param string $paramName Имя параметра.
170- * @param boolean $replaceNotFoundParam заменять ли комментарий, если не нашли соответствующего параметра в списке
187+ * @param boolean $replaceNotFoundParam Заменять ли комментарий, если не нашли соответствующего параметра в списке
171188 */
172189 private function replaceComment ($ comment , $ queryInComment , $ paramName , $ replaceNotFoundParam = true )
173190 {
@@ -189,46 +206,72 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN
189206 } elseif ($ param ) {
190207 $ paramName = $ param [0 ];
191208 $ paramValue = $ param [1 ];
192- if (is_array ($ paramValue )) {
209+
210+ if (is_array ($ paramValue ) && array_key_exists ('bind ' , $ paramValue )) {
211+ /** Пришла пара bind/value, либо просто один bind (тогда он задается = false). */
212+ $ bind = $ paramValue ['bind ' ];
213+
214+ if ($ paramValue ['bind ' ] !== false ) {
215+ /**
216+ * Если указано, что надо что-то биндить, то "value" обязателен.
217+ * Но в некоторых местах его используют неправильно для "bind" => "text".
218+ * По-правильному надо было бы там использовать "bind" => false.
219+ */
220+ $ value = isset ($ paramValue ['value ' ]) ? $ paramValue ['value ' ] : null ;
221+ }
222+ } elseif (is_array ($ paramValue )) {
223+ /**
224+ * Значение параметра - это массив, но почему-то без элемента "bind".
225+ * Это случай, когда у нас элементы для списка значений IN.
226+ * Там массив значений внутри массива - т.е. первым элементом массива является массив значений
227+ * "связанной" переменной.
228+ */
193229 $ value = isset ($ paramValue [0 ]) ? $ paramValue [0 ] : null ;
194- $ bind = isset ( $ paramValue [ ' bind ' ]) ? $ paramValue [ ' bind ' ] : true ;
230+ $ bind = true ;
195231 } else {
196232 $ value = $ paramValue ;
197233 $ bind = true ;
198234 }
199235
200236 if ($ bind === true && is_array ($ value )) {
201237 $ valArr = [];
238+
202239 foreach (array_keys ($ value ) as $ keyVal ) {
203240 $ valArr [] = ': ' . $ paramName . '_ ' . $ keyVal ;
204241 }
242+
205243 $ replacement = implode (', ' , $ valArr );
206244 $ queryInComment = preg_replace ('/:@ ' . preg_quote ($ paramName ) . '/i ' , $ replacement , $ queryInComment );
207245 } elseif ($ bind === 'text ' ) {
208246 $ queryInComment = preg_replace ('/ ' . preg_quote ($ paramName ) . '/i ' , $ value , $ queryInComment );
209247 } elseif ($ bind === 'tuple ' ) {
210248 if (is_array ($ paramValue [0 ])) {
211249 $ replacements = [];
212- //скинем индексы
250+ // Скинем индексы
213251 $ paramValue [0 ] = array_values ($ paramValue [0 ]);
214252
215253 foreach ($ paramValue [0 ] as $ keyParam => $ val ) {
216254 $ name = ': ' . $ paramName . '_ ' . $ keyParam ;
217255 if (is_array ($ val )) {
218256 $ valArr = [];
257+
219258 foreach (array_keys ($ val ) as $ keyVal ) {
220259 $ valArr [] = $ name . '_ ' . $ keyVal ;
221260 }
261+
222262 $ valName = implode (', ' , $ valArr );
223263 } else {
224264 $ valName = $ name ;
225265 }
266+
226267 $ replacements [] = '( ' . $ valName . ') ' ;
227268 }
269+
228270 $ replacement = implode (', ' , $ replacements );
229271 } else {
230272 $ replacement = $ paramValue ;
231273 }
274+
232275 $ queryInComment = preg_replace ('/:@ ' . preg_quote ($ paramName ) . '/i ' , $ replacement , $ queryInComment );
233276 }
234277 } elseif ($ replaceNotFoundParam ) {
@@ -240,9 +283,9 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN
240283
241284 /**
242285 * Ищет параметр в массиве $this->params
243- * @param string $name имя параметра
244- * @return array|bool массив ['имя_параметра_без_ведущего_двоеточия', 'значение_параметра'] или ложь если параметра
245- * нет
286+ * @param string $name Имя параметра
287+ * @return array|bool Массив ['имя_параметра_без_ведущего_двоеточия', 'значение_параметра']
288+ * или ложь, если параметра нет
246289 */
247290 private function getParam ($ name )
248291 {
@@ -251,6 +294,7 @@ private function getParam($name)
251294 // Формируем имя параметра на выход точно такое же, какое и забиндено в параметры.
252295 foreach ($ this ->params as $ key => $ value ) {
253296 $ key = ltrim ($ key , ': ' );
297+
254298 if (mb_strtolower ($ key ) == $ name ) {
255299 $ outName = $ key ;
256300 break ;
0 commit comments