我在使用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
语句参数中的冒号。
我想了解冒号的用途。
答案 0 :(得分:21)
TL; DR 不,你没有遗漏任何东西。 必须使用冒号(:
)在SQL字符串中使用命名占位符,但在执行语句或绑定参数时不需要它们。 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本身的c语言源代码。为了保持最新,我使用了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,您可以在占位符中看到有效的字符列表,即字母数字(数字和字母),下划线和冒号。继该文件中的代码的逻辑是有点棘手,很难在这里说明一下 - 我伤心地说,它涉及到的很多的的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应用程序。< / sarcasm>
答案 1 :(得分:16)
SQL语句中需要冒号,以指示哪些标识符是占位符。
execute()
或bindParam()
来电中的冒号是可选的。文档指定了它们,但实现是否足够聪明,如果你把它们排除在外,你可以弄清楚你的意思(还有什么意思?)。
答案 2 :(得分:4)
bindParam的文档请求冒号。即使它没有工作,我也不会这样做,因为你不能确定它是否也适用于php上的下一个版本。
答案 3 :(得分:0)
这是个人偏好的事情,有些人认为它是明确的,但我没有看到任何含糊不清的东西......这是一个参数。
正如有些人喜欢使用编号参数(使用?)而不是命名参数。
答案 4 :(得分:0)
是的,它非常安全,但也有可能不安全。您可能会问这种对比如何同时存在?嗯,编程世界恕我直言中没有终结。
自PHP 5.1起,PDO附带PHP作为内置功能,从那时起将冒号前置为一个名为参数is out的无结点。说,10年后,PHP社区不会担心放弃它。为什么呢?
没有记录。实际上,PHP社区的好人都知道他们的同事偶然容易犯错误并且实施了这样一个令人头疼的事情,以热情地照顾他们可能在幕后产生的混乱,因为你正在处理占位符,所以没有记录在任何地方。
占位符大多可以与特殊符号/格式区分开来,就像您键入printf
占位符%d %s
而不是d s
一样。您只需要正确遵循占位符格式,而不是试图将其放在PHP的一圈。
如果没有记录,它有 - 甚至是epsilon - 被遗漏的机会。
答案 5 :(得分:0)
官方文档仅显示冒号的语法:
$insertRecord->execute(array(
':column1' => $column1,
':column2' => $column2
));
此外,在内部(PDO源代码),如果缺少前导冒号,它将自动添加 因此,您应该使用WITH colons语法来确定。