Doctrina: ¿cómo imprimir el sql real, no solo la declaración preparada?

167

Estamos usando Doctrine, un PHP ORM. Estoy creando una consulta como esta:

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

y luego en la función estoy agregando varias cláusulas where y cosas según corresponda, como esta

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

Más adelante, antes de execute()poner ese objeto de consulta, quiero imprimir el SQL sin procesar para examinarlo y hacer esto:

$q->getSQLQuery();

Sin embargo, eso solo imprime la declaración preparada, no la consulta completa. Quiero ver qué está enviando a MySQL, pero en su lugar está imprimiendo una declaración preparada, incluida ?la de. ¿Hay alguna forma de ver la consulta 'completa'?

Rory
fuente
La mejor manera que he encontrado para ver la consulta completa se describe en esta respuesta: stackoverflow.com/a/678310/229077
Marek
Puede aprovechar el trabajo realizado por Doctrine (el generador de perfiles muestra una consulta ejecutable). Vea mi respuesta a continuación para más detalles
Vincent Pazeller,

Respuestas:

164

Doctrine no está enviando una "consulta SQL real" al servidor de la base de datos: en realidad está usando declaraciones preparadas, lo que significa:

  • Enviando la declaración, para que se prepare (esto es lo que devuelve $query->getSql())
  • Y, luego, enviando los parámetros (devueltos por $query->getParameters())
  • y ejecutando las declaraciones preparadas

Esto significa que nunca hay una consulta SQL "real" en el lado de PHP, por lo que Doctrine no puede mostrarla.

Pascal MARTIN
fuente
14
Pascal: no debería decir que no es una "consulta SQL real" porque la declaración preparada es una consulta SQL real, es solo que los parámetros se envían por separado. Esta redacción podría confundir a las personas (por ejemplo, olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients ).
Matthieu Napoli
$query->getParameters(); NO devolverá los parámetros en el orden correcto, ya que deberían aparecer en la declaración de consulta preparada
gondo
44
Creo que aquí al autor de la pregunta no le importó qué doctrina envía o no. Lo que el usuario y yo queríamos saber es cómo obtener una consulta que podamos copiar, pegar y ejecutar sin tener que reemplazar manualmente los signos de interrogación con parámetros. Como en codeigniter. Creo que encontré esto en el depurador de Symfony, pero todavía no puedo encontrarlo cuando ejecuto el script desde la línea de comandos.
Darius.V
104

Un ejemplo de trabajo:

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();
Andy.Diaz
fuente
55
Si bien funciona como asignaciones variables, es posible que desee considerar esto: print $ query-> getSQL (); foreach ($ query-> getParameters () como $ param) {print "{$ param-> getName ()} -> {$ param-> getValue ()} \ n"; } ya que obtendrás una salida más legible
Justin Finkelstein
da poco beneficio. Cuando copio el sql, todavía tengo el parámetro wichi de búsqueda donde insertar manualmente, lleva mucho tiempo. Queremos una consulta con parámetros insertados, ¿por qué no podemos encontrarla tanto tiempo? Incluso en el marco de codeigniter, por lo que recuerdo, en el generador de perfiles puede copiar la consulta y ejecutarla instantáneamente sin necesidad de hacerlo manualmente. Necesitamos lo mismo en Symfony.
Darius.V
35

Puede verificar la consulta ejecutada por su aplicación si registra todas las consultas en mysql:

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

Habrá más consultas, no solo la que está buscando, sino que puede obtenerla.

pero generalmente ->getSql();funciona

Editar:

para ver todas las consultas mysql que uso

sudo vim /etc/mysql/my.cnf 

y agrega esas 2 líneas:

general_log = on
general_log_file = /tmp/mysql.log

y reiniciar mysql

alex toader
fuente
17

He creado un registrador Doctrine2 que hace exactamente esto. "Hidrata" la consulta sql parametrizada con los valores utilizando los propios conversores de datos 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;
    }
}

Ejemplo de uso :; La siguiente paz de código se hará eco en la salida estándar de cualquier instrucción INSERT, UPDATE, DELETE SQL generada 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
                        )
                    )
                );
dsamblas
fuente
1
No funciona cuando los parámetros son cadenas de fecha como '2019-01-01'
Darius.V
14

getSqlQuery() técnicamente muestra todo el comando SQL, pero es mucho más útil cuando también puedes ver los parámetros.

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

Para hacer que este patrón sea más reutilizable, hay un buen enfoque descrito en los comentarios en Raw SQL de Doctrine Query Object .

ladenedge
fuente
Sé que esta es una publicación antigua, pero ambos enlaces conducen a una página 404. ¿Puedes actualizar tu respuesta por favor? Estoy preguntando, porque no estoy seguro de a qué te refieres $q. No parece ser la consulta ni el generador de consultas.
k00ni
1
Me temo que no puedo encontrar el código más reutilizable. $qen este caso es una consulta de Doctrine 1. Es posible que estés usando Doctrine 2, en cuyo caso querrás algo como ¡ $qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters(); Espero que eso ayude!
ladenedge
13

No hay otra consulta real, así es como funcionan las declaraciones preparadas. Los valores están vinculados en el servidor de la base de datos, no en la capa de aplicación.

Vea mi respuesta a esta pregunta: en PHP con PDO, ¿cómo verificar la consulta parametrizada final de SQL?

(Repetido aquí por conveniencia :)

Usar sentencias preparadas con valores parametrizados no es simplemente otra forma de crear dinámicamente una cadena de SQL. Cree una declaración preparada en la base de datos y luego envíe los valores de los parámetros solo.

Entonces, lo que probablemente se envíe a la base de datos será un PREPARE ..., luego SET ...y finalmenteEXECUTE ....

No podrá obtener una cadena SQL como SELECT * FROM ..., incluso si produciría resultados equivalentes, porque tal consulta nunca se envió a la base de datos.

Ben James
fuente
9

Mi solución:

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

Ejemplo 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
fuente
Gracias por esto: D
Saad Achemlal
muy agradable. funciona con consultas normales pero tengo una consulta con regexp y parece que no admite $ qb = $ this-> createQueryBuilder ('r') -> innerJoin ('r.profile', 'p') -> addSelect (' p ') -> where (' REGEXP (: fileNamePattern, r.fileNamePattern) = 1 ') -> andWhere (' p.incomingLocation =: entranteLocation ') -> setParameters ([' fileNamePattern '=> $ fileName,' comingLocation ' => $ ubicación]) -> getQuery ();
Fahim
No funciona con todas las consultas. Cuando tuve esto -> setParameters (array ('insuranceCarrier' => $ insuranceCarrier, 'dateFrom' => $ dateFrom-> format ('Ym-d'), 'dateTo' => $ dateTo-> format ('Ym- d '),)) los que quedaron con? marcas en sql.
Darius.V
9

Puede acceder fácilmente a los parámetros SQL utilizando el siguiente enfoque.

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

Entonces, si imprimió el $param_valuesy $col_names, puede obtener los valores de los parámetros que pasan a través del sql y los nombres de columna respectivos.

Nota: Si $paramdevuelve una matriz, debe repetirla, ya que los parámetros que se encuentran en el interior IN (:?)generalmente son una matriz anidada.

Mientras tanto, si encontró otro enfoque, tenga la amabilidad de compartir con nosotros :)

¡Gracias!

Anjana Silva
fuente
6

Solución más 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
fuente
$ consulta-> getFlattenedParams (); no existe
Desarrollador
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
fuente
5

Puedes usar :

$query->getSQL();

Si está utilizando MySQL, puede usar Workbench para ver las instrucciones SQL en ejecución. También puede usar ver la consulta en ejecución desde mysql usando lo siguiente:

 SHOW FULL PROCESSLIST \G
lac_dev
fuente
4

Tal vez pueda ser útil para alguien:

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

en sus archivos de ramita, use los filtros de ayuda de ramita 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 }}

Explicación:

Las otras respuestas que mencionan que la declaración preparada son en realidad "consultas reales" son correctas, pero no responden a la expectativa obvia del autor de la pregunta ... Todo desarrollador quiere mostrar una "consulta ejecutable" para la depuración (o para mostrarla al usuario) .

Entonces, busqué en la fuente del perfilador de Symfony para ver cómo lo hacen. La parte de Doctrine es responsabilidad de Doctrine, por lo que hicieron un paquete de doctrina para integrarse con Symfony. Echar un vistazo a ladoctrine-bundle/Resources/views/Collector/db.html.twig archivo, descubrirá cómo lo hacen (esto puede cambiar según las versiones). Curiosamente, crearon filtros de ramitas que podemos reutilizar (ver arriba).

Para que todo funcione, debemos habilitar el registro para nuestra consulta. Hay varias formas de hacer esto y aquí utilizo DebugStack que permite registrar consultas sin realmente imprimirlas. Esto también asegura que esto funcionará en modo de producción si esto es lo que necesita ...

Si necesita más formatos, verá que incluyen algunos CSS en una etiqueta de estilo, así que simplemente "robe" ^^:

.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 esto ayude ;-)

Vincent Pazeller
fuente
1

Escribí un registrador simple, que puede registrar consultas con parámetros insertados. Instalación:

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
fuente
1
$sql = $query->getSQL();

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

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();
slk500
fuente
Debe agregar un texto a su respuesta que explique lo que hace el código.
DarkMukke
0

Se modificó la función @dsamblas para que funcione cuando los parámetros son cadenas de fecha como esta '2019-01-01' y cuando se pasa una matriz usando IN como

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

. Entonces, haga todo lo que dsamblas escribió, pero reemplace startQuery con este o vea las diferencias y agregue mi código. (en caso de que haya modificado algo en su función y mi versión no tiene modificaciones).

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

No probé mucho.

Darius.V
fuente
0

Investigué un poco sobre este tema, porque quería depurar una consulta SQL generada y ejecutarla en el editor SQL. Como se ve en todas las respuestas, es un tema altamente técnico.

Cuando supongo que la pregunta inicial se basa en dev-env, falta una respuesta muy simple en este momento. Simplemente puede usar la compilación en Symfony Profiler. Simplemente haga clic en la pestaña Doctrina, desplácese hasta la consulta que desea inspeccionar. Luego haga clic en "ver consulta ejecutable" y puede pegar su consulta directamente en su editor SQL

Más enfoque de base de UI pero muy rápido y sin depuración de sobrecarga de código.

ingrese la descripción de la imagen aquí

Matthias Tosch
fuente
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
fuente
-1

Para imprimir una consulta SQL en Doctrine, use:

$query->getResult()->getSql();
Jaydeep Patel
fuente
no olvides agregar una descripción con tu respuesta? Solo un revestimiento sin descripción, no es aceptable.
HaveNoDisplayName
1
Para imprimir una consulta SQL en Doctrine, use $ query-> getResult () -> getSql (); Gracias
Jaydeep Patel
2
en lugar de agregar commnet, edite su respuesta
HaveNoDisplayName