【问题标题】:Which tokens can be parameterized in PDO prepared statements?哪些标记可以在 PDO 准备语句中参数化?
【发布时间】:2010-12-10 00:01:05
【问题描述】:

我正在使用 PHP/PDO 中的准备好的语句。基本查询工作正常,将值传递给 WHERE 子句:

$stmt = $db->prepare( 'SELECT title FROM episode WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$id = 5;
$stmt->execute();

但是,我有一种情况需要为字段名称传递变量。此查询(具有适当的绑定)工作正常:

SELECT :field FROM episode WHERE id=:id

这个报错:

SELECT title FROM :field WHERE id=:id

这个不报错,但不返回行:

SELECT title FROM episode WHERE :field=:id

那么,准备好的语句中应该有哪些内容?我可以“参数化”字段名、表名等吗?

【问题讨论】:

    标签: php pdo prepared-statement


    【解决方案1】:

    您也不能参数化 IN 子句中的任何内容。

    【讨论】:

    • 谢谢,我忘了。我会把它添加到我的答案中。
    【解决方案2】:

    您不能参数化表名、列名或IN 子句中的任何内容(感谢 c0r0ner 提供pointing out the IN clause restriction)。

    查看this question,随后查看this comment in the PHP manual

    【讨论】:

    • 感谢您的回复。事实证明,我发布的第一个查询不起作用 - 如果您将 'title' 绑定到 :field,那么它只会选择字符串“标题”而不是字段的值。奇怪的是没有绑定列/表的方法,因为现在我必须添加一些额外的安全性,PDO 应该为我处理:/
    • 这背后的想法可能是你真的不应该让你的用户直接选择你的查询调用的字段/表。但我同意它确实为您增加了一些额外的工作。
    • 我明白你的意思。但是,我在只指定表的情况下使用它,但是以抽象的方式,例如displayTable('episode')。我想在这种情况下我真的不需要担心参数/安全性。
    • 但是在你的情况下,不需要参数化。我明白你为什么一开始倾向于尝试它。 (我也是,就在我发现限制之前的一周左右。)参数化的目的是防止来自用户的注入,您不必担心自己的私有表/列名。跨度>
    • 说不能参数化“IN 子句中的任何内容”是不正确的。 IN 子句中的文字可以像 SQL 语句中其他任何地方的文字一样进行参数化。问题是IN 经常被用来列出人们试图用一个参数来参数化的一系列文字——这是行不通的。每个字面量必须有一个参数。
    【解决方案3】:

    @乔什·莱泽尔

    这种想法非常具有限制性(在我看来,这只是懒惰实施稳健解决方案的借口),尤其是对于在数据库中表达的动态树结构。

    考虑以下示例:

    我的项目有一个逻辑结构:

    公司层次结构以实体表示。在一般情况下,每个实体都可以被视为层次结构的成员或层次结构的特定级别的成员。层次结构本身在表中定义为单个树分支,如下所示:

    entity_structure (
       id
       name
       parent_entity_structure_id
    );
    

    实体本身表示为:

    entities (
       id
       name
       entity_structure_id
       parent_id
    );
    

    为了便于使用,我构建了一个算法来创建树的平面视图。下面的具体例子说明了我的意思:

    SELECT * FROM entity_structure;
    
    id      | name               | entity_structure_parent_id
    -----------------------------------------------------------
    1       | Company            | null    (special one that always exists)
    2       | Division           | 1
    3       | Area               | 2
    4       | Store              | 3
    

    这将导致生成以下平面表示:

    entity_tree (
       entity_id
       division_id
       area_id
       store_id
    )
    

    处于分区级别的实体将divide_id、area_id和store_id为NULL,一个区域area_id和store_id为NULL,等等。

    这样做的好处是它允许您使用类似于以下的语句查询一个部门的所有子项:

    SELECT * FROM entity_tree WHERE division_id = :division_id;
    

    但是,这假设我知道我正在查询的实体的结构级别。这样做会很好:

    SELECT * FROM entity_tree WHERE :structure = :entity_id;
    

    我知道找出单个实体的结构级别并不难,但假设我正在循环遍历可能并非都处于同一级别的实体集合。现在我必须为层次结构的每个级别构建一个单独的查询,但如果我可以参数化字段,我可以执行以下操作:

    $children = array();
    $stmt = $pdo->prepare('SELECT entity_id FROM entity_tree WHERE :structure = :entityId');
    foreach ($entities AS $entity) {
       $stmt->execute(array(
          ':structure' = $entity->getEntityStructureId(),
          ':entityId'  = $entity->getId()
       ));
    
       $children[$entity->getId()] = $stmt->fetchAll(PDO::FETCH_COLUMN);
    }
    

    导致代码更简洁,并且只有一个准备好的语句。

    整个示例不使用任何用户输入。

    只是需要考虑的事情。

    【讨论】:

    • 有什么意义呢?由于没有用户输入,您可以在此实例中轻松使用常规变量。这里没有任何注射的机会。此外,这应该作为对我的回答的评论发布,因为这不是对 OP 问题的回答。 (我意识到你不可能适应它,只是注意到这一点。)
    最近更新 更多