From 90fce7fa3958c28d36894449afe18f3dbe054287 Mon Sep 17 00:00:00 2001 From: Andrey Arkanov Date: Tue, 13 Nov 2018 17:25:46 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D0=BE=20todo=20-=20=D0=B2=D0=BE=D0=B7?= =?UTF-8?q?=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BC=D0=B0=D1=81=D1=81=D0=B8=D0=B2=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D0=BE=D0=BC=D0=B5=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B9.=20=D0=94=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20replaceComment.=20=D0=A2=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BB=D1=8F=20prepareSql=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D1=8B=20=D1=86=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=BC,=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D1=83=20=D1=87=D1=82=D0=BE=20=D0=BE=D0=BD=D0=B8=20=D0=B1=D1=8B?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BA=D1=80=D0=B8=D0=B2=D1=8B=D0=B5=20=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BD=D1=8F=D1=82=D1=8C=20=D0=BA=D0=B0=D0=BA=20?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=8E?= =?UTF-8?q?=D1=82=20=D0=B1=D1=8B=D0=BB=D0=BE=20=D0=BF=D0=BE=D1=87=D1=82?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B5=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE.=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D1=87=D1=83=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B0=20params=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B5,=20=D1=82.=D0=BA.=20=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=20=D0=BF=D1=80=D0=B5=D0=B4=D1=8B=D0=B4?= =?UTF-8?q?=D1=83=D1=89=D0=B8=D0=B5=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=20replaceComment=20=D0=BF=D1=80=D0=B5=D0=B4=D0=B2=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=20=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=8F=D1=8E=D1=82=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=20=D1=83=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=BC=20=D0=B8=20=D1=81=D0=BB=D0=B5=D0=B4=D1=83?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D1=85=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=85=20=D1=83=D0=B6=D0=B5=20=D0=B2=D1=81=D0=B5=20=D0=B2=20?= =?UTF-8?q?=D0=BD=D0=B8=D0=B6=D0=BD=D0=B5=D0=BC=20=D1=80=D0=B5=D0=B3=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B5,=20=D0=B0=20=D0=BD=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BB=D0=B6=D0=BD=D0=BE.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DbCommand.php | 460 ++++++++++++++++++----------------- tests/DbCommandTest.php | 514 +++++++++++++++++++++++----------------- 2 files changed, 542 insertions(+), 432 deletions(-) diff --git a/DbCommand.php b/DbCommand.php index d92b911..109d0ec 100644 --- a/DbCommand.php +++ b/DbCommand.php @@ -1,5 +1,7 @@ - * - * @TODO: Возможность биндить массивы вне блоков комментариев. - * */ class DbCommand extends \CDbCommand { - /** - * @param DbConnection $connection Соединение с базой. - * @param mixed $query SQL statement or sql file path to be executed. - * @param array $params Параметры построения запроса. - */ - public function __construct(DbConnection $connection, $query = null, $params = null) - { - parent::__construct($connection, is_array($query) ? $query : []); - if (!is_array($query)) - $this->setText($query, $params); - } - - /** - * Specifies the SQL statement or sql file to be executed. - * Any previous execution will be terminated or cancel. - * @param string $value The SQL statement or sql file to be executed. - * @param array $params Параметры построения запроса. - * @return DbCommand Для цепочечных вызовов. - */ - public function setText($value, &$params = null) - { - if (!empty($value)) { - if (substr($value, -4) === '.sql') - $value = file_get_contents($value); - - $value = $this->prepareSql($value, $params); - } - - return parent::setText($value); - } - - /** - * Конвертирует параметры запроса из расширенного формата в key=>value массив. - * @param array $params Параметры построения запроса. - * @return array - */ - protected function simplifyParams($params) - { - if (empty($params)) { - return $params; - } - - $newParams = array(); - foreach ($params as $key => $value) { - if (is_array($value) && array_key_exists('bind', $value)) { - if ($value['bind'] === true) { - if (!is_array($value['value'])) { - $newParams[$key] = $value['value']; - } else { - foreach ($value['value'] as $valKey => $valVal) - $newParams[$key . '_' . $valKey] = $valVal; - } - } - } elseif (is_array($value)) { - foreach ($value as $valKey => $valVal) - $newParams[$key . '_' . $valKey] = $valVal; - } else { - $newParams[$key] = $value; - } - } - - return $newParams; - } - - /** - * Функция разбора и подготовки текста sql запроса. - * @param string $query Запрос который нужно подготовить. - * @param array $params Параметры построения запроса. - * @return string Готовый текст sql запроса. - */ - protected function prepareSql($query, &$params = null) - { - if (empty($params)) { - return $query; - } - - // Разбор многострочных комментариев - if (preg_match_all('#/\*([\w|]+)(.+?)\*/#s', $query, $matches)) { - $count = count($matches[0]); - for ($i = 0; $i < $count; $i++) { - $query = $this->replaceComment($query, $matches[0][$i], $matches[2][$i], $matches[1][$i], $params); - } - } - - // Многоитерационный разбор однострчных комментариев - while (true) { - if (preg_match_all('#--\*([\w|]+)(.+)#', $query, $matches)) { - $count = count($matches[0]); - for ($i = 0; $i < $count; $i++) { - $query = $this->replaceComment($query, $matches[0][$i], $matches[2][$i], $matches[1][$i], $params); - } - } else { - break; - } - } - - return preg_replace("/\n+/", "\n", $query); - } - - /** - * Заменяем коментарий в запросе на соответствующе преобразованный блок или удаляем. - * @param string $query Текст запроса. - * @param string $comment Заменямый комментарий. - * @param string $queryInComment Текст внутри комментария. - * @param string $paramName Имя параметра. - * @param array $params Параметры построения запроса. - * @return string Запрос с замененным комментирием. - */ - protected function replaceComment($query, $comment, $queryInComment, $paramName, &$params) - { - $paramNameLower = mb_strtolower($paramName); - - // Формируем имя параметра точно такое же, какое и забиндено в парметры. - foreach ($params as $key => $value) { - if (mb_strtolower($key) == $paramNameLower) { - $paramName = $key; - break; - } - } - - $params = array_change_key_case($params, CASE_LOWER); - - if (strpos($paramNameLower, '|')) { - $found = false; - - foreach (explode('|', $paramNameLower) as $param) { - if (array_key_exists($param, $params)) { - $found = true; - break; - } - } - - if (!$found) { - $queryInComment = ''; - } - } elseif (array_key_exists($paramNameLower, $params)) { - $param = $params[$paramNameLower]; - $value = null; - if (is_array($param) && array_key_exists('bind', $param)) { - $bind = $param['bind']; - if ($param['bind'] !== false) { - $value = $param['value']; - } - } else { - $value = $param; - $bind = true; - } - - 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') { - $queryInComment = preg_replace('/' . preg_quote($paramName) . '/i', $value, $queryInComment); - } - } else { - $queryInComment = ''; - } - - $query = str_replace($comment, $queryInComment, $query); - - return $query; - } - - /** - * Биндинг переменных в SQL запросе. - * @param array $params Параметры построения запроса. - * @return DbCommand Для цепочечных вызовов. - */ - function bindParams(&$params) - { - if (empty($params)) { - return $this; - } - - $connect = $this->getConnection(); - - foreach ($params as $key => &$value) { - if (is_array($value) && array_key_exists('bind', $value)) { - if ($value['bind'] == true) { - $type = array_key_exists('type', $value) ? $value['type'] : null; - $length = array_key_exists('length', $value) ? $value['length'] : null; - if ($length && is_null($type) && !is_array($value['value'])) { - $type = $connect->getPdoType(gettype(($value['value']))); - } - if (!is_array($value['value'])) { - $this->bindParam($key, $value['value'], $type, $length); - } else { - foreach ($value['value'] as $valKey => &$valVal) - $this->bindParam($key . '_' . $valKey, $valVal, $type, $length); - } - } - } elseif (is_array($value)) { - foreach ($value as $valKey => &$valVal) - $this->bindParam($key . '_' . $valKey, $valVal); - } else { - $this->bindParam($key, $value); - } - } - - return $this; - } - - /** - * Биндинг значений в SQL запросе. - * @param array $values Параметры построения запроса. - * @return DbCommand Для цепочечных вызовов. - */ - public function bindValues($values) - { - $values = $this->simplifyParams($values); - - return parent::bindValues($values); - } + /** + * @param DbConnection $connection Соединение с базой. + * @param mixed $query SQL statement or sql file path to be executed. + * @param array $params Параметры построения запроса. + */ + public function __construct(DbConnection $connection, $query = null, $params = null) + { + parent::__construct($connection, is_array($query) ? $query : []); + if (!is_array($query)) { + $this->setText($query, $params); + } + } + + /** + * Specifies the SQL statement or sql file to be executed. + * Any previous execution will be terminated or cancel. + * @param string $value The SQL statement or sql file to be executed. + * @param array $params Параметры построения запроса. + * @return DbCommand Для цепочечных вызовов. + */ + public function setText($value, &$params = null) + { + if (!empty($value)) { + if (substr($value, -4) === '.sql') { + $value = file_get_contents($value); + } + + $value = $this->prepareSql($value, $params); + } + + return parent::setText($value); + } + + /** + * Конвертирует параметры запроса из расширенного формата в key=>value массив. + * @param array $params Параметры построения запроса. + * @return array + */ + protected function simplifyParams($params) + { + if (empty($params)) { + return $params; + } + + $newParams = array(); + foreach ($params as $key => $value) { + if (is_array($value) && array_key_exists('bind', $value)) { + if ($value['bind'] === true) { + if (!is_array($value['value'])) { + $newParams[$key] = $value['value']; + } else { + foreach ($value['value'] as $valKey => $valVal) { + $newParams[$key . '_' . $valKey] = $valVal; + } + } + } + } elseif (is_array($value)) { + foreach ($value as $valKey => $valVal) { + $newParams[$key . '_' . $valKey] = $valVal; + } + } else { + $newParams[$key] = $value; + } + } + + return $newParams; + } + + /** + * Функция разбора и подготовки текста sql запроса. + * @param string $query Запрос который нужно подготовить. + * @param array $params Параметры построения запроса. + * @return string Готовый текст sql запроса. + */ + protected function prepareSql($query, &$params = null) + { + if (empty($params)) { + return $query; + } + + // Разбор многострочных комментариев + if (preg_match_all('#/\*([\w|]+)(.+?)\*/#s', $query, $matches)) { + $count = count($matches[0]); + for ($i = 0; $i < $count; $i++) { + $query = $this->replaceComment($query, $matches[0][$i], $matches[2][$i], $matches[1][$i], $params); + } + } + + // Многоитерационный разбор однострочных комментариев + while (true) { + if (preg_match_all('#--\*([\w|]+)(.+)#', $query, $matches)) { + $count = count($matches[0]); + for ($i = 0; $i < $count; $i++) { + $query = $this->replaceComment($query, $matches[0][$i], $matches[2][$i], $matches[1][$i], $params); + } + } else { + break; + } + } + + // разбор переменных - массивов, которые находились изначально вне комментариев + if (preg_match_all('#:@(\w+)#', $query, $matches)) { + $count = count($matches[0]); + for ($i = 0; $i < $count; $i++) { + $query = $this->replaceComment($query, $matches[0][$i], $matches[0][$i], $matches[1][$i], $params, false); + } + } + + return preg_replace("/\n+/", "\n", $query); + } + + /** + * Заменяем коментарий и не только комментарий в запросе на соответствующе преобразованный блок или удаляем. + * Используется также для замены параметра-массива - :@ не помещенного в комментарий, но только если такой + * параметр есть в массиве параметров. Отдельную функцию делать не стали, потому что функционал одинаковый. + * Либо можно переименовать функцию. + * @param string $query Текст запроса. + * @param string $comment Заменямый комментарий. + * @param string $queryInComment Текст внутри комментария. + * @param string $paramName Имя параметра. + * @param array $params Параметры построения запроса. + * @param boolean $replaceNotFoundParam заменять ли комментарий, если не нашли соответствующего параметра в списке + * @return string Запрос с замененным комментирием. + * + * Приведение регистра ключей параметров можно было бы сделать и в prepareSql, но тогда ломаются тесты для replaceComment. + * Надо будет тогда и тесты переделывать и гарантировать, чтобы там всегда приходили только правильные параметры. + */ + protected function replaceComment($query, $comment, $queryInComment, $paramName, $params, $replaceNotFoundParam = true) + { + $paramNameLower = mb_strtolower($paramName); + + // Формируем имя параметра точно такое же, какое и забиндено в параметры. + foreach ($params as $key => $value) { + if (mb_strtolower($key) == $paramNameLower) { + $paramName = $key; + break; + } + } + + $params = array_change_key_case($params, CASE_LOWER); + + if (strpos($paramNameLower, '|')) { + $found = false; + + foreach (explode('|', $paramNameLower) as $param) { + if (array_key_exists($param, $params)) { + $found = true; + break; + } + } + + if (!$found) { + $queryInComment = ''; + } + } elseif (array_key_exists($paramNameLower, $params)) { + $param = $params[$paramNameLower]; + $value = null; + if (is_array($param) && array_key_exists('bind', $param)) { + $bind = $param['bind']; + if ($param['bind'] !== false) { + $value = $param['value']; + } + } else { + $value = $param; + $bind = true; + } + + 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') { + $queryInComment = preg_replace('/' . preg_quote($paramName) . '/i', $value, $queryInComment); + } + } elseif ($replaceNotFoundParam) { + $queryInComment = ''; + } + + $query = str_replace($comment, $queryInComment, $query); + + return $query; + } + + /** + * Биндинг переменных в SQL запросе. + * @param array $params Параметры построения запроса. + * @return DbCommand Для цепочечных вызовов. + */ + function bindParams(&$params) + { + if (empty($params)) { + return $this; + } + + $connect = $this->getConnection(); + + foreach ($params as $key => &$value) { + if (is_array($value) && array_key_exists('bind', $value)) { + if ($value['bind'] == true) { + $type = array_key_exists('type', $value) ? $value['type'] : null; + $length = array_key_exists('length', $value) ? $value['length'] : null; + if ($length && is_null($type) && !is_array($value['value'])) { + $type = $connect->getPdoType(gettype(($value['value']))); + } + if (!is_array($value['value'])) { + $this->bindParam($key, $value['value'], $type, $length); + } else { + foreach ($value['value'] as $valKey => &$valVal) { + $this->bindParam($key . '_' . $valKey, $valVal, $type, $length); + } + } + } + } elseif (is_array($value)) { + foreach ($value as $valKey => &$valVal) { + $this->bindParam($key . '_' . $valKey, $valVal); + } + } else { + $this->bindParam($key, $value); + } + } + + return $this; + } + + /** + * Биндинг значений в SQL запросе. + * @param array $values Параметры построения запроса. + * @return DbCommand Для цепочечных вызовов. + */ + public function bindValues($values) + { + $values = $this->simplifyParams($values); + + return parent::bindValues($values); + } } diff --git a/tests/DbCommandTest.php b/tests/DbCommandTest.php index 9ee3746..662dc5d 100644 --- a/tests/DbCommandTest.php +++ b/tests/DbCommandTest.php @@ -1,4 +1,5 @@ db, ''); - } - - /** - * @covers ::simplifyParams - */ - public function testSimplifyParams() - { - $params = [ - 'simpleName' => 'simpleValue1', - 'arrayName' => [0, 1, 2, 3], - 'complexNameSimpleValue' => ['bind' => true, 'value' => 'simpleValue2'], - 'complexNameNoBind' => ['bind' => false], - 'complexNameArrayValue' => ['bind' => true, 'value' => [4, 5, 6, 7]], - ]; - $simplifiedParams = [ - 'simpleName' => 'simpleValue1', - 'arrayName_0' => 0, - 'arrayName_1' => 1, - 'arrayName_2' => 2, - 'arrayName_3' => 3, - 'complexNameSimpleValue' => 'simpleValue2', - 'complexNameArrayValue_0' => 4, - 'complexNameArrayValue_1' => 5, - 'complexNameArrayValue_2' => 6, - 'complexNameArrayValue_3' => 7, - ]; - - $cmd = $this->makeCommand(); - - $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'simplifyParams'); - $method->setAccessible(true); - - $this->assertEquals($simplifiedParams, $method->invoke($cmd, $params)); - } - - /** - * @covers ::bindValues - */ - public function testBindValues() - { - $query = "SELECT 1 FROM dual WHERE :foo = 'bar'"; - $values = ['foo' => 'bar']; - - $mock = $this->getMock('\Intersvyaz\ExtendedDb\DbCommand', ['simplifyParams'], [Yii::app()->db, $query]); - - $mock->expects($this->once()) - ->method('simplifyParams') - ->will($this->returnArgument(0)) - ->with($values); - - $mock->bindValues($values); - } - - public function testBindParams() - { - $params = [ - 'simpleName' => 'simpleValue', - 'arrayName' => [4, 5, 6, 7], - 'complexNameNoBind' => ['bind' => false], - 'complexName' => ['bind' => true, 'value' => 'foo'], - 'complexNameNoType' => ['bind' => true, 'value' => 'foo', 'length' => 31337], - 'complexNameFull' => ['bind' => true, 'value' => 'foo', 'type' => PDO::PARAM_BOOL, 'length' => 31337], - 'complexNameArray' => ['bind' => true, 'value' => [7, 8, 9, 10]], - 'complexNameArrayFull' => ['bind' => true, 'value' => [4, 5, 6, 7], 'type' => PDO::PARAM_BOOL, 'length' => 31337], - ]; - - $mock = $this->getMock('\Intersvyaz\ExtendedDb\DbCommand', ['bindParam'], [Yii::app()->db, '']); - - $i = 0; - foreach ($params as $key => $value) { - if (is_array($value) && array_key_exists('bind', $value)) { - if ($value['bind'] == true) { - $type = array_key_exists('type', $value) ? $value['type'] : null; - $length = array_key_exists('length', $value) ? $value['length'] : null; - if ($length && is_null($type) && !is_array($value['value'])) { - $type = $mock->getConnection()->getPdoType(gettype(($value['value']))); - } - if (!is_array($value['value'])) { - $mock->expects($this->at($i++)) - ->method('bindParam') - ->with($key, $value['value'], $type, $length); - } else { - foreach ($value['value'] as $valKey => &$valVal) - $mock->expects($this->at($i++)) - ->method('bindParam') - ->with($key . '_' . $valKey, $valVal); - } - } - } elseif (is_array($value)) { - foreach ($value as $valKey => $valVal) { - $mock->expects($this->at($i++)) - ->method('bindParam') - ->with($key . '_' . $valKey, $valVal); - } - } else { - $mock->expects($this->at($i++)) - ->method('bindParam') - ->with($key, $value); - } - } - - $mock->bindParams($params); - } - - /** - * @covers ::prepareSql - */ - public function testPrepareSqlClearExtraNewLines() - { - $cmd = $this->makeCommand(); - - $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'prepareSql'); - $method->setAccessible(true); - - $query = "\n\n\n\n\n"; - $params = ['foo' => 'bar']; - - $this->assertEquals("\n", $method->invokeArgs($cmd, [$query, &$params])); - } - - /** - * @covers ::prepareSql - */ - public function testPrepareSql() - { - $mock = $this->getMock('\Intersvyaz\ExtendedDb\DbCommand', ['replaceComment'], [Yii::app()->db, '']); - $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'prepareSql'); - $method->setAccessible(true); - - $params = ['foo' => 'bar']; - - $query = ' - /*param1 sql1 */ - /*param2 sql2 */ - --*param3 sql3 - --*param6 --*param7 sql7 - /*param4 --*param5 sql5 */ - /*param8 --*param9 --*param10 sql10 */ - '; - $expectedArgs = [ - ['/*param1 sql1 */', 'param1', ' sql1 '], - ['/*param2 sql2 */', 'param2', ' sql2 '], - ['/*param4 --*param5 sql5 */', 'param4', ' --*param5 sql5 '], - ['/*param8 --*param9 --*param10 sql10 */', 'param8', ' --*param9 --*param10 sql10 '], - ['--*param3 sql3', 'param3', ' sql3'], - ['--*param6 --*param7 sql7', 'param6', ' --*param7 sql7'], - ['--*param5 sql5 ', 'param5', ' sql5 '], - ['--*param9 --*param10 sql10 ', 'param9', ' --*param10 sql10 '], - ['--*param7 sql7', 'param7', ' sql7'], - ['--*param10 sql10 ', 'param10', ' sql10 '], - ]; - - $i = 0; - foreach ($expectedArgs as $args) { - $mock->expects($this->at($i++)) - ->method('replaceComment') - ->will($this->returnCallback(function ($q, $c, $cq, $pn, $ps) { - return str_replace($c, $cq, $q); - })) - ->with($this->anything(), $args[0], $args[2], $args[1], $params); - } - - $method->invokeArgs($mock, [$query, &$params]); - } - - public function replaceCommentData() - { - /* $query, $comment, $queryInComment, $paramName, $params, $returnQuery */ - return [ - // paramName not listed in params - ['begin /*notInParams sql */ end', '/*notInParams sql */', ' sql ', 'notInParams', [], 'begin end'], - ['begin /*param1 sql */ end', '/*param1 sql */', ' sql ', 'param1', ['param1' => 'foobar'], 'begin sql end'], - ['begin /*param2 sql */ end', '/*param2 sql */', ' sql ', 'param2', ['param2' => 'foobar'], 'begin sql end'], - ['begin /*param3 :@param */ end', '/*param3 :@param */', ' :@param3 ', 'param3', ['param3' => [4, 5, 6]], 'begin :param3_0,:param3_1,:param3_2 end'], - ['begin /*param4 :@param */ end', '/*param4 :@param */', ' :@param4 ', 'param4', ['param4' => ['bind' => true, 'value' => [4, 5, 6]]], 'begin :param4_0,:param4_1,:param4_2 end'], - ['begin /*param5 count(*) */ end', '/*param5 count(*) */', ' count(*) ', 'param5', ['param5' => ['bind' => false]], 'begin count(*) end'], - ['begin /*OLOLO OLOLO */ end', '/*OLOLO OLOLO */', ' OLOLO ', 'OLOLO', ['OLOLO' => ['bind' => 'text', 'value' => 'WOLOLO']], 'begin WOLOLO end'], - ]; - } - - /** - * @covers ::replaceComment - * @dataProvider replaceCommentData - */ - public function testReplaceComment($query, $comment, $queryInComment, $paramName, $params, $returnQuery) - { - $cmd = $this->makeCommand(); - $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'replaceComment'); - $method->setAccessible(true); - - $this->assertEquals($returnQuery, $method->invokeArgs($cmd, [$query, $comment, $queryInComment, $paramName, &$params])); - } - - /** - * @expectedException \PHPUnit_Framework_Error - */ - public function testFileGetNotExists() - { - $command = new DbCommand(Yii::app()->db, __DIR__ . '/fakes/file_not_exists.sql'); - } - - public function testFileGet() - { - $command = new DbCommand(Yii::app()->db, __DIR__ . '/fakes/list.sql'); - $this->assertEquals('select 1 from dual where 1=1 /*id AND id=:id*/', $command->getText()); - } + /** + * @return DbCommand + */ + protected function makeCommand() + { + return new DbCommand(Yii::app()->db, ''); + } + + /** + * @covers ::simplifyParams + */ + public function testSimplifyParams() + { + $params = [ + 'simpleName' => 'simpleValue1', + 'arrayName' => [0, 1, 2, 3], + 'complexNameSimpleValue' => ['bind' => true, 'value' => 'simpleValue2'], + 'complexNameNoBind' => ['bind' => false], + 'complexNameArrayValue' => ['bind' => true, 'value' => [4, 5, 6, 7]], + ]; + $simplifiedParams = [ + 'simpleName' => 'simpleValue1', + 'arrayName_0' => 0, + 'arrayName_1' => 1, + 'arrayName_2' => 2, + 'arrayName_3' => 3, + 'complexNameSimpleValue' => 'simpleValue2', + 'complexNameArrayValue_0' => 4, + 'complexNameArrayValue_1' => 5, + 'complexNameArrayValue_2' => 6, + 'complexNameArrayValue_3' => 7, + ]; + + $cmd = $this->makeCommand(); + + $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'simplifyParams'); + $method->setAccessible(true); + + $this->assertEquals($simplifiedParams, $method->invoke($cmd, $params)); + } + + /** + * @covers ::bindValues + */ + public function testBindValues() + { + $query = "SELECT 1 FROM dual WHERE :foo = 'bar'"; + $values = ['foo' => 'bar']; + + $mock = $this->getMock('\Intersvyaz\ExtendedDb\DbCommand', ['simplifyParams'], [Yii::app()->db, $query]); + + $mock->expects($this->once()) + ->method('simplifyParams') + ->will($this->returnArgument(0)) + ->with($values); + + $mock->bindValues($values); + } + + public function testBindParams() + { + $params = [ + 'simpleName' => 'simpleValue', + 'arrayName' => [4, 5, 6, 7], + 'complexNameNoBind' => ['bind' => false], + 'complexName' => ['bind' => true, 'value' => 'foo'], + 'complexNameNoType' => ['bind' => true, 'value' => 'foo', 'length' => 31337], + 'complexNameFull' => ['bind' => true, 'value' => 'foo', 'type' => PDO::PARAM_BOOL, 'length' => 31337], + 'complexNameArray' => ['bind' => true, 'value' => [7, 8, 9, 10]], + 'complexNameArrayFull' => [ + 'bind' => true, + 'value' => [4, 5, 6, 7], + 'type' => PDO::PARAM_BOOL, + 'length' => 31337 + ], + ]; + + $mock = $this->getMock('\Intersvyaz\ExtendedDb\DbCommand', ['bindParam'], [Yii::app()->db, '']); + + $i = 0; + foreach ($params as $key => $value) { + if (is_array($value) && array_key_exists('bind', $value)) { + if ($value['bind'] == true) { + $type = array_key_exists('type', $value) ? $value['type'] : null; + $length = array_key_exists('length', $value) ? $value['length'] : null; + if ($length && is_null($type) && !is_array($value['value'])) { + $type = $mock->getConnection()->getPdoType(gettype(($value['value']))); + } + if (!is_array($value['value'])) { + $mock->expects($this->at($i++)) + ->method('bindParam') + ->with($key, $value['value'], $type, $length); + } else { + foreach ($value['value'] as $valKey => &$valVal) { + $mock->expects($this->at($i++)) + ->method('bindParam') + ->with($key . '_' . $valKey, $valVal); + } + } + } + } elseif (is_array($value)) { + foreach ($value as $valKey => $valVal) { + $mock->expects($this->at($i++)) + ->method('bindParam') + ->with($key . '_' . $valKey, $valVal); + } + } else { + $mock->expects($this->at($i++)) + ->method('bindParam') + ->with($key, $value); + } + } + + $mock->bindParams($params); + } + + /** + * @covers ::prepareSql + */ + public function testPrepareSqlClearExtraNewLines() + { + $cmd = $this->makeCommand(); + + $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'prepareSql'); + $method->setAccessible(true); + + $query = "\n\n\n\n\n"; + $params = ['foo' => 'bar']; + + $this->assertEquals("\n", $method->invokeArgs($cmd, [$query, &$params])); + } + + /** + * Данные для теста функции prepareSql + * @return array + */ + public function prepareSqlData() + { + $params = [ + 'foo' => 'bar', + 'param1' => 1, + 'paRam2' => 2, + 'param3' => 3, + 'paraM_arr' => [1, 2], + ]; + /* $query, $params, $returnQuery */ + return [ + ['/*param1 sql1 */', $params, ' sql1 '], + ['/*Param2 sql2 */', $params, ' sql2 '], + ['/*Param2 :paraM2 */', $params, ' :paraM2 '], + ['/*PARAM1 sql3 --*not_param sql5 */', $params, ' sql3 '], + ['/*ParaM1 --*param2 sql4 --*not_param sql10 */', $params, ' sql4 '], + ['--*param1 sql5', $params, ' sql5'], + [':@param1', $params, ':@param1'], + ["--*param1 sql6\n :@param_arr", $params, " sql6\n :paraM_arr_0,:paraM_arr_1"], + ["--*param1 sql7\n", $params, " sql7\n"], + ['--*param1 sql8 --*not_param --*param2 sql9', $params, ' sql8 '], + ]; + } + + /** + * @covers ::prepareSql + * @dataProvider prepareSqlData + */ + public function testPrepareSql( + $query, + $params, + $returnQuery + ) { + $cmd = $this->makeCommand(); + $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'prepareSql'); + $method->setAccessible(true); + + $this->assertEquals($returnQuery, + $method->invokeArgs($cmd, + [$query, &$params])); + } + + /** + * Данные для теста функции replaceComment + * @return array + */ + public function replaceCommentData() + { + /* $query, $comment, $queryInComment, $paramName, $params, $returnQuery */ + return [ + // paramName not listed in params + ['begin /*notInParams sql */ end', '/*notInParams sql */', ' sql ', 'notInParams', [], 'begin end'], + [ + 'begin /*param1 sql */ end', + '/*param1 sql */', + ' sql ', + 'param1', + ['param1' => 'foobar'], + 'begin sql end' + ], + [ + 'begin /*param2 sql */ end', + '/*param2 sql */', + ' sql ', + 'param2', + ['param2' => 'foobar'], + 'begin sql end' + ], + [ + 'begin :@param3 end', + ' :@param3 ', + ' :@param3 ', + 'param3', + ['param3' => [4, 5, 6]], + 'begin :param3_0,:param3_1,:param3_2 end', + false + ], + [ + 'begin :@Param3 end', + ' :@Param3 ', + ' :@Param3 ', + 'Param3', + ['paRAm3' => [4, 5, 6]], + 'begin :paRAm3_0,:paRAm3_1,:paRAm3_2 end', + false + ], + [ + 'begin :@param3 end', + ' :@param3 ', + ' :@param3 ', + 'param3', + ['param2' => [4, 5, 6]], + 'begin :@param3 end', + false + ], + [ + 'begin /*param3 :@param3 */ end', + '/*param3 :@param3 */', + ' :@param3 ', + 'param3', + ['param3' => [4, 5, 6]], + 'begin :param3_0,:param3_1,:param3_2 end' + ], + [ + 'begin /*param4 :@param4 */ end', + '/*param4 :@param4 */', + ' :@param4 ', + 'param4', + ['param4' => ['bind' => true, 'value' => [4, 5, 6]]], + 'begin :param4_0,:param4_1,:param4_2 end' + ], + [ + 'begin /*param5 count(*) */ end', + '/*param5 count(*) */', + ' count(*) ', + 'param5', + ['param5' => ['bind' => false]], + 'begin count(*) end' + ], + [ + 'begin /*OLOLO OLOLO */ end', + '/*OLOLO OLOLO */', + ' OLOLO ', + 'OLOLO', + ['OLOLO' => ['bind' => 'text', 'value' => 'WOLOLO']], + 'begin WOLOLO end' + ], + ]; + } + + /** + * @covers ::replaceComment + * @dataProvider replaceCommentData + */ + public function testReplaceComment( + $query, + $comment, + $queryInComment, + $paramName, + $params, + $returnQuery, + $replaceNotFoundParam = true + ) { + $cmd = $this->makeCommand(); + $method = new ReflectionMethod('\Intersvyaz\ExtendedDb\DbCommand', 'replaceComment'); + $method->setAccessible(true); + + $this->assertEquals($returnQuery, + $method->invokeArgs($cmd, + [$query, $comment, $queryInComment, $paramName, &$params, $replaceNotFoundParam])); + } + + /** + * @expectedException \PHPUnit_Framework_Error + */ + public function testFileGetNotExists() + { + $command = new DbCommand(Yii::app()->db, __DIR__ . '/fakes/file_not_exists.sql'); + } + + public function testFileGet() + { + $command = new DbCommand(Yii::app()->db, __DIR__ . '/fakes/list.sql'); + $this->assertEquals('select 1 from dual where 1=1 /*id AND id=:id*/', $command->getText()); + } }