Doutrina - Como imprimir o sql real, não apenas a declaração preparada?

167

Estamos usando o Doctrine, um ORM PHP. Estou criando uma consulta como esta:

$q = Doctrine_Query::create()->select('id')->from('MyTable');

e então na função eu estou adicionando várias cláusulas where e coisas conforme apropriado, como este

$q->where('normalisedname = ? OR name = ?', array($string, $originalString));

Mais tarde, antes de execute()criar esse objeto de consulta, quero imprimir o SQL bruto para examiná-lo e fazer o seguinte:

$q->getSQLQuery();

No entanto, isso apenas imprime a instrução preparada, não a consulta completa. Eu quero ver o que está enviando para o MySQL, mas, em vez disso, está imprimindo uma declaração preparada, incluindo ?'s. Existe alguma maneira de ver a consulta 'completa'?

Rory
fonte
A melhor maneira que eu encontrei para ver consulta completa é descrito nesta resposta: stackoverflow.com/a/678310/229077
Marek
Você pode tirar proveito do trabalho realizado pelo Doctrine (o criador de perfil está exibindo uma consulta executável). Veja minha resposta abaixo para obter detalhes
Vincent Pazeller

Respostas:

164

A doutrina não está enviando uma "consulta SQL real" ao servidor de banco de dados: na verdade, ela está usando instruções preparadas, o que significa:

  • Enviando a declaração, para que ela seja preparada (é isso que é retornado por $query->getSql())
  • E, então, enviando os parâmetros (retornados por $query->getParameters())
  • e executando as instruções preparadas

Isso significa que nunca há uma consulta SQL "real" no lado do PHP - portanto, o Doctrine não pode exibi-la.

Pascal MARTIN
fonte
14
Pascal: você não deve dizer que não é uma "consulta SQL real" porque a instrução preparada é uma consulta SQL real, apenas os parâmetros enviados separadamente. Esta redação pode confundir as pessoas (por exemplo, olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients ).
Matthieu Napoli
$query->getParameters();NÃO irá retornar parâmetros na ordem correta, como eles devem aparecer na declaração consulta preparada
gôndola
4
Acho que aqui o autor da pergunta não se importava com o que a doutrina envia ou não. O que eu e o usuário queríamos saber é como obter consultas que podemos copiar, colar e executar sem precisar substituir manualmente pontos de interrogação por parâmetros. Como no codeigniter. Acho que encontrei isso no depurador symfony, mas ainda não consigo encontrar quando executo o script na linha de comando.
precisa saber é o seguinte
104

Um exemplo de trabalho:

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();
Andy.Diaz
fonte
5
Embora funcione como atribuições de variáveis, você pode considerar isso: print $ query-> getSQL (); foreach ($ query-> getParameters () como $ param) {print "{$ param-> getName ()} -> {$ param-> getValue ()} \ n"; } Como você vai obter uma saída mais legível
Justin Finkelstein
dá um pequeno benefício. Quando copio o sql, ainda tenho o parâmetro wichi de pesquisa onde inserir manualmente, isso leva muito tempo. Queremos uma consulta com os parâmetros inseridos, por que não conseguimos encontrá-la por tanto tempo? Mesmo no framework codeigniter, tanto quanto me lembro, no criador de perfil, você pode copiar a consulta e executar instantaneamente sem manualmente. Precisamos do mesmo no symfony.
precisa saber é o seguinte
35

Você pode verificar a consulta executada pelo seu aplicativo se registrar todas as consultas no mysql:

http://dev.mysql.com/doc/refman/5.1/en/query-log.html

haverá mais consultas, não apenas a que você está procurando, mas também poderá cumpri-la.

mas geralmente ->getSql();funciona

Editar:

para visualizar todas as consultas mysql que eu uso

sudo vim /etc/mysql/my.cnf 

e adicione essas 2 linhas:

general_log = on
general_log_file = /tmp/mysql.log

e reinicie o mysql

alex toader
fonte
17

Eu criei um Doctrine2 Logger que faz exatamente isso. Ele "hidrata" a consulta sql parametrizada com os valores usando os conversores de tipo de dados do Doctrine 2.

<?php


namespace Drsm\Doctrine\DBAL\Logging;
use Doctrine\DBAL\Logging\SQLLogger,
    Doctrine\DBAL\Types\Type,
    Doctrine\DBAL\Platforms\AbstractPlatform;
/**
 * A SQL logger that logs to the standard output and
 * subtitutes params to get a ready to execute SQL sentence

 * @author  [email protected]
 */
class EchoWriteSQLWithoutParamsLogger implements SQLLogger

