TYPO3 在使用 unqoted 命名参数时抛出异常

TYPO3 throws exception when using unqoted named param

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

An exception occurred while executing 'SELECT * FROM pages WHERE title LIKE :title': You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ':title' at line 1

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

使用 unnamed/numbered 和不带引号的参数(案例 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 实际上如何 does it
  2. 使用 executeQuery() 而不是 prepare() 是否有一些缺点(如果有的话)?
  3. 我应该使用带有编号参数的 prepare() 吗?
  4. 使用原始查询而不是 QueryBuilder 之间有什么显着差异吗?

备注

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

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

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

PDO will emulate prepared statements/bound parameters for drivers that do not natively support them, and can also rewrite named or question mark style parameter markers to something more appropriate, if the driver supports one style but not the other.

原始问题中给出的代码片段在使用 pdo_mysql 作为驱动程序时有效 - 而不是可以在 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 ?

总结


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