Получите подготовленный запрос PDOStatement [дубликат]

Вы можете использовать строку подключения следующим образом, и вам нужно только добавить имя вашей базы данных.

string connetionString = "Data Source=.;Initial Catalog=DB name;Integrated Security=True;MultipleActiveResultSets=True";
114
задан Mike 26 February 2013 в 01:17
поделиться

14 ответов

Предполагаю, вы имеете в виду, что вы хотите получить окончательный SQL-запрос с параметрическими значениями, интерполированными в него. Я понимаю, что это было бы полезно для отладки, но это не так, как работают подготовленные заявления. Параметры не объединены с подготовленным оператором на стороне клиента, поэтому PDO никогда не должен иметь доступ к строке запроса в сочетании со своими параметрами.

Оператор SQL отправляется на сервер базы данных при подготовке ( ), и параметры отправляются отдельно, когда вы выполняете (). Общий журнал запросов MySQL показывает окончательный SQL со значениями, интерполированными после выполнения (). Ниже приведен фрагмент моего основного журнала запросов. Я запускал запросы из CLI mysql, а не из PDO, но принцип тот же.

081016 16:51:28 2 Query       prepare s1 from 'select * from foo where i = ?'
                2 Prepare     [2] select * from foo where i = ?
081016 16:51:39 2 Query       set @a =1
081016 16:51:47 2 Query       execute s1 using @a
                2 Execute     [2] select * from foo where i = 1

Вы также можете получить то, что хотите, если вы установите атрибут PDO PDO :: ATTR_EMULATE_PREPARES. В этом режиме PDO интерполирует параметры в SQL-запрос и отправляет весь запрос при выполнении (). Это не настоящий подготовленный запрос. Вы обойдете преимущества подготовленных запросов путем интерполяции переменных в строку SQL перед execute ().


Re comment from @afilina:

Нет, текстовый SQL-запрос не в сочетании с параметрами во время выполнения. Таким образом, PDO не показывает вам.

Внутренне, если вы используете PDO :: ATTR_EMULATE_PREPARES, PDO создает копию SQL-запроса и интерполирует значения параметров в него перед выполнением подготовки и выполнения. Но PDO не предоставляет этот модифицированный SQL-запрос.

Объект PDOStatement имеет свойство $ queryString, но это устанавливается только в конструкторе для PDOStatement, и оно не обновляется, когда запрос переписывается с параметрами.

Это будет разумный запрос функции для PDO, чтобы попросить их разоблачить перезаписанный запрос. Но даже это не даст вам «полный» запрос, если вы не используете PDO :: ATTR_EMULATE_PREPARES.

Вот почему я показываю обходное решение выше, используя общий журнал запросов MySQL, поскольку в этом случае даже готовый запрос с записями параметров перезаписывается на сервере, а значения параметров возвращаются в строку запроса. Но это делается только во время ведения журнала, а не во время выполнения запроса.

97
ответ дан Bill Karwin 22 August 2018 в 07:09
поделиться
  • 1
    И как вы получаете запрос на отверстие, когда для параметра PDO :: ATTR_EMULATE_PREPARES установлено значение TRUE? – Yasen Zhelev 17 June 2011 в 12:54
  • 2
    @ Yasen Zhelev: Если PDO подготавливает эмуляцию, то он будет интерполировать значения параметров в запрос, прежде чем он подготавливает запрос. Поэтому MySQL никогда не видит версию запроса с записями параметров. MySQL регистрирует только полный запрос. – Bill Karwin 17 June 2011 в 18:19
  • 3
    @ Bill: «Параметры не объединены с подготовленным оператором на стороне клиента» - подождите - но объединяются ли они на стороне сервера? Или как mysql вставляет значения в БД? – Stann 21 July 2011 в 17:36
  • 4
    @afilina, нет, вы не можете. См. Мои объяснения выше. – Bill Karwin 15 September 2014 в 19:46
  • 5
    Ничего себе, нисходящая линия? Пожалуйста, не стреляйте в посланника. Я просто описываю, как это работает. – Bill Karwin 15 September 2014 в 21:13
