使用未引用的命名参数时,TYPO3 抛出异常

Posted

技术标签:

【中文标题】使用未引用的命名参数时,TYPO3 抛出异常【英文标题】:TYPO3 throws exception when using unqoted named param 【发布时间】:2020-11-17 22:17:04 【问题描述】:

我正在尝试使用未加引号的参数(案例 1、1A)以类似 PDO 的样式使用准备好的语句执行原始查询,但无论如何它都会引发异常:

执行 'SELECT * FROM pages WHERE title LIKE :title' 时发生异常:您的 SQL 语法有错误;检查与您的 mysql 服务器版本相对应的手册,以在第 1 行的 ':title' 附近使用正确的语法

此外,引用命名参数不起作用(案例 2),它不会抛出异常,但也找不到任何东西。

根据需要使用未命名/编号和未引用的参数(案例 3、3A)或 executeQuery() 而不是 prepare()(案例 4)。特别是我想使用命名参数,最后一个是我的选择。

use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

... 

public function queryPagesByTitle(string $title = null): array

    /** @var Connection $conn */
    $conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');

//  Case 1: DOESN'T work with non-quoted params
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :title");
    $stmt->execute(['title' => $title]);

//  Case 1A: DOESN'T work with non-quoted params
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :title");
    $stmt->bindValue('title', $title, \PDO::PARAM_STR);
    $stmt->execute();

//  Case 1B: DOESN'T work with non-quoted,unique params
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :dcUniqueParam");
    $stmt->bindParam('dcUniqueParam', $title, \PDO::PARAM_STR);
    $stmt->execute();

//  Case 1C: DOESN'T work with non-quoted,unique params even with :colon while binding
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :dcUniqueParam");
    $stmt->bindParam(':dcUniqueParam', $title, \PDO::PARAM_STR);

//  Case 2: DOESN'T work with quoted params neither, doesn't throw an exception, but doesn;t find anything
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE ':title'");
    $stmt->execute(['title' => $title]);

//  Case 3: Works with numbered param(s)
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE ?");
    $stmt->execute([1 => $title]);

//  Case 3A: Works with numbered param(s)
    $stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE ?");
    $stmt->bindParam(1, $title, \PDO::PARAM_STR);
    $stmt->execute();

//  Case 4: Works with non-quoted named param(s)
    $stmt = $conn->executeQuery(
        "SELECT uid, title FROM pages WHERE title LIKE :title",
        ['title' => $title],
        [\PDO::PARAM_STR]
    );
    return $stmt->fetchAll(FetchMode::ASSOCIATIVE);

几个问题

    为什么第一个案例在 PDO 继承后不能像我预期的那样工作,或者 Doctrine 实际上如何 does it? 使用executeQuery() 代替prepare() 是否有一些缺点(如果有)? 我应该使用带有编号参数的prepare() 吗? 使用原始查询代替 QueryBuilder 有什么显着区别吗?

注意

我知道,为了正确处理模型数据和存储库,我可以/应该使用通用 QueryBuilder 接口。这种情况适用于我的数据库中不使用数据映射的一些原始数据,我正在寻找一些性能改进。 pages 表在这里仅用于演示概念。

【问题讨论】:

我敢打赌,它与在没有给出显式类型的情况下应用默认值有关,以及 sql LIKE的古怪之处 @NikosM。 $stmt->bindValue('title', $title, \PDO::PARAM_STR); 也抛出异常,添加case 1A 我想需要深入研究框架代码才能理解这一点。 我想这与作为字段的参数名称存在冲突。可能所有“标题”都替换为'tablename.title' @BerndWilkerπφ 逻辑结论,但仍然不是这个。我添加了带有一些非常独特的参数的案例 1B,仍然是相同的例外。至于其他 1x 案例。 【参考方案1】:

最后,这一切都归结为类似 PDO 的语句,但是使用 mysqli 作为驱动程序 (https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php) 或 pdo_mysql 作为驱动程序 (https://www.php.net/manual/en/pdo.prepared-statements.php) 之间存在本质区别。

PDO 文档 (https://www.php.net/manual/en/pdo.prepare.php) 中提到了重要的方面:

PDO 将为本身不支持它们的驱动程序模拟准备好的语句/绑定参数,并且如果驱动程序支持一种样式但不支持另一种样式,还可以将命名或问号样式参数标记重写为更合适的内容。

当使用pdo_mysql 作为驱动程序时,原始问题中给出的代码 sn-ps 可以工作 - 而不是可以在typo3conf/LocalConfiguration.php 中配置的mysqli

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'] = 'pdo_mysql';

现在关注 Doctrine DBAL 的内部细节,它也只是 mysqlipdo_mysql 的包装器 - 在内部 DBAL 使用定位参数 ? 并相应地转换命名参数。

实际上这发生在Doctrine DBAL's Connection - 命名参数被转换为定位参数(无论使用了哪个数据库驱动程序):

SELECT * FROM `pages` WHERE `title` LIKE :dcValue1

转换成

SELECT * FROM `pages` WHERE `title` LIKE ?

总结

DBAL 的Connection::executeQuery 在内部使用Connection::prepare,请参阅https://github.com/doctrine/dbal/blob/2.10.x/lib/Doctrine/DBAL/Connection.php#L886 DBAL 也使用定位/编号参数 ? - 命名参数 :dcValue 只是“虚拟” 直接使用 $stmt->bindParam 不会执行该转换过程,使用 Connection::executeQuery 会执行并解释为什么该剪辑有效

除此之外,由于您已经处于 TYPO3 环境中,您可能希望使用它的 QueryBuilder,它也在内部使用准备好的语句。

public function queryPagesByTitle(string $title = null): array

    $builder = GeneralUtility::makeInstance(ConnectionPool::class)
        ->getQueryBuilderForTable('page');
    $stmt = $builder->select('*')
        ->from('pages')
        ->where($builder->expr()->like(
            'title',
            $builder->createNamedParameter(
                '%' . $builder->escapeLikeWildcards($title) . '%',
                \PDO::PARAM_STR
            )
        ))
        ->execute();
    return $stmt->fetchAll(FetchMode::ASSOCIATIVE) ?? [];

【讨论】:

感谢您的努力,但您错了。尽管在 PDO 中没有记录,但正如在许多地方所述,绑定参数不需要在常见的 PDO 中使用冒号,可以正常工作$stmt->bindValue('deleted', 0, PDO::PARAM_INT);(经过多次测试)。抱歉,我之前测试过 :colon 案例,但没有添加到我的代码中,现在在我的问题中更新了检查案例 1C 正如我在问题中提到的,我知道 QueryBuilder,毫无疑问,这就是我在一般 TYPO3 上下文中工作时所使用的。然而,对于我数据库中的一些查询,尤其是在不是 TYPO3 特定的表中(没有 pid、隐藏、删除、sysl_language 等),使用它可能只是很大的过载。 感谢解释,实际上我从来没有在没有:colon 的情况下使用过它 - 现在我知道它也能正常工作 ;-) 我猜你正在使用 mysqli 作为数据库驱动程序,这与 @ 略有不同987654350@. 不,不,我还在谈论PDO,实际上,mysqli 本身根本不支持命名参数。这只是 PDO 中未记录的有趣事实;) 线索是在typo3conf/LocalConfiguration.php 中从mysqli 切换到pdo_mysql 在这种情况下,所有情况都有效,除了这些引用的命名参数(这是有效的)。非常感谢您的努力和调查。你能用关于设置$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'] = 'pdo_mysql';的信息更新你的答案吗?

以上是关于使用未引用的命名参数时,TYPO3 抛出异常的主要内容,如果未能解决你的问题,请参考以下文章

未定义命名参数“home” - Flutter

当请求的命名客户端未定义时,使 ASP.NET 核心 IHttpClientFactory 抛出

从调试模式切换到发布模式时,C# Asp.Net Webservice 抛出异常

TYPO3:重命名通量字段并保留值?

流体驱动的 TYPO3 - 命名空间的正确设置

Roles.GetRolesForUser 抛出异常对象引用未设置为对象的实例