{
    const QUERY_TYPE_SELECT="SELECT";
    const QUERY_TYPE_UPDATE="UPDATE";
    const QUERY_TYPE_INSERT="INSERT";
    const QUERY_TYPE_DELETE="DELETE";
    const QUERY_TYPE_CREATE="CREATE";
    const QUERY_TYPE_ALTER="ALTER";

    private $dbPlatform;
    private $loggedQueryTypes;
    public function __construct(AbstractPlatform $dbPlatform, array $loggedQueryTypes=array()){
        $this->dbPlatform=$dbPlatform;
        $this->loggedQueryTypes=$loggedQueryTypes;
    }
    /**
     * {@inheritdoc}
     */
    public function startQuery($sql, array $params = null, array $types = null)

    {
        if($this->isLoggable($sql)){
            if(!empty($params)){
                foreach ($params as $key=>$param) {
                    $type=Type::getType($types[$key]);
                    $value=$type->convertToDatabaseValue($param,$this->dbPlatform);
                    $sql = join(var_export($value, true), explode('?', $sql, 2));
                }

            }
            echo $sql . " ;".PHP_EOL;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function stopQuery()
    {

    }
    private function isLoggable($sql){
        if (empty($this->loggedQueryTypes)) return true;
        foreach($this->loggedQueryTypes as $validType){
            if (strpos($sql, $validType) === 0) return true;
        }
        return false;
    }
}

Exemplo de uso :; A seguinte paz de código ecoará na saída padrão de qualquer sentença INSERT, UPDATE, DELETE SQL gerada com o $ em Entity Manager,

/**@var  \Doctrine\ORM\EntityManager $em */
$em->getConnection()
                ->getConfiguration()
                ->setSQLLogger(
                    new EchoWriteSQLWithoutParamsLogger(
                        $em->getConnection()->getDatabasePlatform(),
                        array(
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_UPDATE,
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_INSERT,
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_DELETE
                        )
                    )
                );
dsamblas
fonte
1
Não funciona quando parâmetros são cadeias de datas como '2019-01-01'
Darius.V
14

getSqlQuery() tecnicamente mostra todo o comando SQL, mas é muito mais útil quando você também pode ver os parâmetros.

echo $q->getSqlQuery();
foreach ($q->getFlattenedParams() as $index => $param)
  echo "$index => $param";

Para tornar esse padrão mais reutilizável, há uma boa abordagem descrita nos comentários em Raw SQL do Doctrine Query Object .

ladenedge
fonte
Eu sei que este é um post antigo, mas os dois links levam a uma página 404. Você pode atualizar sua resposta, por favor? Estou perguntando, porque não tenho certeza do que você quer dizer com isso $q. Não parece ser a consulta nem o construtor de consultas.
k00ni 22/04
1
Receio não encontrar o código mais reutilizável. $qnesse caso, é uma consulta da doutrina 1. Você pode estar usando a Doutrina 2; nesse caso, você desejará algo como $qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters(); Espero que ajude!
ladenedge 22/04
13

Não há outra consulta real, é assim que as instruções preparadas funcionam. Os valores são vinculados no servidor de banco de dados, não na camada de aplicativo.

Veja minha resposta a esta pergunta: No PHP com PDO, como verificar a consulta parametrizada final do SQL?

(Repetido aqui por conveniência :)

O uso de instruções preparadas com valores parametrizados não é simplesmente outra maneira de criar dinamicamente uma sequência de SQL. Você cria uma instrução preparada no banco de dados e envia apenas os valores dos parâmetros.

Então, o que provavelmente é enviado ao banco de dados será um PREPARE ..., então SET ...e finalmenteEXECUTE ....

Você não poderá obter algumas strings SQL SELECT * FROM ..., mesmo que produzam resultados equivalentes, porque nenhuma consulta desse tipo foi realmente enviada ao banco de dados.

Ben James
fonte
9

Minha solução:

 /**
 * Get SQL from query
 * 
 * @author Yosef Kaminskyi 
 * @param QueryBilderDql $query
 * @return int
 */
public function getFullSQL($query)
{
    $sql = $query->getSql();
    $paramsList = $this->getListParamsByDql($query->getDql());
    $paramsArr =$this->getParamsArray($query->getParameters());
    $fullSql='';
    for($i=0;$i<strlen($sql);$i++){
        if($sql[$i]=='?'){
            $nameParam=array_shift($paramsList);

            if(is_string ($paramsArr[$nameParam])){
                $fullSql.= '"'.addslashes($paramsArr[$nameParam]).'"';
             }
            elseif(is_array($paramsArr[$nameParam])){
                $sqlArr='';
                foreach ($paramsArr[$nameParam] as $var){
                    if(!empty($sqlArr))
                        $sqlArr.=',';

                    if(is_string($var)){
                        $sqlArr.='"'.addslashes($var).'"';
                    }else
                        $sqlArr.=$var;
                }
                $fullSql.=$sqlArr;
            }elseif(is_object($paramsArr[$nameParam])){
                switch(get_class($paramsArr[$nameParam])){
                    case 'DateTime':
                             $fullSql.= "'".$paramsArr[$nameParam]->format('Y-m-d H:i:s')."'";
                          break;
                    default:
                        $fullSql.= $paramsArr[$nameParam]->getId();
                }

            }
            else                     
                $fullSql.= $paramsArr[$nameParam];

        }  else {
            $fullSql.=$sql[$i];
        }
    }
    return $fullSql;
}

 /**
 * Get query params list
 * 
 * @author Yosef Kaminskyi <[email protected]>
 * @param  Doctrine\ORM\Query\Parameter $paramObj
 * @return int
 */
protected function getParamsArray($paramObj)
{
    $parameters=array();
    foreach ($paramObj as $val){
        /* @var $val Doctrine\ORM\Query\Parameter */
        $parameters[$val->getName()]=$val->getValue();
    }

    return $parameters;
}
 public function getListParamsByDql($dql)
{
    $parsedDql = preg_split("/:/", $dql);
    $length = count($parsedDql);
    $parmeters = array();
    for($i=1;$i<$length;$i++){
        if(ctype_alpha($parsedDql[$i][0])){
            $param = (preg_split("/[' ' )]/", $parsedDql[$i]));
            $parmeters[] = $param[0];
        }
    }

    return $parmeters;}

Exemplo de uso:

$query = $this->_entityRepository->createQueryBuilder('item');
$query->leftJoin('item.receptionUser','users');
$query->where('item.customerid = :customer')->setParameter('customer',$customer)
->andWhere('item.paymentmethod = :paymethod')->setParameter('paymethod',"Bonus");
echo $this->getFullSQL($query->getQuery());
moledet
fonte
Obrigado por isso: D
Saad Achemlal
muito agradável. funciona com consultas normais, mas eu tenho uma consulta com regexp e parece que não suporta $ qb = $ this-> createQueryBuilder ('r') -> innerJoin ('r.profile', 'p') -> addSelect (' p ') -> where (' REGEXP (: fileNamePattern, r.fileNamePattern) = 1 ') -> andWhere (' p.incomingLocation =: incomingLocation ') -> setParameters ([' fileNamePattern '=> $ fileName,' incomingLocation ' => $ location]) -> getQuery ();
Fahim
Não funciona com todas as consultas. Quando eu tinha esse -> setParameters (array ('insuranceCarrier' => $ insuranceCarrier, 'dateFrom' => $ dateFrom-> formato ('Ym-d'), 'dateTo' => $ dateTo-> format ('Ym- d '),)) esses foram deixados com? marcas em sql.
Darius.V 17/05/19
9

Você pode acessar facilmente os parâmetros SQL usando a seguinte abordagem.

   $result = $qb->getQuery()->getSQL();

   $param_values = '';  
   $col_names = '';   

   foreach ($result->getParameters() as $index => $param){              
            $param_values .= $param->getValue().',';
            $col_names .= $param->getName().',';
   } 

   //echo rtrim($param_values,',');
   //echo rtrim($col_names,',');    

Portanto, se você imprimiu o $param_valuese $col_names, pode obter os valores dos parâmetros passando pelos nomes de sql e das respectivas colunas.

Nota: Se $paramretornar uma matriz, você precisará repetir, pois os parâmetros internos IN (:?)geralmente vêm como uma matriz aninhada.

Enquanto isso, se você encontrar outra abordagem, tenha a gentileza de compartilhar conosco :)

Obrigado!

Anjana Silva
fonte
6

Solução mais clara:

 /**
 * Get string query 
 * 
 * @param Doctrine_Query $query
 * @return string
 */
public function getDqlWithParams(Doctrine_Query $query){
    $vals = $query->getFlattenedParams();
    $sql = $query->getDql();
    $sql = str_replace('?', '%s', $sql);
    return vsprintf($sql, $vals);
}
dudapiotr
fonte
$ query-> getFlattenedParams (); não existe
Desenvolvedor
5
Solution:1
====================================================================================

function showQuery($query)
{
    return sprintf(str_replace('?', '%s', $query->getSql()), $query->getParams());
}

// call function  
echo showQuery($doctrineQuery);

Solution:2
====================================================================================

function showQuery($query)
{
    // define vars              
    $output    = NULL;
    $out_query = $query->getSql();
    $out_param = $query->getParams();

    // replace params
   for($i=0; $i<strlen($out_query); $i++) {
       $output .= ( strpos($out_query[$i], '?') !== FALSE ) ? "'" .str_replace('?', array_shift($out_param), $out_query[$i]). "'" : $out_query[$i];
   }

   // output
   return sprintf("%s", $output);
}

// call function  
echo showQuery($doctrineQueryObject);
Sandip Patel
fonte
5

Você pode usar :

$query->getSQL();

Se você estiver usando o MySQL, poderá usar o Workbench para visualizar instruções SQL em execução. Você também pode usar a visualização da consulta em execução no mysql usando o seguinte:

