Doctrine - Comment imprimer le vrai SQL, pas seulement la déclaration préparée?

167

Nous utilisons Doctrine, un ORM PHP. Je crée une requête comme celle-ci:

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

puis dans la fonction, j'ajoute diverses clauses where et des choses comme il convient, comme ceci

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

Plus tard, avant de execute()lancer cet objet de requête, je veux imprimer le SQL brut afin de l'examiner, et faire ceci:

$q->getSQLQuery();

Cependant, cela n'imprime que l'instruction préparée, pas la requête complète. Je veux voir ce qu'il envoie à MySQL, mais à la place, il imprime une instruction préparée, y compris celle ?de. Existe-t-il un moyen de voir la requête «complète»?

Rory
la source
Le meilleur moyen que j'ai trouvé pour voir la requête complète est décrit dans cette réponse: stackoverflow.com/a/678310/229077
Marek
Vous pouvez profiter du travail effectué par Doctrine (le profileur affiche une requête exécutable). Voir ma réponse ci-dessous pour plus de détails
Vincent Pazeller

Réponses:

164

Doctrine n'envoie pas une "vraie requête SQL" au serveur de base de données: elle utilise en fait des instructions préparées, ce qui signifie:

  • Envoi de l'instruction, pour qu'elle soit préparée (c'est ce qui est renvoyé par $query->getSql())
  • Et, ensuite, envoyer les paramètres (renvoyés par $query->getParameters())
  • et exécuter les instructions préparées

Cela signifie qu'il n'y a jamais de "vraie" requête SQL du côté PHP - donc, Doctrine ne peut pas l'afficher.

Pascal MARTIN
la source
14
Pascal: il ne faut pas dire que ce n'est pas une "vraie requête SQL" car les instructions préparées sont de vraies requêtes SQL, c'est juste que les paramètres sont envoyés séparément. Cette formulation pourrait dérouter les gens (ex: olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients ).
Matthieu Napoli
$query->getParameters();ne renverra PAS les paramètres dans le bon ordre, car ils devraient apparaître dans l'instruction de requête préparée
gondo
4
Je pense qu'ici l'auteur de la question se moquait de ce que la doctrine envoie ou non. Ce que l'utilisateur et moi voulions savoir, c'est comment obtenir une requête que nous pouvons copier-coller et exécuter sans avoir à remplacer manuellement les points d'interrogation par des paramètres. Comme dans codeigniter. Je pense que j'ai trouvé cela dans le débogueur symfony, mais je ne trouve toujours pas quand j'exécute le script à partir de la ligne de commande.
Darius.V
104

Un exemple de travail:

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();
Andy.Diaz
la source
5
Bien que cela fonctionne comme des affectations de variables, vous voudrez peut-être considérer ceci: print $ query-> getSQL (); foreach ($ query-> getParameters () as $ param) {print "{$ param-> getName ()} -> {$ param-> getValue ()} \ n"; } car vous obtiendrez une sortie plus lisible
Justin Finkelstein
cela donne un petit avantage. Lorsque je copie le sql, j'ai toujours le paramètre de recherche wichi où insérer manuellement, cela prend beaucoup de temps. Nous voulons une requête avec des paramètres insérés, pourquoi nous ne pouvons pas la trouver si longtemps? Même dans le cadre de codeigniter pour autant que je me souvienne, dans le profileur, vous pouvez copier la requête et l'exécuter instantanément sans manuellement. Nous avons besoin de la même chose sur symfony.
Darius.V
35

Vous pouvez vérifier la requête exécutée par votre application si vous enregistrez toutes les requêtes dans mysql:

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

il y aura plus de requêtes non seulement celle que vous recherchez, mais vous pouvez la grep.

mais ->getSql();fonctionne généralement

Éditer:

pour voir toutes les requêtes mysql que j'utilise

sudo vim /etc/mysql/my.cnf 

et ajoutez ces 2 lignes:

general_log = on
general_log_file = /tmp/mysql.log

et redémarrez mysql

alex toader
la source
17

J'ai créé un enregistreur Doctrine2 qui fait exactement cela. Il «hydrate» la requête sql paramétrée avec les valeurs en utilisant les propres convertisseurs de type de données de 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;
    }
}

