Dottrina - Come stampare il vero sql, non solo la dichiarazione preparata?


167

Stiamo usando Doctrine, un PHP ORM. Sto creando una query come questa:

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

e poi nella funzione sto aggiungendo varie clausole e cose appropriate, come questa

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

Più avanti, prima di execute()quell'oggetto query, voglio stampare l'SQL grezzo per esaminarlo e fare questo:

$q->getSQLQuery();

Tuttavia, ciò stampa solo l'istruzione preparata, non l'intera query. Voglio vedere cosa sta inviando a MySQL, ma invece sta stampando una dichiarazione preparata, inclusa quella ?. C'è un modo per vedere la query "completa"?


Il modo migliore che ho trovato per vedere la query completa è descritto in questa risposta: stackoverflow.com/a/678310/229077
Marek

Puoi sfruttare il lavoro svolto da Doctrine (il profiler sta visualizzando una query eseguibile). Vedi la mia risposta di seguito per i dettagli
Vincent Pazeller,

Risposte:


164

Doctrine non sta inviando una "vera query SQL" al server di database: in realtà sta usando istruzioni preparate, il che significa:

  • Invio della dichiarazione, affinché sia ​​preparata (questo è ciò che viene restituito da $query->getSql())
  • E, quindi, l'invio dei parametri (restituito da $query->getParameters())
  • ed eseguendo le dichiarazioni preparate

Ciò significa che non esiste mai una query SQL "reale" sul lato PHP, quindi Doctrine non può visualizzarla.


14
Pascal: non dovresti dire che non è una "query SQL reale" perché le istruzioni preparate sono query SQL reali, è solo che i parametri vengono inviati separatamente. Questa formulazione potrebbe confondere le persone (ad es. Olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients ).
Matthieu Napoli,

$query->getParameters();NON restituiranno i parametri nell'ordine corretto, poiché dovrebbero apparire
nell'istruzione della

4
Penso che all'autore della domanda non importasse cosa mandasse o meno la dottrina. Quello che io e l'utente volevamo sapere è come ottenere una query che possiamo copiare incollare ed eseguire senza dover sostituire manualmente i punti interrogativi con parametri. Come in codeigniter. Penso di averlo trovato nel debugger di symfony, ma non riesco ancora a trovarlo quando eseguo lo script dalla riga di comando.
Darius.V

104

Un esempio funzionante:

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();

5
Mentre funziona come assegnazione di variabili, potresti considerare questo: print $ query-> getSQL (); foreach ($ query-> getParameters () as $ param) {print "{$ param-> getName ()} -> {$ param-> getValue ()} \ n"; } man mano che otterrai un output più leggibile
Justin Finkelstein il

dà un piccolo vantaggio. Quando copio il sql, ho ancora il parametro di ricerca wichi dove inserire manualmente, ci vuole un sacco di tempo. Vogliamo una query con i parametri inseriti, perché non riusciamo a trovarlo così a lungo? Anche nel framework codeigniter, per quanto mi ricordo, nel profiler è possibile copiare la query ed eseguire istantaneamente senza manualmente. Abbiamo bisogno dello stesso su symfony.
Darius.V,

35

Puoi controllare la query eseguita dalla tua app se registri tutte le query in mysql:

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

ci saranno più domande non solo quella che stai cercando, ma puoi richiederla.

ma di solito ->getSql();funziona

Modificare:

per visualizzare tutte le query mysql che utilizzo

sudo vim /etc/mysql/my.cnf 

e aggiungi quelle 2 righe:

general_log = on
general_log_file = /tmp/mysql.log

e riavvia mysql


17

Ho creato un Logger Doctrine2 che fa esattamente questo. "Idrata" la query sql parametrizzata con i valori utilizzando i convertitori del tipo di dati di 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  dsamblas@gmail.com
 */
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;
    }
}

Esempio di utilizzo :; Il seguente codice di pace echeggerà sull'output standard tutte le frasi SQL INSERT, UPDATE, DELETE generate con $ 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
                        )
                    )
                );

1
Non funziona quando i parametri sono stringhe di date come '2019-01-01'
Darius.V

14

getSqlQuery() mostra tecnicamente l'intero comando SQL, ma è molto più utile quando puoi vedere anche i parametri.

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

Per rendere questo modello più riutilizzabile, c'è un buon approccio descritto nei commenti su Raw SQL di Doctrine Query Object .


So che questo è un vecchio post, ma entrambi i tuoi link portano a una pagina 404. Puoi aggiornare la tua risposta per favore? Lo sto chiedendo, perché non sono sicuro di cosa tu voglia dire $q. Non sembra essere la query né il generatore di query.
k00ni,

1
Temo di non poter trovare il codice più riutilizzabile. $qin questo caso è una query di Doctrine 1. Forse stai usando Doctrine 2, nel qual caso vorrai qualcosa come $qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters(); Speriamo che ti aiuti!
Ladenedge,

13

Non esiste un'altra vera query, ecco come funzionano le istruzioni preparate. I valori sono associati nel server di database, non nel livello dell'applicazione.

Vedi la mia risposta a questa domanda: In PHP con PDO, come controllare la query parametrizzata SQL finale?

(Ripetuto qui per comodità :)

L'uso di istruzioni preparate con valori parametrizzati non è semplicemente un altro modo per creare dinamicamente una stringa di SQL. Si crea un'istruzione preparata nel database e quindi si inviano i valori dei parametri da soli.

Quindi, ciò che viene probabilmente inviato al database sarà a PREPARE ..., quindi SET ...e infineEXECUTE ....