 SHOW FULL PROCESSLIST \G
lac_dev
fonte
4

Talvez possa ser útil para alguém:

// Printing the SQL with real values
$vals = $query->getFlattenedParams();
foreach(explode('?', $query->getSqlQuery()) as $i => $part) {
    $sql = (isset($sql) ? $sql : null) . $part;
    if (isset($vals[$i])) $sql .= $vals[$i];
}

echo $sql;
wcomnisky
fonte
2

TL; DR

$qb = ... // your query builder
$query = $qb->getQuery();
// temporarily enable logging for your query (will also work in prod env)
$conf = $query->getEntityManager()->getConnection()->getConfiguration();
$backupLogger = $conf->getSQLLogger();
$logger = new \Doctrine\DBAL\Logging\DebugStack();
$conf->setSQLLogger($logger);
// execute query
$res = $query->getResult();
$conf->setSQLLogger($backupLogger); //restore logger for other queries
$params = [
  'query' => array_pop($logger->queries) //extract query log details
  //your other twig params here...
]
return $params; //send this to your twig template...

nos arquivos de galho, use os filtros auxiliares de galho do Doctrine:

// show raw query:
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)
// highlighted
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query(highlight_only = true) }}
// highlighted and formatted (i.e. with tabs and newlines)
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query }}

Explicação:

As outras respostas mencionando que a declaração preparada são realmente "consultas reais" estão corretas, mas elas não respondem à expectativa óbvia do solicitante ... Todo desenvolvedor deseja exibir uma "consulta executável" para depuração (ou exibi-la ao usuário) .

Então, procurei na fonte do Symfony Profiler para ver como eles fazem isso. A parte Doutrina é responsabilidade da Doutrina, de modo que eles criaram um pacote de doutrinas para integrar com o Symfony. Examinando o doctrine-bundle/Resources/views/Collector/db.html.twigarquivo, você descobrirá como eles o fazem (isso pode mudar entre as versões). Curiosamente, eles criaram filtros de galhos que podemos reutilizar (veja acima).

Para que tudo funcione, precisamos ativar o Log para nossa consulta. Existem várias maneiras de fazer isso e aqui eu uso o DebugStack, que permite registrar consultas sem realmente imprimi-las. Isso também garante que isso funcione no modo de produção, se é isso que você precisa ...

Se você precisar de mais formatação, verá que eles incluem algum CSS em uma tag de estilo, então simplesmente "roube" ^ ^:

