21

我看到很多文章:在使用 PDO 时在命名参数前使用冒号 (),还有几篇不使用冒号。我会尽快不使用冒号,因为它减少了一次击键并且更易于阅读。

它似乎对我来说工作得很好,但我很好奇在使用冒号时我是否遗漏了一些重要的东西?

例如,这很好用:

function insertRecord ($conn, $column1, $comumn2) {
    try {
        $insertRecord = $conn->prepare('INSERT INTO Table1 (column1, column2)
        VALUES(:column1, :column2)');
        $insertRecord->execute(array(
                'column1' => $column1,
                'column2' => $column2
            ));
    }
    catch(PDOException $e) {
        echo $e->getMessage();
    }
}

与大多数开发人员使用它相反,它也有效:

function insertRecord ($conn, $column1, $comumn2) {
    try {
        $insertRecord = $conn->prepare('INSERT INTO Table1 (column1, column2)
        VALUES(:column1, :column2)');
        $insertRecord->execute(array(
                ':column1' => $column1,
                ':column2' => $column2
            ));
    }
    catch(PDOException $e) {
        echo $e->getMessage();
    }
}

execute注意语句参数中的冒号。

我想了解冒号的用途。

4

6 回答 6

28

TL; DR不,您没有遗漏任何东西。您必须:在 SQL 字符串中使用带有命名占位符的冒号 ( ),但在执行语句或绑定参数时不需要它们。如果您在该上下文中将其关闭, PHP 将推断 a :(请参阅下面的第二部分,以获取 PHP 解释器本身源代码的解释和证明)。

什么有效(你可以在 PHP 中做什么)

换句话说,这是可以接受的:

$insertRecord = $conn->prepare('INSERT INTO Table1 (column1, column2)
    VALUES(:column1, :column2)');
//         ^         ^  note the colons

但这不是,因为占位符名称不明确,看起来像列(或其他)名称:

$insertRecord = $conn->prepare('INSERT INTO Table1 (column1, column2)
    VALUES(column1, column2)');
//         ^        ^  no colons

相比之下,使用PDOStatement::bindParam()or时冒号是可选的PDOStatement::execute()。这两者的工作方式基本相同:*

$insertRecord->execute(array(
    ':column1' => $column1,
    ':column2' => $column2
));
// or
$insertRecord->execute(array(
    'column1' => $column1,
    'column2' => $column2
));

为什么有效(探索 PHP 源代码)

为什么它会这样工作?好吧,为此我们必须了解 PHP 本身的语言源代码。为了保持最新,我使用了来自 github (PHP 7) 的最新源代码,但同样的基本分析适用于早期版本。

如文档中所述,PHP 语言希望命名占位符在 SQL 中有一个冒号。当您将参数绑定到 placeholder 时,用于指示参数的文档PDOStatement::bindParam()必须是表单:name。但这并不是真的,原因如下。

在绑定参数或执行语句时没有歧义的风险,因为 SQL 占位符必须有一个且只有一个冒号。这意味着 PHP 解释器可以做出一个关键的假设并且安全地这样做。如果您查看pdo_sql_parser.cPHP 源代码,尤其是第 90 行,您可以看到占位符中的有效字符列表,即字母数字(数字和字母)、下划线和冒号。遵循该文件中代码的逻辑有点棘手,在这里很难解释——我很遗憾地说它涉及到很多语句——但简短的goto版本是只有第一个字符可以是冒号。

简而言之,:name是 SQL 中的有效占位符,但name不是::name

这意味着解析器可以安全地假设你到达的时候bindParam()或者execute()一个名为的参数name真的应该是:name. 也就是说,它可以:在参数名称的其余部分之前添加一个。事实上,这正是它所做的pdo_stmt.c,从第 362 行开始

if (param->name) {
    if (is_param && param->name[0] != ':') {
        char *temp = emalloc(++param->namelen + 1);
        temp[0] = ':';
        memmove(temp+1, param->name, param->namelen);
        param->name = temp;
    } else {
        param->name = estrndup(param->name, param->namelen);
    }
}

这样做是在稍微简化的伪代码中:

if the parameter has a name then
    if the parameter name does not start with ':' then
        allocate a new string, 1 character larger than the current name
        add ':' at the start of that string
        copy over the rest of the name to the new string
        replace the old string with the new string
    else
        call estrndup, which basically just copies the string as-is (see https://github.com/php/php-src/blob/1c295d4a9ac78fcc2f77d6695987598bb7abcb83/Zend/zend_alloc.h#L173)

因此,name(在bindParam()or的上下文中execute())变为:name,这与我们的 SQL 匹配,并且 PDO 非常满意。

最佳实践

从技术上讲,任何一种方式都有效,所以你可以说这是一个偏好问题。但是,如果它不明显,则没有很好的记录。我必须深入研究源代码才能弄清楚这一点,理论上它可以随时改变。为了在 IDE 中保持一致性、可读性和更轻松的搜索,请使用冒号。


* 我说它们“基本上”工作相同,因为上面的 c 代码对省略冒号施加了极小的惩罚。它必须分配更多的内存,构建一个新的字符串,并替换旧的字符串。也就是说,对于像这样的名称,惩罚在纳秒范围内:name. 如果您倾向于给参数提供很长(如 64 Kb)的名称并且您有很多参数,那么它可能会变得可测量,在这种情况下您还有其他问题......无论如何,这可能都不重要,因为冒号添加读取和解析文件的时间惩罚非常小,因此这两个超微小的惩罚甚至可能抵消。如果您担心这个级别的性能,那么您在晚上保持清醒的问题比我们其他人要酷得多。此外,此时,您可能应该在纯汇编程序中构建您的 web 应用程序。</sarcasm>

于 2016-08-12T19:24:54.983 回答
18

SQL 语句中需要冒号,以指示哪些标识符是占位符。

execute()or调用中的冒号bindParam()是可选的。文档指定了它们,但实现足够聪明,如果你把它们排除在外,你可以弄清楚你的意思(你还有什么意思?)。

于 2013-06-30T02:16:59.867 回答
4

bindParam的文档要求使用冒号。即使没有它也可以工作,我不会使用它,因为您无法确定是否也适用于 php 的下一个版本。

于 2014-01-02T11:10:46.373 回答
0

这是个人喜好,有些人声称它是明确的,但我没有看到任何含糊之处..它是一个参数。

就像有些人喜欢使用编号参数(使用?)而不是命名参数一样。

于 2013-06-30T02:22:36.400 回答
0

是的,它绝对安全,但也有可能不安全。你可能会问,这样的对比怎么会同时存在呢?好吧,恕我直言,编程世界没有终结。

安全的:

自 PHP 5.1 起,PDO 作为 PHP 的内置功能提供,从那时起,在没有冒号的名称参数前面加上冒号是 out。话虽如此,10 年后 PHP 社区不再担心放弃它。为什么真的?

不安全:

它没有记录。实际上,PHP 社区的好人意识到了他们的伙伴偶然容易犯的错误,并实施了这样一个避免头痛的事情,以热情地处理他们在幕后可能出现的困惑,并且由于您正在处理占位符,因此没有在任何地方记录。

占位符通常可以通过特殊符号/格式来区分,例如您如何键入printf占位符%d %s而不是d s. 您只需要正确遵循占位符格式,而不是试图将其放在 PHP 的膝上。

当它没有被记录时,它有 - 甚至是一个 epsilon - 被省略的机会。

于 2016-08-10T10:31:01.197 回答
0

官方文档仅显示带有冒号的语法:

$insertRecord->execute(array(
    ':column1' => $column1,
    ':column2' => $column2
));

此外,在内部(PDO 源代码),如果缺少前导冒号,它将自动添加。
所以你应该使用带冒号的语法来确定。

于 2016-08-14T16:54:07.487 回答