/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public static function interpolateQuery($query, $params) {
    $keys = array();

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }
    }

    $query = preg_replace($keys, $params, $query, 1, $count);

    #trigger_error('replaced '.$count.' keys');

    return $query;
}
98
ответ дан bigwebguy 22 August 2018 в 07:09
поделиться
  • 1
    Ты полностью, потрясающе, рок! Каков ваш адрес в PayPal, чтобы я мог отправить вам пожертвование, серьезно? – Theodore R. Smith 1 October 2010 в 19:04
  • 2
    Еще раз, я прошу 3 года спустя. Как я могу заплатить вам? Эта функция в одиночку спасла мне бесчисленные часы отладки запутанных подготовленных заявлений на протяжении многих лет. Я серьезно хочу отплатить вам. – Theodore R. Smith 14 April 2012 в 20:23
  • 3
    @ TheodoreR.Smith: Вы можете предложить щедрость :) – Amal Murali 8 March 2014 в 09:18
  • 4
    Черт. Каждый раз, когда я вижу это, я хочу заплатить парню 50 долларов ;-) Это похоже на 250 долларов! – Theodore R. Smith 27 August 2014 в 20:14
  • 5
    почему бы просто не использовать strtr(): быстрее, проще и одинаковых результатов. strtr($query, $params); – Tony Chiboucas 10 October 2014 в 21:44

Добавил немного код к Mike - пройдите значения, чтобы добавить одинарные кавычки

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}
7
ответ дан Chris Go 22 August 2018 в 07:09
поделиться
  • 1
    Очень полезно, что я сделал некоторые изменения, чтобы переопределить функцию bindParam класса PDOStatement и проверить, является ли значение строкой или целым числом с PDO: PARAMS . – Sergio Flores 23 April 2013 в 16:34
  • 2
    где мы можем это увидеть? – Mawg 3 June 2014 в 10:22

Я модифицировал метод, чтобы включить обработку выходных массивов для операторов типа WHERE IN (?).

UPDATE: просто добавлена ​​проверка значения NULL и дублированных $ params, поэтому фактические значения $ param не изменяются.

Отличная работа bigwebguy и спасибо!

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    $query = preg_replace($keys, $values, $query);

    return $query;
}
27
ответ дан Etienne Martin 22 August 2018 в 07:09
поделиться
  • 1
    Я думаю, вам нужно сделать $values = $params; вместо $values = array(). – testing 5 April 2012 в 13:33
  • 2
    Еще одна небольшая часть, пропущенная здесь, - это строки. Чтобы захватить их, поставьте это выше проверки is_array: if (is_string($value)) $values[$key] = "'" . $value . "'"; – treeface 21 July 2013 в 03:17
  • 3
    Это только ограниченное значение привязки только один раз в preg_replace. добавьте эту строку после того, как $values = $params; $values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_')); добавьте это внутри сначала, если в foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);, а это в первом в foreach $values_limit = []; снова использовать значения foreach loop $ для preg_replace с помощью isset($values_limit[$key]) – vee 26 November 2015 в 07:27
  • 4
    например, значения цикла $. if (is_array($values)) { foreach ($values as $key => $val) { if (isset($values_limit[$key])) { $sql = preg_replace(['/:'.$key.'/'], [$val], $sql, $values_limit[$key], $count); } } unset($key, $val); } else { $sql = preg_replace($keys, $values, $sql, 1, $count); } – vee 26 November 2015 в 07:28

PDOStatement имеет общедоступное свойство $ queryString. Это должно быть то, что вы хотите.

Я только заметил, что у PDOStatement есть недокументированный метод debugDumpParams (), который вы также можете посмотреть.

8
ответ дан Glass Robot 22 August 2018 в 07:09
поделиться

Возможно, немного поздно, но теперь есть PDOStatement::debugDumpParams

Сбрасывает информацию, содержащуюся в подготовленной инструкции непосредственно на выходе. Он будет использовать используемый SQL-запрос, количество используемых параметров (Params), список параметров, их имя, тип (paramtype) в качестве целого, их имя или позицию ключа и позицию в запросе (если это поддерживается драйвером PDO, в противном случае он будет равен -1).

