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

167

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

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

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

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

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

$q->getSQLQuery();

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

Рори
источник
В этом ответе описан лучший способ увидеть полный запрос: stackoverflow.com/a/678310/229077
Марек,
Вы можете воспользоваться работой, проделанной Doctrine (профилировщик отображает выполняемый запрос). См. Мой ответ ниже для деталей
Винсент Пазеллер

Ответы:

164

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

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

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

Паскаль МАРТИН
источник
14
Паскаль: вы не должны говорить, что это не «реальный запрос SQL», потому что подготовленный оператор - это реальный запрос SQL, просто параметры отправляются отдельно. Эта формулировка может сбить людей с толку (например, olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients ).
Матье Наполи
$query->getParameters();НЕ будет возвращать параметры в правильном порядке, так как они должны отображаться в подготовленном операторе запроса
gondo
4
Я думаю, что здесь автору вопроса было все равно, что доктрина посылает или нет. Мы с пользователем хотели узнать, как получить запрос, который мы можем скопировать, вставить и выполнить без необходимости вручную заменять вопросительные знаки параметрами. Как в кодеигниторе. Я думаю, что нашел это в отладчике Symfony, но я все еще не могу найти, когда я запускаю скрипт из командной строки.
Дариус. В
104

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

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

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

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

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

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

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

для просмотра всех запросов MySQL, которые я использую

sudo vim /etc/mysql/my.cnf 

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

general_log = on
general_log_file = /tmp/mysql.log

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

Алекс Тодер
источник
17

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

Пример использования :; Следующий фрагмент кода будет отображаться при стандартном выводе любых предложений 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
                        )
                    )
                );
dsamblas
источник
1
Не работает, когда параметры представляют собой строки даты, такие как '2019-01-01'
Darius.V
14

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

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

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

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

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

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

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

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

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

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

Бен Джеймс
источник
9

Мое решение:

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

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

$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());
Моледет
источник
Спасибо за это: D
Саад Ахемал
очень хорошо. работает с обычными запросами, но я получил запрос с регулярным выражением и похоже, что он не поддерживает $ qb = $ this-> createQueryBuilder ('r') -> innerJoin ('r.profile', 'p') -> addSelect (' p ') -> где (' REGEXP (: fileNamePattern, r.fileNamePattern) = 1 ') -> andWhere (' p.incomingLocation = :comingLocation ') -> setParameters ([' fileNamePattern '=> $ fileName,'comingLocation' => $ location]) -> getQuery ();
Фахим
Не работает со всеми запросами. Когда у меня было это -> setParameters (массив ('insuranceCarrier' => $ insuranceCarrier, 'dateFrom' => $ dateFrom-> формат ('Ym-d'), 'dateTo' => $ dateTo-> формат ('Ym- д '),)) те остались? отметок в sql.
Дариус.В
9

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

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

Спасибо!

Анжана Силва
источник
6

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

 /**
 * 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
источник
$ query-> getFlattenedParams (); не существует
разработчик
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

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

$query->getSQL();

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

 SHOW FULL PROCESSLIST \G
lac_dev
источник
4

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

// 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
источник
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...

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

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

Винсент Пазеллер
источник
1

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

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;
Cmyker
источник
1
$sql = $query->getSQL();

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

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();
slk500
источник
Вы должны добавить текст к своему ответу, объясняющий, что делает код.
DarkMukke
0

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

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

Darius.V
источник
0

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

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

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

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

Матиас Тош
источник
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
источник
-1

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

$query->getResult()->getSql();
Джейдип Патель
источник
не забыли добавить описание своим ответом? Просто один лайнер без описания, не приемлемо.
HaveNoDisplayName
1
Чтобы распечатать SQL-запрос в Doctrine, используйте $ query-> getResult () -> getSql (); Спасибо
Джэйдип Патель
2
вместо добавления комнета отредактируйте свой ответ
HaveNoDisplayName