Non sarai in grado di ottenere una stringa SQL simile SELECT * FROM ..., anche se produrrebbe risultati equivalenti, poiché tale query non è mai stata effettivamente inviata al database.


9

La mia soluzione:

 /**
 * 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 <yosefk@spotoption.com>
 * @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;}

Esempio di utilizzo:

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

Grazie per questo: D
Saad Achemlal

molto bella. funziona con le query normali ma ho una query con regexp e sembra che non supporti $ qb = $ this-> createQueryBuilder ('r') -> innerJoin ('r.profile', 'p') -> addSelect (' p ') -> dove (' REGEXP (: fileNamePattern, r.fileNamePattern) = 1 ') -> andWhere (' p.incomingLocation =: incomingLocation ') -> setParameters ([' fileNamePattern '=> $ fileName,' incomingLocation ' => $ location]) -> getQuery ();
Fahim,

Non funziona con tutte le query. Quando ho avuto questo -> setParameters (array ('insuranceCarrier' => $ insuranceCarrier, 'dateFrom' => $ dateFrom-> format ('Ym-d'), 'dateTo' => $ dateTo-> format ('Ym- d '),)) quelli rimasti? segna in sql.
Darius.V,

9

È possibile accedere facilmente ai parametri SQL utilizzando il seguente approccio.

   $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,',');    

Quindi, se hai stampato $param_valuese $col_names, puoi ottenere i valori dei parametri passando attraverso sql e i rispettivi nomi delle colonne.

Nota: se $paramrestituisce un array, è necessario ripetere l'iterazione, poiché i parametri all'interno vengono IN (:?)generalmente come array nidificati.

Nel frattempo, se hai trovato un altro approccio, sii gentile abbastanza da condividere con noi :)

Grazie!


6

Soluzione più chiara:

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

$ query-> getFlattenedParams (); non esiste
Sviluppatore

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

5

Puoi usare :

$query->getSQL();

Se si utilizza MySQL è possibile utilizzare Workbench per visualizzare le istruzioni SQL in esecuzione. Puoi anche utilizzare Visualizza la query in esecuzione da mysql usando quanto segue:

 SHOW FULL PROCESSLIST \G

4

Forse può essere utile per qualcuno:

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

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...

nei tuoi file ramoscello, usa i filtri per gli aiutanti del ramoscello di 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 }}

Spiegazione:

Le altre risposte che menzionano che l'istruzione Prepared sono in realtà "query reali" sono giuste, ma non rispondono alle ovvie aspettative del richiedente ... Ogni sviluppatore desidera visualizzare una "query eseguibile" per il debug (o mostrarla all'utente) .

Quindi, ho esaminato la fonte del profiler di Symfony per vedere come lo fanno. La parte di Dottrina è responsabilità di Doctrine, quindi hanno creato un fascio di dottrine da integrare con Symfony. Dando un'occhiata al doctrine-bundle/Resources/views/Collector/db.html.twigfile, scoprirai come lo fanno (questo potrebbe cambiare tra le versioni). È interessante notare che hanno creato filtri ramoscello che possiamo riutilizzare (vedi sopra).

Perché tutto funzioni, dobbiamo abilitare la registrazione per la nostra query. Esistono diversi modi per farlo e qui uso DebugStack che consente di registrare le query senza stamparle. Ciò garantisce anche che funzionerà in modalità di produzione se questo è ciò di cui hai bisogno ...

Se hai bisogno di ulteriore formattazione, vedrai che includono alcuni CSS in un tag di stile, quindi semplicemente "rubalo" ^^:

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

Spero che questo possa aiutare ;-)


1

Ho scritto un logger semplice, che può registrare query con parametri inseriti. Installazione:

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;

1
$sql = $query->getSQL();

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

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();

Dovresti aggiungere del testo alla tua risposta spiegando cosa fa il codice.
DarkMukke,

0

Funzione @dsamblas modificata per funzionare quando i parametri sono stringhe di date come questa '01-01-2019' e quando viene passato un array usando IN come

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

. Quindi fai tutto quello che ha scritto dsamblas, ma sostituisci startQuery con questo o vedi le differenze e aggiungi il mio codice. (nel caso abbia modificato qualcosa nella sua funzione e la mia versione non ha modifiche).

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

Non ho testato molto.


0

Ho fatto alcune ricerche su questo argomento, perché volevo eseguire il debug di una query SQL generata ed eseguirla nell'editor sql. Come visto in tutte le risposte, si tratta di un argomento altamente tecnico.

Quando presumo che la domanda iniziale sia basata su dev-env, al momento manca una risposta molto semplice. Puoi semplicemente usare il build nel profiler di Symfony. Basta fare clic sulla scheda Dottrina, scorrere fino alla query che si desidera controllare. Quindi fai clic su "visualizza query eseguibile" e puoi incollare la query direttamente nel tuo editor SQL

Più approccio alla UI ma molto veloce e senza sovraccarico del codice di debug.

inserisci qui la descrizione dell'immagine


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

-1

Per stampare una query SQL in Doctrine, utilizzare:

$query->getResult()->getSql();

non dimenticare di aggiungere una descrizione con la tua risposta? Solo una fodera senza descrizione, non accettabile.
HaveNoDisplayName

1
Per stampare la query sql in Doctrine usa $ query-> getResult () -> getSql (); Grazie
Jaydeep Patel,

2
invece di aggiungere commnet, modifica la tua risposta
HaveNoDisplayName
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.