Доктрина - как распечатать настоящий sql, а не только подготовленный оператор?

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

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

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

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

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

$q->getSQLQuery();

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


person Rory    schedule 19.01.2010    source источник
comment
Лучший способ увидеть полный запрос, который я нашел, описан в этом ответе: stackoverflow.com/a/678310/229077   -  person Marek    schedule 15.05.2015
comment
Вы можете воспользоваться преимуществами работы, проделанной Doctrine (профилировщик отображает выполнимый запрос). Подробности см. В моем ответе ниже   -  person Vincent Pazeller    schedule 11.10.2019


Ответы (17)


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

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

Это означает, что на стороне PHP никогда не бывает «настоящего» SQL-запроса, поэтому Doctrine не может его отобразить.

person Pascal MARTIN    schedule 19.01.2010
comment
Паскаль: вы не должны говорить, что это не настоящий SQL-запрос, потому что подготовленный оператор является настоящим SQL-запросом, просто параметры отправляются отдельно. Эта формулировка может запутать людей (например, olivierpons.fr/ 22.03.2014 / symfony-2-avantages-et-inconvenients). - person Matthieu Napoli; 06.12.2014
comment
$query->getParameters(); НЕ будет возвращать параметры в правильном порядке, как они должны отображаться в подготовленном операторе запроса. - person gondo; 09.12.2016
comment
Думаю, здесь автора вопроса не волновало, какое учение присылает или нет. Мы с пользователем хотели узнать, как получить запрос, который можно скопировать, вставить и запустить без необходимости вручную заменять вопросительные знаки параметрами. Как в codeigniter. Я думаю, что нашел это в отладчике Symfony, но я все еще не могу найти, когда запускаю скрипт из командной строки. - person Darius.V; 21.03.2019

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

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();
person Andy.Diaz    schedule 05.11.2012
comment
Хотя он работает как присваивание переменных, вы можете подумать об этом: print $ query- ›getSQL (); foreach ($ query- ›getParameters () as $ param) {print {$ param-› getName ()} - ›{$ param-› getValue ()} \ n; } так как вы получите более читаемый результат - person Justin Finkelstein; 02.07.2014
comment
это дает небольшую выгоду. Когда я копирую sql, у меня все еще есть параметр search wichi, куда вставлять вручную, это занимает много времени. Нам нужен запрос со вставленными параметрами, почему мы не можем его найти так долго? Даже в фреймворке codeigniter, насколько я помню, в профилировщике можно было скопировать запрос и запустить его мгновенно, без необходимости вручную. Нам нужно то же самое на symfony. - person Darius.V; 20.08.2018

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

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

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

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

Редактировать:

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

sudo vim /etc/mysql/my.cnf 

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

general_log = on
general_log_file = /tmp/mysql.log

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

person alex toader    schedule 27.10.2011

Я создал 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;
    }
}

Пример использования :; Следующий фрагмент кода будет выводить на стандартный вывод любые SQL-предложения INSERT, UPDATE, DELETE, сгенерированные с помощью $ 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
                        )
                    )
                );
person dsamblas    schedule 05.09.2013
comment
Не работает, если параметры представляют собой строки даты, такие как '2019-01-01' - person Darius.V; 22.07.2019

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

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

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

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

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

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

person Ben James    schedule 19.01.2010

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

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

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

person ladenedge    schedule 14.05.2012
comment
Я знаю, что это старый пост, но обе ваши ссылки ведут на страницу 404. Не могли бы вы обновить свой ответ? Я спрашиваю, потому что не понимаю, что вы имеете в виду под $q. Похоже, это не запрос или не конструктор запросов. - person k00ni; 22.04.2020
comment
Боюсь, я не могу найти более пригодный для повторного использования код. $q в данном случае - это запрос Doctrine 1. Возможно, вы используете Doctrine 2, и в этом случае вам понадобится что-то вроде $qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters(); Надеюсь, это поможет! - person ladenedge; 22.04.2020

