使用 Doctrine 查询构建器正确转义 LIKE 查询

Posted

技术标签:

【中文标题】使用 Doctrine 查询构建器正确转义 LIKE 查询【英文标题】:Proper escaping of LIKE queries with Doctrine querybuilder 【发布时间】:2015-09-15 11:15:19 【问题描述】:

我正在尝试使用 Doctrine QueryBuilder 进行 LIKE 查询。我阅读了其他各种问题和文章,我必须正确逃避这种类型的查询,但我不明白 Doctrine 是否自己做。以此数据为例:

my_column
ABC
ABCD
A%BCD

以及下面的输入数据

ABC
ABCD
A
A%

我期待这些结果:

SELECT * FROM my_table WHERE my_column LIKE "%ABC%" => ABC, ABCD
SELECT * FROM my_table WHERE my_column LIKE "%ABCD%" => ABCD
SELECT * FROM my_table WHERE my_column LIKE "%A%" => ABC, ABCD, A, A%
SELECT * FROM my_table WHERE my_column LIKE "%A%%" => A%

我的问题与最新查询(和输入数据)有关。我应该如何正确逃避该查询? '%' . addcslashes($input, '%_') . '%'够了吗?

如果有帮助,我准备了这个 SQL Fiddle:http://sqlfiddle.com/#!9/35bc8/9

【问题讨论】:

只要使用参数就可以了。将 % 符号应用于您的数据。 “选择……喜欢?” setParameter(0,'%' . 'ABC' . '%') 根据要点gist.github.com/johnkary/9770413 我应该逃避输入数据。按照您的建议使用 setParameter 返回不正确的结果(ABC、ABCD、A、A%) 我不确定您链接的要点的上下文。 DQL 上有大量错误信息。也许手册可能会有所帮助:symfony.com/doc/current/book/…(搜索 LIKE 以获取示例)。如果您得到意想不到的结果,那么您可能会仔细查看实际查询。也许用真实的代码更新你的问题。但我 100% 确信您在使用参数化语句时不需要显式转义数据。 参数化语句不需要为引号或反斜杠等字符显式转义,但 LIKE 语句显然不能正确转义字符,如 % 或 _ 导致像我写的最后一个 SQL 查询在小提琴中。要将这些字符视为文字,必须进行手动转义,如链接的要点中所述。剩下的唯一问题是为什么转义字符会自行转义。 不管这里的最终答案是什么,我觉得我应该指出包含LIKE '%...%' 的查询真的真的对性能不利。您基本上是在告诉数据库加载每条记录并单独解析它们以搜索您的字符串,然后丢弃不匹配的记录。如果您的数据库甚至有中等数量的记录,那么这种查询将像糖蜜一样运行缓慢。甚至不要考虑进行连接。如果您需要,有一些很好的方法可以进行复杂的搜索。 LIKE %...% 不是其中一种方式。 【参考方案1】:

正如 John Kary 在 this gist 中发现的那样,Doctrine 不会逃避 LIKE 查询语句。更具体地说,它使用准备好的语句对参数进行转义(反斜杠或引号等字符被正确转义),但%_ 等作为LIKE 语句语法一部分的字符不会被转义,输入也不会被转义消毒。下面链接的要点提供了一种解决此问题的好方法,经过 Symfony 2.6 和 Doctrine 2.5 测试,它运行良好。为了确保不会删除要点,我将代码复制到这里:

<?php
namespace Foo;

/**
 * Methods for safe LIKE querying.
 */
