diff --git a/Parser.php b/Parser.php index 382926c..718d8fa 100644 --- a/Parser.php +++ b/Parser.php @@ -1,20 +1,25 @@ + * AND t.name IN (:names_1, :names_2, :names_3, ...) + */ class Parser { - - /** - * @var string Текст sql запроса, который надо преобразовать - */ + /** @var string Текст sql запроса, который надо преобразовать, либо имя файла с запросом */ private $sql; - /** - * @var array параметры, влияющие на парсинг sql запроса - */ + /** @var array Параметры, влияющие на парсинг sql запроса */ private $params; - /** - * @var array "упрощённый" список параметров, для кеширования - */ + /** @var array "Упрощённый" список параметров (для кеширования) */ private $simplifiedParams; /** @@ -34,7 +39,8 @@ public function __construct($sql, $params = []) } /** - * @return string готовый sql запрос + * Готовый sql запрос + * @return string */ public function getSql() { @@ -50,7 +56,8 @@ public function __toString() } /** - * @return array "упрощённый" список параметров + * "Упрощённый" список параметров + * @return array */ public function getSimplifiedParams() { @@ -66,7 +73,7 @@ public function getSimplifiedParams() * @param array $params Параметры построения запроса. * @return array */ - private function simplifyParams($params) + private function simplifyParams(array $params) { if (empty($params)) { return $params; @@ -75,6 +82,7 @@ private function simplifyParams($params) $newParams = []; foreach ($params as $key => $value) { $key = ':' . ltrim($key, ':'); + if (is_array($value)) { if (!isset($value['bind'])) { $value['bind'] = true; @@ -93,9 +101,8 @@ private function simplifyParams($params) } } } elseif ($value['bind'] === 'tuple') { - if (isset($value[0]) && is_array($value[0])) { - //скинем индексы + // Скинем индексы $value[0] = array_values($value[0]); foreach ($value[0] as $valKey => $valVal) { @@ -127,9 +134,17 @@ private function simplifyParams($params) */ private function parseSql() { + $matches = null; + // Разбор многострочных комментариев - if (preg_match_all('#/\*([\w|]+)(.+?)\*/#s', $this->sql, $matches)) { + // ВАЖНО: "(.*?)" а не "(.+?)" на случай, если просто написали код такого типа: + // WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/ + // В случае "+" как комментарий будет распознана строка, включающая код: + // "*/ THEN 24 /*FLT_MANT_DIG" + // который в итоге пропадет потом из результирующей строки запроса. + if (preg_match_all('#/\*([\w|]+)(.*?)\*/#s', $this->sql, $matches)) { $count = count($matches[0]); + for ($i = 0; $i < $count; $i++) { $this->replaceComment($matches[0][$i], $matches[2][$i], $matches[1][$i]); } @@ -139,6 +154,7 @@ private function parseSql() while (true) { if (preg_match_all('#--\*([\w|]+)(.+)#', $this->sql, $matches)) { $count = count($matches[0]); + for ($i = 0; $i < $count; $i++) { $this->replaceComment($matches[0][$i], $matches[2][$i], $matches[1][$i]); } @@ -147,9 +163,10 @@ private function parseSql() } } - // разбор переменных - массивов, которые находились изначально вне комментариев + // Разбор переменных-массивов, которые находились изначально вне комментариев if (preg_match_all('#:@(\w+)#', $this->sql, $matches)) { $count = count($matches[0]); + for ($i = 0; $i < $count; $i++) { $this->replaceComment($matches[0][$i], $matches[0][$i], $matches[1][$i], false); } @@ -159,15 +176,15 @@ private function parseSql() } /** - * Заменяем коментарий или некоторую другую подстроку в запросе на соответствующе преобразованный блок или удаляем, + * Заменяем комментарий или некоторую другую подстроку в запросе на соответствующе преобразованный блок или удаляем, * если указан соответствующий параметр (делается по умолчанию - для комментариев). - * Используется также для замены параметра-массива - :@ не помещенного в комментарий, но только если такой - * параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый. + * Используется также для замены параметра-массива - :@ не помещенного в комментарий, но только если + * такой параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый. * Либо можно переименовать функцию. - * @param string $comment Заменямый комментарий. + * @param string $comment Заменяемый комментарий. * @param string $queryInComment Текст внутри комментария. * @param string $paramName Имя параметра. - * @param boolean $replaceNotFoundParam заменять ли комментарий, если не нашли соответствующего параметра в списке + * @param boolean $replaceNotFoundParam Заменять ли комментарий, если не нашли соответствующего параметра в списке */ private function replaceComment($comment, $queryInComment, $paramName, $replaceNotFoundParam = true) { @@ -189,9 +206,28 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN } elseif ($param) { $paramName = $param[0]; $paramValue = $param[1]; - if (is_array($paramValue)) { + + if (is_array($paramValue) && array_key_exists('bind', $paramValue)) { + /** Пришла пара bind/value, либо просто один bind (тогда он задается = false). */ + $bind = $paramValue['bind']; + + if ($paramValue['bind'] !== false) { + /** + * Если указано, что надо что-то биндить, то "value" обязателен. + * Но в некоторых местах его используют неправильно для "bind" => "text". + * По-правильному надо было бы там использовать "bind" => false. + */ + $value = isset($paramValue['value']) ? $paramValue['value'] : null; + } + } elseif (is_array($paramValue)) { + /** + * Значение параметра - это массив, но почему-то без элемента "bind". + * Это случай, когда у нас элементы для списка значений IN. + * Там массив значений внутри массива - т.е. первым элементом массива является массив значений + * "связанной" переменной. + */ $value = isset($paramValue[0]) ? $paramValue[0] : null; - $bind = isset($paramValue['bind']) ? $paramValue['bind'] : true; + $bind = true; } else { $value = $paramValue; $bind = true; @@ -199,9 +235,11 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN if ($bind === true && is_array($value)) { $valArr = []; + foreach (array_keys($value) as $keyVal) { $valArr[] = ':' . $paramName . '_' . $keyVal; } + $replacement = implode(',', $valArr); $queryInComment = preg_replace('/:@' . preg_quote($paramName) . '/i', $replacement, $queryInComment); } elseif ($bind === 'text') { @@ -209,26 +247,31 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN } elseif ($bind === 'tuple') { if (is_array($paramValue[0])) { $replacements = []; - //скинем индексы + // Скинем индексы $paramValue[0] = array_values($paramValue[0]); foreach ($paramValue[0] as $keyParam => $val) { $name = ':' . $paramName . '_' . $keyParam; if (is_array($val)) { $valArr = []; + foreach (array_keys($val) as $keyVal) { $valArr[] = $name . '_' . $keyVal; } + $valName = implode(',', $valArr); } else { $valName = $name; } + $replacements[] = '(' . $valName . ')'; } + $replacement = implode(',', $replacements); } else { $replacement = $paramValue; } + $queryInComment = preg_replace('/:@' . preg_quote($paramName) . '/i', $replacement, $queryInComment); } } elseif ($replaceNotFoundParam) { @@ -240,9 +283,9 @@ private function replaceComment($comment, $queryInComment, $paramName, $replaceN /** * Ищет параметр в массиве $this->params - * @param string $name имя параметра - * @return array|bool массив ['имя_параметра_без_ведущего_двоеточия', 'значение_параметра'] или ложь если параметра - * нет + * @param string $name Имя параметра + * @return array|bool Массив ['имя_параметра_без_ведущего_двоеточия', 'значение_параметра'] + * или ложь, если параметра нет */ private function getParam($name) { @@ -251,6 +294,7 @@ private function getParam($name) // Формируем имя параметра на выход точно такое же, какое и забиндено в параметры. foreach ($this->params as $key => $value) { $key = ltrim($key, ':'); + if (mb_strtolower($key) == $name) { $outName = $key; break;