Exemple d'utilisation :; La paix de code suivante fera écho sur la sortie standard de toutes les phrases SQL INSERT, UPDATE, DELETE générées avec $ 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
la source
1
Ne fonctionne pas lorsque les paramètres sont des chaînes de date comme '2019-01-01'
Darius.V
14

getSqlQuery() affiche techniquement toute la commande SQL, mais c'est beaucoup plus utile lorsque vous pouvez également voir les paramètres.

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

Pour rendre ce modèle plus réutilisable, il existe une belle approche décrite dans les commentaires de Raw SQL de Doctrine Query Object .

chargé
la source
Je sais que c'est un ancien message, mais vos deux liens mènent à une page 404. Pouvez-vous mettre à jour votre réponse s'il vous plaît? Je demande, parce que je ne sais pas de quoi vous parlez $q. Cela ne semble pas être la requête ni le générateur de requêtes.
k00ni le
1
J'ai peur de ne pas trouver le code le plus réutilisable. $qdans ce cas, il s'agit d'une requête Doctrine 1. Vous utilisez peut-être Doctrine 2, auquel cas vous voudrez quelque chose comme $qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters(); Espérons que cela aide!
chargé le
13

Il n'y a pas d'autre vraie requête, c'est ainsi que fonctionnent les instructions préparées. Les valeurs sont liées dans le serveur de base de données, pas dans la couche application.

Voir ma réponse à cette question: En PHP avec PDO, comment vérifier la requête paramétrée SQL finale?

(Répété ici pour plus de commodité :)

L'utilisation d'instructions préparées avec des valeurs paramétrées n'est pas simplement une autre façon de créer dynamiquement une chaîne SQL. Vous créez une instruction préparée dans la base de données, puis envoyez les valeurs de paramètre seules.

Donc, ce qui est probablement envoyé à la base de données sera un PREPARE ..., puis SET ...et enfinEXECUTE ....

Vous ne pourrez pas obtenir une chaîne SQL comme SELECT * FROM ..., même si cela produirait des résultats équivalents, car aucune requête de ce type n'a jamais été envoyée à la base de données.

Ben James
la source
9

Ma solution:

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

Exemple d'utilisation:

$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
la source
Merci pour cela: D
Saad Achemlal
très agréable. fonctionne avec les requêtes normales mais j'ai une requête avec regexp et il semble ne pas prendre en charge $ 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
Ne fonctionne pas avec toutes les requêtes. Quand j'ai eu ceci -> setParameters (array ('insuranceCarrier' => $ insuranceCarrier, 'dateFrom' => $ dateFrom-> format ('Ym-d'), 'dateTo' => $ dateTo-> format ('Ym- d '),)) ceux qui ont été laissés avec? marques en sql.
Darius.V
9

Vous pouvez facilement accéder aux paramètres SQL en utilisant l'approche suivante.

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

Donc, si vous avez imprimé le $param_valueset $col_names, vous pouvez obtenir les valeurs des paramètres en passant par le sql et les noms de colonne respectifs.

Remarque: si $paramrenvoie un tableau, vous devez réitérer, car les paramètres à l'intérieur sont IN (:?)généralement sous forme de tableau imbriqué.

En attendant, si vous avez trouvé une autre approche, merci de bien vouloir partager avec nous :)

Je vous remercie!

Anjana Silva
la source
6