Мое решение:

 /**
 * 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());
person moledet    schedule 23.12.2014
comment
очень хорошо. работает с обычными запросами, но у меня есть запрос с регулярным выражением, и похоже, что он не поддерживает $ 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 (); - person Fahim; 06.02.2019
comment
Не работает со всеми запросами. Когда у меня было это - ›setParameters (array ('insuranceCarrier' =› $ insuranceCarrier, 'dateFrom' = ›$ dateFrom-› format ('Ym-d'), 'dateTo' = ›$ dateTo-› format ('Ym- г '),)) те остались? отметки в sql. - person Darius.V; 17.05.2019

Вы можете легко получить доступ к параметрам 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 (:?) обычно представляют собой вложенный массив.

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

Спасибо!

person Anjana Silva    schedule 03.02.2015

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

 /**
 * 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);
}
person dudapiotr    schedule 08.10.2013
comment
$ query- ›getFlattenedParams (); не существует - person Developer; 15.09.2018
comment
@Developer для более новой версии Doctrine, вы можете заменить getFlattenedParams() на getParameters(). Также можно найти более полезный getSQL() вместо getDql(). - person hdorio; 19.02.2021

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

$query->getSQL();

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

 SHOW FULL PROCESSLIST \G
person lac_dev    schedule 08.05.2014

Может быть, это кому-нибудь пригодится:

// 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;
person wcomnisky    schedule 23.01.2013

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

Объяснение:

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

Итак, я заглянул в исходный код профилировщика Symfony, чтобы узнать, как они это делают. Часть Doctrine - это ответственность Doctrine, поэтому они сделали пакет доктрины для интеграции с 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; }

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

person Vincent Pazeller    schedule 26.09.2019
comment
Это очень помогает! Я просто беру ваш код и помещаю его в функцию executeAndReturnRealQuery($query). The only changes I made is I directly use an instance of DoctrineExtension, then return $ doctrineExtension- ›replaceQueryParameters ($ params ['query'] ['sql'], $ params ['query'] ['sql']); - person Asenar; 06.03.2021

Я провел небольшое исследование по этой теме, потому что хотел отладить сгенерированный SQL-запрос и выполнить его в редакторе sql. Как видно из всех ответов, это сугубо техническая тема.

Когда я предполагаю, что исходный вопрос основан на dev-env, в настоящий момент отсутствует один очень простой ответ. Вы можете просто использовать сборку в профилировщике Symfony. Просто щелкните вкладку Doctrine и прокрутите до запроса, который хотите проверить. Затем нажмите на просмотр выполняемого запроса, и вы можете вставить свой запрос прямо в редактор SQL.

Больше базового подхода к пользовательскому интерфейсу, но очень быстро и без дополнительных затрат на отладку кода.

введите описание изображения здесь

person Matthias Tosch    schedule 27.01.2020
comment
Только что нашел также, как использовать это с вызовами API. Просто посмотрите на заголовок ответа для x-debug-token-link, это URL-адрес связанной страницы профилировщика :) - person Erdal G.; 18.01.2021

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

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;
person Cmyker    schedule 13.08.2015

Модифицированная функция @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;
    }
}

Много не тестировал.

person Darius.V    schedule 22.07.2019

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

$query->getResult()->getSql();
person Jaydeep Patel    schedule 27.01.2015
comment
не забудьте добавить описание к своему ответу? Только один лайнер без описания, неприемлем. - person HaveNoDisplayName; 28.01.2015
comment
Чтобы распечатать sql-запрос в Doctrine, используйте $ query- ›getResult () -› getSql (); Спасибо - person Jaydeep Patel; 28.01.2015
comment
вместо добавления Commnet отредактируйте свой ответ - person HaveNoDisplayName; 28.01.2015

person    schedule
comment
Вы должны добавить к своему ответу текст, объясняющий, что делает код. - person DarkMukke; 30.11.2018