trait LikeQueryHelpers

    /**
     * Format a value that can be used as a parameter for a DQL LIKE search.
     *
     * $qb->where("u.name LIKE (:name) ESCAPE '!'")
     *    ->setParameter('name', $this->makeLikeParam('john'))
     *
     * NOTE: You MUST manually specify the `ESCAPE '!'` in your DQL query, AND the
     * ! character MUST be wrapped in single quotes, else the Doctrine DQL
     * parser will throw an error:
     *
     * [Syntax Error] line 0, col 127: Error: Expected Doctrine\ORM\Query\Lexer::T_STRING, got '"'
     *
     * Using the $pattern argument you can change the LIKE pattern your query
     * matches again. Default is "%search%". Remember that "%%" in a sprintf
     * pattern is an escaped "%".
     *
     * Common usage:
     *
     * ->makeLikeParam('foo')         == "%foo%"
     * ->makeLikeParam('foo', '%s%%') == "foo%"
     * ->makeLikeParam('foo', '%s_')  == "foo_"
     * ->makeLikeParam('foo', '%%%s') == "%foo"
     * ->makeLikeParam('foo', '_%s')  == "_foo"
     *
     * Escapes LIKE wildcards using '!' character:
     *
     * ->makeLikeParam('foo_bar') == "%foo!_bar%"
     *
     * @param string $search        Text to search for LIKE
     * @param string $pattern       sprintf-compatible substitution pattern
     * @return string
     */
    protected function makeLikeParam($search, $pattern = '%%%s%%')
    
        /**
         * Function defined in-line so it doesn't show up for type-hinting on
         * classes that implement this trait.
         *
         * Makes a string safe for use in an SQL LIKE search query by escaping all
         * special characters with special meaning when used in a LIKE query.
         *
         * Uses ! character as default escape character because \ character in
         * Doctrine/DQL had trouble accepting it as a single \ and instead kept
         * trying to escape it as "\\". Resulted in DQL parse errors about "Escape
         * character must be 1 character"
         *
         * % = match 0 or more characters
         * _ = match 1 character
         *
         * Examples:
         *      gloves_pink   becomes  gloves!_pink
         *      gloves%pink   becomes  gloves!%pink
         *      glo_ves%pink  becomes  glo!_ves!%pink
         *
         * @param string $search
         * @return string
         */
        $sanitizeLikeValue = function ($search) 
            $escapeChar = '!';
            $escape = [
                '\\' . $escapeChar, // Must escape the escape-character for regex
                '\%',
                '\_',
            ];
            $pattern = sprintf('/([%s])/', implode('', $escape));
            return preg_replace($pattern, $escapeChar . '$0', $search);
        ;
        return sprintf($pattern, $sanitizeLikeValue($search));
    

并且它的使用就像这个例子:

<?php
namespace Foo\Entity;

use Doctrine\ORM\EntityRepository;
use Foo\LikeQueryHelpers;

class ProductRepository extends EntityRepository

    use LikeQueryHelpers;
    /**
     * Find Product entities containing searched terms
     *
     * @param string $term
     * @return Product[]
     */
    public function findInSearchableFields($term)
    
        return $this->createQueryBuilder('p')
            ->where("p.title LIKE :title ESCAPE '!'")
            ->setParameter('title', $this->makeLikeParam($term))
            ->getQuery()
            ->execute();
    

【讨论】:

这似乎没有必要。当您使用准备好的语句时,您不需要转义参数。见:docs.doctrine-project.org/projects/doctrine-dbal/en/latest/… 从答案中引用,Doctrine 确实转义了输入参数,但不是 LIKE SQL 语句的语法,所以如果你在参数的值内加上引号,它将被转义,但如果你放一个% 签名它不会,这将使您的应用程序面临安全问题 嗯 % 不会被转义,因为在参数中包含 % 不是安全风险,因为它不会导致 SQL 注入等问题。如果您想使用 % 作为文字或通配符,这取决于您。所以这是你的设计决定。是否是安全问题取决于您的设计。 实际上不在 LIKE 语句中转义 % 是一个安全问题,因为它可能会导致您的应用程序出现性能问题,因此是拒绝服务向量,如下所述:githubengineering.com/like-injection 就像我说的,这取决于你的设计。默认情况下,我可能想进行搜索并在其中使用 % 并带有限制子句。使用它可能是完全可以接受的。我知道一些允许使用通配符的服务。如果您说的是真的,并且在 100% 的情况下存在安全风险,则默认情况下会被规避。

以上是关于使用 Doctrine 查询构建器正确转义 LIKE 查询的主要内容,如果未能解决你的问题,请参考以下文章

Doctrine查询构建器

Doctrine查询构建器 - 参数太少

在 Symfony Doctrine 查询构建器中使用 PostgreSQL NOT SIMILAR TO

首先在doctrine查询构建器中选择完全匹配

避免使用Doctrine DBAL查询构建器select语句将驼峰别名名称转换为小写

用于标量查询的 Doctrine 返回数组数组