Вы можете найти более подробную информацию о официальных php docs

Пример:

<?php
/* Execute a prepared statement by binding PHP variables */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR, 12);
$sth->execute();

$sth->debugDumpParams();

?>
5
ответ дан Jimmy Kane 22 August 2018 в 07:09
поделиться

Я потратил много времени, исследуя эту ситуацию для своих собственных нужд. Это и несколько других потоков SO очень помогли мне, поэтому я хотел поделиться тем, что я придумал.

Хотя доступ к интерполированной строке запроса является значительным преимуществом при устранении неполадок, мы хотели иметь возможность для ведения журнала только определенных запросов (поэтому использование журналов базы данных для этой цели не было идеальным). Мы также хотели иметь возможность использовать журналы для воссоздания условий таблиц в любой момент времени, поэтому нам нужно было убедиться, что интерполированные строки были экранированы должным образом. Наконец, мы хотели расширить эту функциональность до всей нашей базы кода, чтобы перезаписать ее как можно меньше (сроки, маркетинг и т. Д., Вы знаете, как это делается).

Мое решение было расширить функциональность объекта PDOStatement по умолчанию для кэширования параметризованных значений (или ссылок), и когда оператор выполняется, используйте функциональные возможности объекта PDO для правильного выхода из параметров, когда они будут введены обратно в строку запроса. Затем мы могли бы связать себя, чтобы выполнить метод объекта-оператора и записать фактический запрос, который был выполнен в это время ( или, по крайней мере, как можно точнее воспроизведения) .

Как я уже сказал, мы не хотели модифицировать всю базу кода, чтобы добавить эту функциональность, поэтому мы перезаписываем стандартные методы bindParam() и bindValue() объекта PDOStatement, выполняем кэширование связанных данных, затем вызываем parent::bindParam() или parent :: bindValue(). Это позволило нашей существующей базе кода продолжать функционировать как обычно.

Наконец, когда вызывается метод execute(), мы выполняем нашу интерполяцию и предоставляем результирующую строку как новое свойство E_PDOStatement->fullQuery. Это может быть выведено для просмотра запроса или, например, записано в файл журнала.

Расширение вместе с инструкциями по установке и настройке доступно на github:

https://github.com/noahheck/E_PDOStatement

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Очевидно, как я уже упоминал, я написал это расширение. Поскольку он был разработан с помощью многих потоков здесь, я хотел опубликовать свое решение здесь, если кто-то еще сталкивается с этими потоками, как и я.

3
ответ дан myesain 22 August 2018 в 07:09
поделиться
  • 1
    Спасибо, что поделился. Нет перемотки, потому что слишком длинный ответ с слишком маленьким кодом – T30 20 December 2016 в 10:51

Ответ Майка работает хорошо, пока вы не используете значение привязки «повторное использование». Например:

SELECT * FROM `an_modules` AS `m` LEFT JOIN `an_module_sites` AS `ms` ON m.module_id = ms.module_id WHERE 1 AND `module_enable` = :module_enable AND `site_id` = :site_id AND (`module_system_name` LIKE :search OR `module_version` LIKE :search)

Ответ Майка может заменить только первый: поиск, но не второй. Итак, я переписываю свой ответ на работу с несколькими параметрами, которые могут быть повторно использованы должным образом.

public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;
    $values_limit = [];

    $words_repeated = array_count_values(str_word_count($query, 1, ':_'));

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
            $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);
        } else {
            $keys[] = '/[?]/';
            $values_limit = [];
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    if (is_array($values)) {
        foreach ($values as $key => $val) {
            if (isset($values_limit[$key])) {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, $values_limit[$key], $count);
            } else {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, 1, $count);
            }
        }
        unset($key, $val);
    } else {
        $query = preg_replace($keys, $values, $query, 1, $count);
    }
    unset($keys, $values, $values_limit, $words_repeated);

    return $query;
}
-1
ответ дан Community 22 August 2018 в 07:09
поделиться

