PDO 准备好的语句 - 参数名称中的冒号用于啥?
Posted
技术标签:
【中文标题】PDO 准备好的语句 - 参数名称中的冒号用于啥?【英文标题】:PDO prepared statement - what are colons in parameter names used for?PDO 准备好的语句 - 参数名称中的冒号用于什么? 【发布时间】:2013-06-27 12:16:40 【问题描述】:我看到很多文章在使用 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
语句参数中的冒号。
我想了解冒号的用途。
【问题讨论】:
也复制了question 【参考方案1】:SQL 语句中需要使用冒号,以指示哪些标识符是占位符。
execute()
或 bindParam()
调用中的冒号是可选的。文档指定了它们,但实现足够聪明,如果你把它们排除在外,你会明白你的意思(你还有什么意思?)。
【讨论】:
今天出现的问题请参阅此答案***.com/a/39213381 下的 cmets。奇异的;确实。莫名其妙,那也是。【参考方案2】:这是个人喜好,有些人声称它是明确的,但我没有看到任何含糊之处..它是一个参数。
就像有些人喜欢使用编号参数(使用?)而不是命名参数。
【讨论】:
【参考方案3】:bindParam 的文档要求使用冒号。即使没有它也能工作,我不会使用它,因为你不能确定 if 是否也适用于 php 的下一个版本。
【讨论】:
【参考方案4】:是的,它绝对安全,但也有可能不安全。你可能会问,这样的对比怎么会同时存在呢?好吧,恕我直言,编程世界没有终结。
安全:
自 PHP 5.1 起,PDO 作为 PHP 的内置功能提供,从那时起,在名为参数 is out 的无冒号前面加上冒号。话虽如此,10 年后 PHP 社区不再担心放弃它。为什么真的?
不安全:
没有记录。实际上,PHP 社区的好人意识到了他们的伙伴偶然容易犯的错误,并实施了这样一个避免头痛的事情,以热情地处理他们在幕后可能出现的困惑,并且由于您正在处理占位符,因此没有在任何地方记录。
占位符主要可以通过特殊符号/格式进行区分,例如您将如何键入 printf
占位符 %d %s
而不是 d s
。您只需要正确遵循占位符格式,而不是试图将其放在 PHP 的膝上。
当它没有被记录时,它有 - 甚至是一个 epsilon - 被省略的机会。
【讨论】:
好吧no finality in programming
是真的,通常在删除记录的功能之前会发出弃用通知。
可以,只要文档存在。 @chris85
听起来是***.com/documentation :D
的完美插件【参考方案5】:
TL;DR 不,您没有遗漏任何东西。您必须在 SQL 字符串中使用带有命名占位符的冒号 (:
),但在执行语句或绑定参数时不需要它们。 PHP 将推断出 @987654329 @ 如果您在该上下文中不使用它(有关 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()
或PDOStatement::execute()
时,冒号是可选的。这两者的工作原理基本相同:*
$insertRecord->execute(array(
':column1' => $column1,
':column2' => $column2
));
// or
$insertRecord->execute(array(
'column1' => $column1,
'column2' => $column2
));
为什么有效(探索 PHP 源代码)
为什么会这样?好吧,为此我们必须进入 PHP 本身的 c-language 源代码。为了保持最新状态,我使用了来自 github 的最新源代码(PHP 7),但同样的基本分析也适用于早期版本。
PHP 语言 expects named placeholders to have a colon in the SQL,如文档中所述。和the documentation for PDOStatement::bindParam()
indicates the parameter must be of the form :name
when you bind the parameter to the placeholder。但事实并非如此,原因如下。
在绑定参数或执行语句时没有歧义的风险,因为 SQL 占位符必须有一个且只有一个冒号。这意味着 PHP 解释器可以做出一个关键的假设并且安全地这样做。如果您查看pdo_sql_parser.c
in the PHP source code, particularly at line 90,您可以在占位符中看到有效的字符列表,即字母数字(数字和字母)、下划线和冒号。遵循该文件中代码的逻辑有点棘手,在这里很难解释——我很遗憾地说它涉及到 lot 的 goto
语句——但简短的版本是 只有第一个字符可以是冒号。
简单地说,:name
是 SQL 中的有效占位符,但 name
和 ::name
不是。
这意味着当您到达bindParam()
或execute()
时,解析器可以安全地假设名为name
的参数实际上应该是:name
。也就是说,它可以在参数名称的其余部分之前添加一个:
。事实上,这正是它所做的,在pdo_stmt.c
, starting at line 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()
或execute()
的上下文中)变为:name
,与我们的SQL 匹配,PDO 非常满意。
最佳实践
从技术上讲,任何一种方式都有效,所以你可以说这是一个偏好问题。但是,如果它不明显,则没有很好的记录。我必须深入研究源代码才能弄清楚这一点,理论上它可以随时改变。为了在 IDE 中保持一致性、可读性和更轻松的搜索,请使用冒号。
* 我说它们“基本上”工作相同,因为上面的 c 代码对省略冒号施加了极小的惩罚。它必须分配更多的内存,构建一个新的字符串,并替换旧的字符串。也就是说,对于:name
这样的名称,该惩罚在纳秒范围内。如果您倾向于给参数提供很长(如 64 Kb)的名称并且您有很多参数,那么它可能会变得可测量,在这种情况下您还有其他问题......无论如何,这可能都不重要,因为冒号添加读取和解析文件的时间惩罚非常小,因此这两个超小的惩罚甚至可能抵消。如果您担心这个级别的性能,那么您在晚上保持清醒的问题比我们其他人要酷得多。此外,此时,您可能应该在纯汇编程序中构建您的 web 应用程序。
【讨论】:
@Downvoter 如果您有什么要添加到主题的内容,请这样做;或者如果您看到不正确的地方,请说明。 @EdCottrell “相比之下,在使用 PDOStatement::bindParam() 或 PDOStatement::execute() 时,冒号是可选的。它们的工作原理基本相同” - 其中今天出现的这个问题似乎并非如此***.com/q/39213328 - 请参阅***.com/a/39213381 给出的答案下的 cmets - 莫名其妙?是的,你在告诉我 ;-) @Fred-ii- 这真的很奇怪。我评论了另一个答案。我想知道那里出了什么问题。 @EdCottrell 是的,我自己也注意到了。我猜到他们使用 mysql 的TYPE
关键字,它可能与它有关。在那之前,我们知道 PHP 和 MySQL 的版本,那么这仍然是一个谜。【参考方案6】:
官方文档只显示了带有冒号的语法:
$insertRecord->execute(array(
':column1' => $column1,
':column2' => $column2
));
此外,在内部(PDO 源代码),如果缺少前导冒号,则会自动添加。 所以你应该使用带冒号的语法来确定。
【讨论】:
以上是关于PDO 准备好的语句 - 参数名称中的冒号用于啥?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用准备好的 PDO 语句设置 ORDER BY 参数?
如何使用准备好的 PDO 语句设置 ORDER BY 参数?