.highlight pre { margin: 0; white-space: pre-wrap; }
.highlight .keyword   { color: #8959A8; font-weight: bold; }
.highlight .word      { color: #222222; }
.highlight .variable  { color: #916319; }
.highlight .symbol    { color: #222222; }
.highlight .comment   { color: #999999; }
.highlight .backtick  { color: #718C00; }
.highlight .string    { color: #718C00; }
.highlight .number    { color: #F5871F; font-weight: bold; }
.highlight .error     { color: #C82829; }

Espero que isso ajude ;-)

Vincent Pazeller
fonte
1

Eu escrevi um logger simples, que pode registrar a consulta com os parâmetros inseridos. Instalação:

composer require cmyker/doctrine-sql-logger:dev-master

Uso:

$connection = $this->getEntityManager()->getConnection(); 
$logger = new \Cmyker\DoctrineSqlLogger\Logger($connection);
$connection->getConfiguration()->setSQLLogger($logger);
//some query here
echo $logger->lastQuery;
Cmyker
fonte
1
$sql = $query->getSQL();

$parameters = [];
    foreach ($query->getParameters() as $parameter) {
        $parameters[] = $parameter->getValue();
    }

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();
slk500
fonte
Você deve adicionar algum texto à sua resposta, explicando o que o código faz.
DarkMukke
0

A função @dsamblas modificada para funcionar quando parâmetros são strings de data como este '2019-01-01' e quando há uma matriz passada usando IN como

$qb->expr()->in('ps.code', ':activeCodes'),

. Faça tudo o que dsamblas escreveu, mas substitua startQuery por este ou veja as diferenças e adicione meu código. (caso ele tenha modificado algo em sua função e minha versão não tenha modificações).

public function startQuery($sql, array $params = null, array $types = null)

{
    if($this->isLoggable($sql)){
        if(!empty($params)){
            foreach ($params as $key=>$param) {

                try {
                    $type=Type::getType($types[$key]);
                    $value=$type->convertToDatabaseValue($param,$this->dbPlatform);
                } catch (Exception $e) {
                    if (is_array($param)) {
                        // connect arrays like ("A", "R", "C") for SQL IN
                        $value = '"' . implode('","', $param) . '"';
                    } else {
                        $value = $param; // case when there are date strings
                    }
                }

                $sql = join(var_export($value, true), explode('?', $sql, 2));
            }

        }
        echo $sql . " ;".PHP_EOL;
    }
}

Não testou muito.

Darius.V
fonte
0

Eu fiz algumas pesquisas para este tópico, porque eu queria depurar uma consulta SQL gerada e executá-la no editor sql. Como visto em todas as respostas, é um tópico altamente técnico.

Quando eu assumo que a pergunta inicial é baseada no dev-env, uma resposta muito simples está faltando no momento. Você pode simplesmente usar a compilação no Symfony Profiler. Basta clicar na guia Doutrina, vá até a consulta que deseja inspecionar. Em seguida, clique em "visualizar consulta executável" e você pode colar sua consulta diretamente no seu editor SQL

Abordagem mais básica da interface do usuário, mas muito rápida e sem sobrecarga de código de depuração.

insira a descrição da imagem aqui

Matthias Tosch
fonte
0
$sql = $query->getSQL();
$obj->mapDQLParametersNamesToSQL($query->getDQL(), $sql);
echo $sql;//to see parameters names in sql
$obj->mapDQLParametersValuesToSQL($query->getParameters(), $sql);
echo $sql;//to see parameters values in sql

public function mapDQLParametersNamesToSQL($dql, &$sql)
{
    $matches = [];
    $parameterNamePattern = '/:\w+/';
    /** Found parameter names in DQL */
    preg_match_all($parameterNamePattern, $dql, $matches);
    if (empty($matches[0])) {
        return;
    }
    $needle = '?';
    foreach ($matches[0] as $match) {
        $strPos = strpos($sql, $needle);
        if ($strPos !== false) {
            /** Paste parameter names in SQL */
            $sql = substr_replace($sql, $match, $strPos, strlen($needle));
        }
    }
}

public function mapDQLParametersValuesToSQL($parameters, &$sql)
{
    $matches = [];
    $parameterNamePattern = '/:\w+/';
    /** Found parameter names in SQL */
    preg_match_all($parameterNamePattern, $sql, $matches);
    if (empty($matches[0])) {
        return;
    }
    foreach ($matches[0] as $parameterName) {
        $strPos = strpos($sql, $parameterName);
        if ($strPos !== false) {
            foreach ($parameters as $parameter) {
                /** @var \Doctrine\ORM\Query\Parameter $parameter */
                if ($parameterName !== ':' . $parameter->getName()) {
                    continue;
                }
                $parameterValue = $parameter->getValue();
                if (is_string($parameterValue)) {
                    $parameterValue = "'$parameterValue'";
                }
                if (is_array($parameterValue)) {
                    foreach ($parameterValue as $key => $value) {
                        if (is_string($value)) {
                            $parameterValue[$key] = "'$value'";
                        }
                    }
                    $parameterValue = implode(', ', $parameterValue);
                }
                /** Paste parameter values in SQL */
                $sql = substr_replace($sql, $parameterValue, $strPos, strlen($parameterName));
            }
        }
    }
}
ks1bbk
fonte
-1

Para imprimir uma consulta SQL no Doctrine, use:

$query->getResult()->getSql();
Jaydeep Patel
fonte
não se esqueça de adicionar descrição com sua resposta? Apenas um revestimento sem descrição, não aceitável.
precisa saber é o seguinte
1
Para imprimir a consulta sql no Doctrine, use $ query-> getResult () -> getSql (); Graças
Jaydeep Patel
2
em vez de adicionar commnet, edite sua resposta
HaveNoDisplayName