Мне нужно записать полную строку запроса после параметра param, поэтому это часть моего кода. Надеюсь, это полезно для всех, у кого есть такая же проблема.

/**
 * 
 * @param string $str
 * @return string
 */
public function quote($str) {
    if (!is_array($str)) {
        return $this->pdo->quote($str);
    } else {
        $str = implode(',', array_map(function($v) {
                    return $this->quote($v);
                }, $str));

        if (empty($str)) {
            return 'NULL';
        }

        return $str;
    }
}

/**
 * 
 * @param string $query
 * @param array $params
 * @return string
 * @throws Exception
 */
public function interpolateQuery($query, $params) {
    $ps = preg_split("/'/is", $query);
    $pieces = [];
    $prev = null;
    foreach ($ps as $p) {
        $lastChar = substr($p, strlen($p) - 1);

        if ($lastChar != "\\") {
            if ($prev === null) {
                $pieces[] = $p;
            } else {
                $pieces[] = $prev . "'" . $p;
                $prev = null;
            }
        } else {
            $prev .= ($prev === null ? '' : "'") . $p;
        }
    }

    $arr = [];
    $indexQuestionMark = -1;
    $matches = [];

    for ($i = 0; $i < count($pieces); $i++) {
        if ($i % 2 !== 0) {
            $arr[] = "'" . $pieces[$i] . "'";
        } else {
            $st = '';
            $s = $pieces[$i];
            while (!empty($s)) {
                if (preg_match("/(\?|:[A-Z0-9_\-]+)/is", $s, $matches, PREG_OFFSET_CAPTURE)) {
                    $index = $matches[0][1];
                    $st .= substr($s, 0, $index);
                    $key = $matches[0][0];
                    $s = substr($s, $index + strlen($key));

                    if ($key == '?') {
                        $indexQuestionMark++;
                        if (array_key_exists($indexQuestionMark, $params)) {
                            $st .= $this->quote($params[$indexQuestionMark]);
                        } else {
                            throw new Exception('Wrong params in query at ' . $index);
                        }
                    } else {
                        if (array_key_exists($key, $params)) {
                            $st .= $this->quote($params[$key]);
                        } else {
                            throw new Exception('Wrong params in query with key ' . $key);
                        }
                    }
                } else {
                    $st .= $s;
                    $s = null;
                }
            }
            $arr[] = $st;
        }
    }

    return implode('', $arr);
}
0
ответ дан ducminh1903 22 August 2018 в 07:09
поделиться

Решение состоит в том, чтобы добровольно поставить ошибку в запросе и напечатать сообщение об ошибке:

//Connection to the database
$co = new PDO('mysql:dbname=myDB;host=localhost','root','');
//We allow to print the errors whenever there is one
$co->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

//We create our prepared statement
$stmt = $co->prepare("ELECT * FROM Person WHERE age=:age"); //I removed the 'S' of 'SELECT'
$stmt->bindValue(':age','18',PDO::PARAM_STR);
try {
    $stmt->execute();
} catch (PDOException $e) {
    echo $e->getMessage();
}

Стандартный вывод:

SQLSTATE [42000]: Синтаксис ошибка или нарушение доступа: [...] рядом с «ELECT * FROM Person WHERE age = 18» в строке 1

Важно отметить, что он печатает только первые 80 символов запрос.

-1
ответ дан JacopoStanchi 22 August 2018 в 07:09
поделиться

Указанное свойство $ queryString, вероятно, только вернет запрос, переданный без замены параметров на их значения. В .Net у меня есть часть catch, выполняющая простой запрос на замену параметров, с их значениями, которые были предоставлены, чтобы журнал ошибок отображал фактические значения, которые использовались для запроса. Вы должны иметь возможность перечислять параметры в PHP и заменять параметры своим назначенным значением.

1
ответ дан Kibbee 22 August 2018 в 07:09
поделиться

preg_replace не работал для меня, и когда binding_ было более 9, binding_1 и binding_10 были заменены на str_replace (оставив 0 позади), поэтому я сделал замены назад:

public function interpolateQuery($query, $params) {
$keys = array();
    $length = count($params)-1;
    for ($i = $length; $i >=0; $i--) {
            $query  = str_replace(':binding_'.(string)$i, '\''.$params[$i]['val'].'\'', $query);
           }
        // $query  = str_replace('SQL_CALC_FOUND_ROWS', '', $query, $count);
        return $query;

}

Надеюсь, кто-то сочтет это полезным.

-1
ответ дан Markos F 22 August 2018 в 07:09
поделиться

Вы можете расширить класс PDOStatement, чтобы захватить ограниченные переменные и сохранить их для последующего использования. Затем могут быть добавлены 2 метода: один для переменной sanitizing (debugBindedVariables), а другой для печати запроса с этими переменными (debugQuery):

class DebugPDOStatement extends \PDOStatement{
  private $bound_variables=array();
  protected $pdo;

  protected function __construct($pdo) {
    $this->pdo = $pdo;
  }

  public function bindValue($parameter, $value, $data_type=\PDO::PARAM_STR){
    $this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>$value);
    return parent::bindValue($parameter, $value, $data_type);
  }

  public function bindParam($parameter, &$variable, $data_type=\PDO::PARAM_STR, $length=NULL , $driver_options=NULL){
    $this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>&$variable);
    return parent::bindParam($parameter, $variable, $data_type, $length, $driver_options);
  }

  public function debugBindedVariables(){
    $vars=array();

    foreach($this->bound_variables as $key=>$val){
      $vars[$key] = $val->value;

      if($vars[$key]===NULL)
        continue;

      switch($val->type){
        case \PDO::PARAM_STR: $type = 'string'; break;
        case \PDO::PARAM_BOOL: $type = 'boolean'; break;
        case \PDO::PARAM_INT: $type = 'integer'; break;
        case \PDO::PARAM_NULL: $type = 'null'; break;
        default: $type = FALSE;
      }

      if($type !== FALSE)
        settype($vars[$key], $type);
    }

    if(is_numeric(key($vars)))
      ksort($vars);

    return $vars;
  }

  public function debugQuery(){
    $queryString = $this->queryString;

    $vars=$this->debugBindedVariables();
    $params_are_numeric=is_numeric(key($vars));

    foreach($vars as $key=>&$var){
      switch(gettype($var)){
        case 'string': $var = "'{$var}'"; break;
        case 'integer': $var = "{$var}"; break;
        case 'boolean': $var = $var ? 'TRUE' : 'FALSE'; break;
        case 'NULL': $var = 'NULL';
        default:
      }
    }

    if($params_are_numeric){
      $queryString = preg_replace_callback( '/\?/', function($match) use( &$vars) { return array_shift($vars); }, $queryString);
    }else{
      $queryString = strtr($queryString, $vars);
    }

    echo $queryString.PHP_EOL;
  }
}


class DebugPDO extends \PDO{
  public function __construct($dsn, $username="", $password="", $driver_options=array()) {
    $driver_options[\PDO::ATTR_STATEMENT_CLASS] = array('DebugPDOStatement', array($this));
    $driver_options[\PDO::ATTR_PERSISTENT] = FALSE;
    parent::__construct($dsn,$username,$password, $driver_options);
  }
}

И тогда вы можете использовать этот унаследованный класс для отладки purpouses.

$dbh = new DebugPDO('mysql:host=localhost;dbname=test;','user','pass');

$var='user_test';
$sql=$dbh->prepare("SELECT user FROM users WHERE user = :test");
$sql->bindValue(':test', $var, PDO::PARAM_STR);
$sql->execute();

$sql->debugQuery();
print_r($sql->debugBindedVariables());

Результат в

SELECT user FROM users WHERE user = 'user_test'

Array ([: test] => user_test)

2
ответ дан Otamay 22 August 2018 в 07:09
поделиться

Немного связано ... если вы просто пытаетесь дезинфицировать определенную переменную, вы можете использовать PDO :: quote . Например, для поиска нескольких частичных условий LIKE, если вы застряли в ограниченной структуре, например CakePHP:

$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);
0
ответ дан Synexis 22 August 2018 в 07:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: