Pergunta

Existe uma maneira de obter a seqüência SQL cru executada ao chamar PDOStatement :: execute () em uma declaração preparada? Para fins de depuração isso seria extremamente útil.

Foi útil?

Solução

Eu suponho que você quer dizer que você quer que a consulta SQL final, com valores de parâmetros interpolados para ele. Eu entendo que isso seria útil para depurar, mas não é o caminho preparado declarações trabalho. Os parâmetros não são combinados com uma declaração preparada no lado do cliente, de modo DOP nunca deve ter acesso à string de consulta combinada com seus parâmetros.

A instrução SQL é enviada para o servidor de banco de dados quando você prepare (), e os parâmetros são enviados separadamente quando você execute (). log de consulta geral do MySQL não mostrar o SQL final com valores interpolados depois que você execute (). Abaixo está um trecho do meu log de consulta geral. Corri as consultas a partir do CLI mysql, não do DOP, mas o princípio é o mesmo.

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

Você também pode obter o que você quer se você definir o atributo PDO PDO :: ATTR_EMULATE_PREPARES. Neste modo, DOP parâmetros Interpolar para a consulta SQL e envia a consulta inteira quando você execute (). Esta não é uma verdadeira consulta preparada. Você vai contornar os benefícios de consultas preparadas pela interpolação variáveis ??na cadeia de SQL antes de executar ().


Re comentário de @afilina:

Não, o texto SQL consulta é não combinado com os parâmetros durante a execução. Então não há nada para DOP para mostrar a você.

Internamente, se você usar PDO :: ATTR_EMULATE_PREPARES, DOP faz uma cópia da consulta SQL e interpola parâmetro valores para ele antes de fazer a preparar e executar. Mas DOP não expõe esta consulta SQL modificado.

O objeto PDOStatement tem uma propriedade $ queryString, mas isso é definido apenas no construtor para a PDOStatement, e não é atualizada quando a consulta é reescrito com parâmetros.

Seria um pedido de recurso razoável para DOP para pedir-lhes para expor a consulta reescrito. Mas mesmo que não iria dar-lhe a consulta "completa" a menos que você use PDO :: ATTR_EMULATE_PREPARES.

É por isso que eu mostro a solução acima de usar log consulta geral do servidor MySQL, porque neste caso, mesmo uma consulta preparada com espaços reservados de parâmetro é reescrito no servidor, com valores de parâmetro preenchidos na string de consulta. Mas isso só é feito durante o registro, não durante a execução da consulta.

Outras dicas

/**
 * 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;
}

Eu modifiquei o método para incluir manipulação de saída de matrizes para as demonstrações como onde IN (?).

UPDATE: verificação Apenas adicionado para o valor NULL e duplicados $ parâmetros valores param de modo real $ não são modificados.

Great trabalho bigwebguy e obrigado!

/**
 * 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;
}

PDOStatement tem uma propriedade pública $ queryString. Deve ser o que quiser.

Acabei de perceber que PDOStatement tem um indocumentados debugDumpParams () método que você também pode querer olhar.

Um pouco tarde, provavelmente, mas agora há PDOStatement::debugDumpParams

despeja as informações contidas por uma declaração preparada diretamente no a saída. Ele irá fornecer a consulta SQL em uso, o número de parâmetros utilizados (Parâmetros), a lista de parâmetros, com o seu nome, digitar (ParamType) como um número inteiro, o seu nome de chave ou posição, e o posição na consulta (se for suportado pelo driver DOP, caso contrário, será -1).

Você pode encontrar mais informações sobre os documentação oficial do PHP

Exemplo:

<?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();

?>

Adicionado um pouco mais para o código por Mike - a pé os valores para adicionar aspas simples

/**
 * 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;
}

Eu passei uma boa parte do tempo pesquisando esta situação para minhas próprias necessidades. Este e vários outros tópicos Então me ajudou muito, então eu queria compartilhar o que eu vim acima com.

Apesar de ter acesso à string de consulta interpolada é um benefício significativo, enquanto solução de problemas, queríamos ser capaz de manter um registro de apenas determinadas consultas (portanto, usando os logs de banco de dados para este fim não era o ideal). Nós também queria ser capaz de usar os logs para recriar a condição das mesas, em determinado momento, portanto, precisamos fazer certas as cordas interpolados foram escapou corretamente. Finalmente, queria estender essa funcionalidade para toda a nossa base de código ter que re-escrever tão pouco dele quanto possível (prazos, marketing, e tal, você sabe como é).

Minha solução foi estender a funcionalidade do objeto PDOStatement padrão para armazenar em cache os valores parametrizados (ou referências), e quando a instrução é executada, utilize a funcionalidade do objeto PDO para escapar corretamente os parâmetros quando são injetados de volta no para a string de consulta. Poderíamos, então, amarrar no para executar método do objeto declaração e log a consulta real que foi executado naquele tempo ( ou pelo menos o mais fiel de uma reprodução possível) .

Como eu disse, nós não deseja modificar toda a base de código para adicionar esta funcionalidade, por isso, substituir os bindParam() e bindValue() métodos padrão do objeto PDOStatement, fazer o nosso cache dos dados vinculados, então parent::bindParam() chamada ou pai :: bindValue(). Isto permitiu que a nossa base de código existente para continuar a funcionar como normal.

Finalmente, quando o método execute() é chamado, realizamos nossa interpolação e fornecer a string resultante como uma nova E_PDOStatement->fullQuery propriedade. Esta pode ser a saída para exibir a consulta ou, por exemplo, gravados em um arquivo de log.

A extensão, juntamente com as instruções de instalação e configuração, estão disponíveis no github:

https://github.com/noahheck/E_PDOStatement

AVISO LEGAL :
Obviamente, como eu mencionei, eu escrevi esta extensão. Porque foi desenvolvido com a ajuda de muitos tópicos aqui, eu queria postar minha solução aqui no caso de alguém se depara com esses tópicos, assim como eu fiz.

Você pode estender a classe PDOStatement para capturar as variáveis ??limitadas e armazená-los para uso posterior. Então 2 métodos podem ser adicionados, um para saneantes variável (debugBindedVariables) e outro para imprimir a consulta com essas variáveis ??(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);
  }
}

E então você pode usar esta classe herdada para depurar 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());

Resultando em

user SELECT FROM usuários ONDE user = 'user_test'

Array ( [: Teste] => user_test )

A solução é colocar voluntariamente um erro na consulta e para imprimir a mensagem do erro:

//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();
}

A saída padrão:

SQLSTATE [42000]: Erro de sintaxe ou violação de acesso: [...] perto 'ELECT * FROM Pessoa ONDE idade = 18' na linha 1

É importante notar que ele imprime apenas os primeiros 80 caracteres da consulta.

A propriedade $ queryString mencionado provavelmente só irá retornar a consulta passada, sem os parâmetros substituídos por seus valores. Na Net, eu tenho a parte captura de minha consulta executer fazer uma pesquisa simples substituir sobre os parâmetros com os valores que foram fornecidas para que o log de erro pode mostrar valores reais que estavam sendo usados ??para a consulta. Você deve ser capaz de enumerar os parâmetros em PHP, e substituir os parâmetros com o seu valor atribuído.

Um pouco relacionado ... se você está apenas tentando sanear uma variável especial que você pode usar PDO :: citação . Por exemplo, para procurar várias condições parciais como se você está preso com um quadro limitado como CakePHP:

$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);

Eu preciso logar string de consulta completo depois param ligamento de modo que este é um pedaço no meu código. Esperança, é útil para o chapéu todo mundo tem o mesmo problema.

/**
 * 
 * @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);
}

Eu sei que esta questão é um pouco velho, mas, eu estou usando esse código desde tempos muito atrás (eu usei resposta de @ chris-go), e agora, estes códigos são obsoletos com PHP 7.2

Vou postar uma versão atualizada destes código (Crédito para o código principal são de @bigwebguy , @ Mike e @ chris-go , todos eles responde desta pergunta):

/**
 * 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, function(&$v, $k) { if (!is_numeric($v) && $v != "NULL") $v = "\'" . $v . "\'"; });

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

    return $query;
}

Observe a mudança no código estão em array_walk function (), substituindo create_function por uma função anônima. Este torná-los bom pedaço de código funcional e compatível com PHP 7.2 (e versões futuras esperança também).

Você pode usar sprintf(str_replace('?', '"%s"', $sql), ...$params);

Aqui está um exemplo:

function mysqli_prepared_query($link, $sql, $types='', $params=array()) {
    echo sprintf(str_replace('?', '"%s"', $sql), ...$params);
    //prepare, bind, execute
}

$link = new mysqli($server, $dbusername, $dbpassword, $database);
$sql = "SELECT firstname, lastname FROM users WHERE userage >= ? AND favecolor = ?";
$types = "is"; //integer and string
$params = array(20, "Brown");

if(!$qry = mysqli_prepared_query($link, $sql, $types, $params)){
    echo "Failed";
} else {
    echo "Success";
}

Note que este só funciona para PHP> = 5,6

resposta de Mike está trabalhando bom até que você está usando a "reutilização" ligar valor.
Por exemplo:

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)

A resposta do Mike só pode substituir em primeiro lugar: procurar, mas não o segundo
. Então, eu reescrever sua resposta ao trabalho com vários parâmetros que podem re-utilizado corretamente.

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;
}

preg_replace não funcionou para mim e quando binding_ durou mais de 9, binding_1 e binding_10 foi substituído por str_replace (deixando a 0 trás), então eu fiz as substituições para trás:

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;

}

Hope alguém acha útil.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top