首页 > 解决方案 > 使用未引用的命名参数时,TYPO3 抛出异常

问题描述

我正在尝试使用未引用的参数(案例 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);
}

几个问题

  1. 为什么第一个案例在 PDO 继承之后不能像我预期的那样工作,或者 Doctrine实际上是如何做到的?
  2. executeQuery()使用而不是有一些缺点(如果有的话)prepare()吗?
  3. 我应该使用prepare()带编号的参数吗?
  4. 使用原始查询而不是 QueryBuilder 之间有什么显着区别吗?

笔记

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

标签: typo3typo3-10.xdoctrine-dbal

解决方案


最后,这一切都归结为类似 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用作驱动程序时有效 - 而不是mysqli可以在typo3conf/LocalConfiguration.php

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

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

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

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

转换为

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

概括


除此之外,由于您已经处于 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) ?? [];
}

推荐阅读