Doctrine - 如何打印出真正的 sql,而不仅仅是准备好的语句?
Posted
技术标签:
【中文标题】Doctrine - 如何打印出真正的 sql,而不仅仅是准备好的语句?【英文标题】:Doctrine - How to print out the real sql, not just the prepared statement? 【发布时间】:2011-01-06 21:55:17 【问题描述】:我们正在使用 Doctrine,一个 php ORM。我正在创建这样的查询:
$q = Doctrine_Query::create()->select('id')->from('MyTable');
然后在函数中添加各种 where 子句和适当的东西,像这样
$q->where('normalisedname = ? OR name = ?', array($string, $originalString));
稍后,在execute()
-ing 查询对象之前,我想打印出原始 SQL 以检查它,然后执行以下操作:
$q->getSQLQuery();
但是,这只会打印出准备好的语句,而不是完整的查询。我想看看它发送到 mysql 的内容,但它打印出一个准备好的语句,包括?
's。有什么方法可以查看“完整”查询吗?
【问题讨论】:
我发现查看完整查询的最佳方式在此答案中进行了描述:***.com/a/678310/229077 您可以利用 Doctrine 所做的工作(分析器正在显示一个可运行的查询)。详情见我的回答 【参考方案1】:getSqlQuery()
在技术上确实显示了整个 SQL 命令,但是当您还可以看到参数时它会更有用。
echo $q->getSqlQuery();
foreach ($q->getFlattenedParams() as $index => $param)
echo "$index => $param";
为了使这种模式更可重用,commentsRaw SQL from Doctrine Query Object 中描述了一种很好的方法。
【讨论】:
我知道这是一篇旧帖子,但您的两个链接都指向 404 页面。你能更新你的答案吗?我在问,因为我不确定您对$q
的意思。它似乎不是查询也不是查询生成器。
恐怕找不到更可重用的代码了。 $q
在这种情况下是一个 Doctrine 1 查询。您可能正在使用 Doctrine 2,在这种情况下,您会想要 $qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters();
这样的东西,希望对您有所帮助!【参考方案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...
在你的 twig 文件中,使用 Doctrine 的 twig helpers 过滤器:
// 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 分析器的源代码,看看他们是如何做到的。 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;
希望,这会有所帮助;-)
【讨论】:
这很有帮助!我只是把你的代码放在一个函数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'] );【参考方案3】:
$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));
【讨论】:
【参考方案4】:我对这个主题做了一些研究,因为我想调试生成的 SQL 查询并在 sql 编辑器中执行它。从所有答案中都可以看出,这是一个技术性很强的话题。
当我假设最初的问题是基于 dev-env 时,目前缺少一个非常简单的答案。您可以只使用 Symfony 分析器中的构建。只需单击 Doctrine 选项卡,滚动到要检查的查询。然后点击“查看可运行查询”,您可以直接将查询粘贴到您的 SQL 编辑器中
更多基于 UI 的方法,但非常快速且无需调试代码开销。
【讨论】:
刚刚还发现了如何在 API 调用中使用它。只需查看x-debug-token-link
的响应标头,它是关联分析器页面的 URL :)【参考方案5】:
修改了@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;
没有测试太多。
【讨论】:
【参考方案6】:一个工作示例:
$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL:
echo $query->getSQL();
// Show Parameters:
echo $query->getParameters();
【讨论】:
虽然它可以作为变量赋值,但您可能需要考虑这一点: print $query->getSQL(); foreach ($query->getParameters() as $param) print "$param->getName() -> $param->getValue()\n"; 因为你会得到一个更具可读性的输出 它带来了一点好处。当我复制 sql 时,我仍然有搜索 wichi 参数在哪里手动插入,这需要大量时间。我们想要一个带有插入参数的查询,为什么我们找不到这么久?据我所知,即使在 codeigniter 框架中,在分析器中,您也可以复制查询并立即运行而无需手动。我们在 symfony 上也需要同样的东西。【参考方案7】:$sql = $query->getSQL();
$parameters = [];
foreach ($query->getParameters() as $parameter)
$parameters[] = $parameter->getValue();
$result = $connection->executeQuery($sql, $parameters)
->fetchAll();
【讨论】:
您应该在答案中添加一些文字来解释代码的作用。【参考方案8】:如果你在mysql中记录所有查询,你可以检查你的应用程序执行的查询:
http://dev.mysql.com/doc/refman/5.1/en/query-log.html
会有更多查询,不仅是您要查找的查询,而且您可以使用 grep 来查找。
但通常->getSql();
有效
编辑:
查看我使用的所有 mysql 查询
sudo vim /etc/mysql/my.cnf
并添加这两行:
general_log = on
general_log_file = /tmp/mysql.log
然后重启mysql
【讨论】:
【参考方案9】:Doctrine 并没有向数据库服务器发送“真正的 SQL 查询”:它实际上是在使用准备好的语句,这意味着:
发送语句,以便准备(这是$query->getSql()
返回的内容)
然后,发送参数(由$query->getParameters()
返回)
并执行准备好的语句
这意味着 PHP 端永远不会有“真正的”SQL 查询——因此,Doctrine 无法显示它。
【讨论】:
Pascal:你不应该说它不是“真正的 SQL 查询”,因为准备好的语句是真正的 SQL 查询,只是参数分开发送。这种措辞可能会让人们感到困惑(例如olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients)。$query->getParameters();
不会以正确的顺序返回参数,因为它们应该出现在准备好的查询语句中
我认为这里的问题作者并不关心教义发送与否。用户和我想知道的是如何获取查询,我们可以复制粘贴并运行,而无需手动将问号替换为参数。就像在代码点火器中一样。我想我在 symfony 调试器中找到了这个,但是当我从命令行运行脚本时仍然找不到。【参考方案10】:
我写了一个简单的记录器,它可以记录插入参数的查询。 安装:
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;
【讨论】:
【参考方案11】:您可以使用以下方法轻松访问 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 (:?)
里面的参数通常是嵌套数组。
同时,如果您发现了另一种方法,请与我们分享 :)
谢谢!
【讨论】:
【参考方案12】:要在 Doctrine 中打印出 SQL 查询,请使用:
$query->getResult()->getSql();
【讨论】:
不要忘记在您的回答中添加描述?只有一个没有描述的班轮,不能接受。 在 Doctrine 中打印 sql 查询使用 $query->getResult()->getSql();谢谢 编辑你的答案而不是添加commnet【参考方案13】:我的解决方案:
/**
* 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());
【讨论】:
非常好。适用于普通查询,但我有一个使用正则表达式的查询,看起来不支持 $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(); 不适用于所有查询。当我有这个时 ->setParameters(array( 'insuranceCarrier' => $insuranceCarrier, 'dateFrom' => $dateFrom->format('Ym-d'), 'dateTo' => $dateTo->format('Ym- d'), )) 剩下的那些? sql中的标记。【参考方案14】:你可以使用:
$query->getSQL();
如果您使用 MySQL,您可以使用 Workbench 查看正在运行的 SQL 语句。 您还可以使用以下命令从 mysql 查看正在运行的查询:
SHOW FULL PROCESSLIST \G
【讨论】:
【参考方案15】: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);
【讨论】:
【参考方案16】:更清晰的解决方案:
/**
* 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);
【讨论】:
$query->getFlattenedParams();不存在 @Developer 对于较新版本的 Doctrine,您可以将getFlattenedParams()
替换为 getParameters()
。还有人可能会发现getSQL()
比getDql()
更有用。【参考方案17】:
我已经创建了一个 Doctrine2 Logger 来完成这个。它使用 Doctrine 2 自己的数据类型转换器将参数化的 sql 查询与值“水合”。
<?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;
用法示例:; 以下代码将在标准输出上回显使用 $em 实体管理器生成的任何 INSERT、UPDATE、DELETE SQL 语句,
/**@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
)
)
);
【讨论】:
当参数是像'2019-01-01'这样的日期字符串时不起作用【参考方案18】:也许它对某人有用:
// 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;
【讨论】:
【参考方案19】:没有其他真正的查询,这就是准备好的语句的工作方式。这些值绑定在数据库服务器中,而不是应用层中。
查看我对这个问题的回答:In PHP with PDO, how to check the final SQL parametrized query?
(为方便起见,在此重复:)
使用带有参数值的预处理语句不仅仅是动态创建 SQL 字符串的另一种方法。您在数据库中创建准备好的语句,然后单独发送参数值。
所以发送到数据库的可能是
PREPARE ...
,然后是SET ...
,最后是EXECUTE ....
您将无法获得像
SELECT * FROM ...
这样的 SQL 字符串,即使它会产生相同的结果,因为实际上从未将此类查询发送到数据库。
【讨论】:
以上是关于Doctrine - 如何打印出真正的 sql,而不仅仅是准备好的语句?的主要内容,如果未能解决你的问题,请参考以下文章