Doctrine - Как распечатать реальный sql, а не только подготовленное выражение?

Мы используем Doctrine, PHP ORM. Я создаю такой запрос:

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

а затем в функции, которую я добавляю в разных местах, где и где это необходимо, например,

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

Позже, перед execute() -используя этот объект запроса, я хочу распечатать необработанный SQL для его проверки и сделать следующее:

$q->getSQLQuery();

Однако это только распечатывает подготовленный оператор, а не полный запрос. Я хочу посмотреть, что он отправляет в MySQL, но вместо этого распечатывает подготовленное выражение, в том числе ?. Есть ли способ увидеть "полный" запрос?

Ответ 1

Doctrine не отправляет "реальный SQL-запрос" на сервер базы данных: на самом деле он использует подготовленные операторы, что означает:

  • Отправка инструкции, чтобы она была подготовлена ​​(это то, что возвращается $query->getSql())
  • И затем, отправив параметры (возвращенные $query->getParameters())
  • и выполнение подготовленных операторов

Это означает, что на стороне PHP не существует "реального" SQL-запроса, поэтому Doctrine не может отображать его.

Ответ 2

Рабочий пример:

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

Ответ 3

Вы можете проверить запрос, выполняемый вашим приложением, если вы зарегистрируете все запросы в mysql:

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

будет больше запросов не только того, что вы ищете, но вы можете grep для него.

но обычно ->getSql(); работает

Edit:

чтобы просмотреть все запросы mysql, которые я использую

sudo vim /etc/mysql/my.cnf 

и добавьте эти две строки:

general_log = on
general_log_file = /tmp/mysql.log

и перезапустите mysql

Ответ 4

Я создал Doctrine2 Logger, который делает именно это. Он "увлажняет" параметризованный sql-запрос со значениями, используя собственные методы преобразования типов 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;
    }
}

Пример использования:; Следующий мир кода будет отражать на стандартном выходе любые предложения INSERT, UPDATE, DELETE SQL, сгенерированные с помощью $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
                        )
                    )
                );

Ответ 5

Других реальных запросов нет, так работают подготовленные операторы. Значения привязаны к серверу базы данных, а не к прикладному уровню.

См. мой ответ на этот вопрос: В PHP с PDO, как проверить окончательный SQL-запрос с параметрами?

(Повторяется здесь для удобства:)

Использование подготовленных операторов с параметризованными значениями - не просто еще один способ динамического создания строки SQL. Вы создаете подготовленный оператор в базе данных, а затем отправляете значения параметров самостоятельно.

Итак, что, вероятно, отправлено в базу данных, будет PREPARE ..., затем SET ... и, наконец, EXECUTE ....

Вы не сможете получить некоторую строку SQL, например SELECT * FROM ..., даже если она приведет к эквивалентным результатам, поскольку такой запрос никогда не отправлялся в базу данных.

Ответ 6

getSqlQuery() технически показывает всю команду SQL, но это намного более полезно, когда вы также можете увидеть параметры.

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

Чтобы сделать этот шаблон более многоразовым, есть хороший подход, описанный в комментариях в Необработанный SQL из объекта запроса Doctrine.

Ответ 7

Мое решение:

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

Пример использования:

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

Ответ 8

Вы можете легко получить доступ к параметрам SQL, используя следующий подход.

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

Итак, если вы напечатали $param_values и $col_names, вы можете получить значения параметров, проходящие через имена sql и соответствующих столбцов.

Примечание. Если $param возвращает массив, вам нужно повторить его, поскольку параметры внутри IN (:?) обычно появляются как вложенный массив.

Тем временем, если вы нашли другой подход, пожалуйста, будьте любезны поделиться с нами:)

Спасибо!

Ответ 9

Более четкое решение:

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

Ответ 10

Вы можете использовать:

$query->getSQL();

Если вы используете MySQL, вы можете использовать Workbench для просмотра запущенных операторов SQL. Вы также можете использовать просмотр текущего запроса из mysql, используя следующее:

 SHOW FULL PROCESSLIST \G

Ответ 11

Может быть, это может быть полезно для кого-то:

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

Ответ 12

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

Ответ 13

Я написал простой регистратор, который может записывать запрос с вставленными параметрами. Установка:

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

Использование:

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

Ответ 14

$sql = $query->getSQL();

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

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

Ответ 15

Модифицированная функция @dsamblas для работы, когда параметры представляют собой строки даты, например, "2019-01-01", и когда массив передается с использованием IN, например,

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

. Так что делайте все, что написал dsamblas, но замените startQuery на этот или посмотрите различия и добавьте мой код. (в случае, если он что-то изменил в своей функции и моя версия не имеет изменений).

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

Много не проверял.

Ответ 16

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

в файлах веток пользовательские помощники по веткам 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 }}

Объяснение:

Другие ответы, в которых упоминается, что оператор Prepared на самом деле являются "реальными запросами", верны, но они не отвечают очевидным ожиданиям запрашивающего... Каждый разработчик хочет отобразить "выполняемый запрос" для отладки (или отобразить его пользователю),

Итак, я заглянул в источник профайлера Symfony, чтобы узнать, как они это делают. Часть Доктрины - ответственность Доктрины, таким образом, они создали пакет доктрины, чтобы объединиться с Symfony. Взглянув на файл doctrine-bundle/Resources/views/Collector/db.html.twig, вы узнаете, как они это делают (это может измениться в разных версиях). Интересно, что они создали фильтры веточек, которые мы можем использовать повторно (см. выше).

Чтобы все работало, нам нужно включить ведение журнала для нашего запроса. Есть несколько способов сделать это, и здесь я использую DebugStack, который позволяет регистрировать запросы без фактической их печати. Это также гарантирует, что это будет работать в производственном режиме, если это то, что вам нужно...

Если вам понадобится дальнейшее форматирование, вы увидите, что они включают в себя некоторый CSS в теге стиля, поэтому просто "украдите" его ^^:

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

Надеюсь, это поможет ;-)

Ответ 17

Чтобы распечатать SQL-запрос в Doctrine, используйте:

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