Solution plus claire:

 /**
 * 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
la source
$ query-> getFlattenedParams (); n'existe pas
Développeur
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
la source
5

Vous pouvez utiliser :

$query->getSQL();

Si vous utilisez MySQL, vous pouvez utiliser Workbench pour afficher les instructions SQL en cours d'exécution. Vous pouvez également utiliser afficher la requête en cours à partir de mysql en utilisant ce qui suit:

 SHOW FULL PROCESSLIST \G
lac_dev
la source
4

Peut-être que cela peut être utile pour quelqu'un:

// 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
la source
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...

dans vos fichiers twig, utilisez les filtres twig helpers de 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 }}

Explication:

Les autres réponses mentionnant que l'instruction Prepared sont en fait de "vraies requêtes" sont correctes, mais elles ne répondent pas aux attentes évidentes du demandeur ... Chaque développeur veut afficher une "requête exécutable" pour le débogage (ou l'afficher à l'utilisateur) .

J'ai donc regardé la source du profileur Symfony pour voir comment ils le faisaient. La partie Doctrine est de la responsabilité de Doctrine, ils ont donc créé un ensemble de doctrine à intégrer à Symfony. En regardant le doctrine-bundle/Resources/views/Collector/db.html.twigfichier, vous découvrirez comment ils le font (cela peut changer d'une version à l'autre). Fait intéressant, ils ont créé des filtres de brindilles que nous pouvons réutiliser (voir ci-dessus).

Pour que tout fonctionne, nous devons activer la journalisation pour notre requête. Il existe plusieurs façons de le faire et j'utilise ici DebugStack qui permet de journaliser les requêtes sans les imprimer. Cela garantit également que cela fonctionnera en mode production si c'est ce dont vous avez besoin ...

Si vous avez besoin d'un formatage supplémentaire, vous verrez qu'ils incluent du CSS dans une balise de style, alors "volez-le" simplement ^^:

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

J'espère que cela aidera ;-)

Vincent Pazeller
la source
1

J'ai écrit un simple enregistreur, qui peut enregistrer une requête avec des paramètres insérés. Installation:

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

Usage:

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

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

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();
slk500
la source
Vous devriez ajouter du texte à votre réponse expliquant ce que fait le code.
DarkMukke
0

Fonction @dsamblas modifiée pour fonctionner lorsque les paramètres sont des chaînes de date comme celle-ci '2019-01-01' et lorsqu'un tableau est passé en utilisant IN comme

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

. Alors faites tout ce que dsamblas a écrit, mais remplacez startQuery par celui-ci ou voyez les différences et ajoutez mon code. (au cas où il aurait modifié quelque chose dans sa fonction et que ma version n'aurait pas de modifications).

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

Je n'ai pas beaucoup testé.

Darius.V
la source
0

J'ai fait des recherches sur ce sujet, car je voulais déboguer une requête SQL générée et l'exécuter dans l'éditeur SQL. Comme on le voit dans toutes les réponses, c'est un sujet très technique.

Quand je suppose que la question initiale est basée sur dev-env, une réponse très simple manque pour le moment. Vous pouvez simplement utiliser la compilation dans le profileur Symfony. Cliquez simplement sur l'onglet Doctrine, faites défiler jusqu'à la requête que vous souhaitez inspecter. Cliquez ensuite sur "afficher la requête exécutable" et vous pourrez coller votre requête directement dans votre éditeur SQL

Plus d'approche de base de l'interface utilisateur mais très rapide et sans surcharge de code de débogage.

entrez la description de l'image ici

Matthias Tosch
la source
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
la source
-1

Pour imprimer une requête SQL dans Doctrine, utilisez:

$query->getResult()->getSql();
Jaydeep Patel
la source
n'oubliez pas d'ajouter une description avec votre réponse? Une seule doublure sans description, non acceptable.
HaveNoDisplayName
1
Pour imprimer une requête SQL dans Doctrine, utilisez $ query-> getResult () -> getSql (); Merci
Jaydeep Patel
2
au lieu d'ajouter commnet, modifiez votre réponse
HaveNoDisplayName