在我看到的大多数SQL PHP预准备语句中,例如:
$sql = 'INSERT INTO tasks(task_name, start_date, completed_date) VALUES(:task_name, :start_date, :completed_date)';
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
':task_name' => $taskName,
':start_date' => $startDate,
':completed_date' => $completedDate,
]);
字段名称几乎重复了……4次!
INSERT INTO(...)
之后的一次:task_name
(SQL中的列名)VALUES(...)
之后的一次::task_name
:task_name
$taskName
(局部变量)我知道它们每个都有不同的含义,但是,这种冗余确实很烦人:如果我们要更改查询中的某些内容,则必须对其进行4次更改!
如何编写更好的语句,避免在PHP中有太多冗余?
这是一个非常好的问题,对此我有几个答案。
首先,您可以使用多种技巧来减少冗长程度,例如在查询中省略fields子句(并为缺少的字段在values子句中添加默认值)并使用位置占位符:
$sql = 'INSERT INTO tasks VALUES(null, ?, ?, ?)';
$this->pdo->prepare($sql)->execute([$taskName, $startDate, $completedDate]);
我称它们为技巧,因为它们并不总是适用。
下一个级别是为插入创建辅助函数。执行此操作时,必须敏锐地意识到 SQL Injection through field names。
因此,这样的辅助功能必须具有自己的辅助功能:
function escape_mysql_identifier($field){
return "`".str_replace("`", "``", $field)."`";
}
具有这样的功能,我们可以创建一个帮助器功能,该功能接受一个字段名和一个带有小数的数组
function prepared_insert($conn, $table, $data) {
$keys = array_keys($data);
$keys = array_map('escape_mysql_identifier', $keys);
$fields = implode(",", $keys);
$table = escape_mysql_identifier($table);
$placeholders = str_repeat('?,', count($keys) - 1) . '?';
$sql = "INSERT INTO $table ($fields) VALUES ($placeholders)";
$conn->prepare($sql)->execute(array_values($data));
}
我故意在这里不使用命名的占位符,因为它会使代码更短,在占位符名称中可能不允许使用字符,而对于列名(例如,空格或破折号)则完全有效;也是因为我们通常不在乎它在内部如何工作。
现在您的插入代码将变为
prepared_insert($this->pdo, 'tasks',[
'task_name' => $taskName,
'start_date' => $startDate,
'completed_date' => $completedDate,
]);
删除了很多重复项
但是我也不喜欢上面的解决方案,其中有一些怪癖。
为了满足自动化的需求,我宁愿创建一个简单的ORM。不要害怕它不像某些图片那样可怕。我最近发布了一个complete working example,因此您也可以将其用于您的案例,特别是考虑到您已经在使用OOP。
只需抛出insert()
方法
public function insert()
{
$fields = '`'.implode("`,`", $this->_fields);
$placeholders = str_repeat('?,', count($this->_fields) - 1) . '?';
$data = [];
foreach($this->_fields as $key)
{
$data[] = $this->{$key};
}
$sql = "INSERT INTO `{$this->_table}` ($fields) VALUES ($placeholders)";
$this->_db->prepare($sql)->execute($data);
}
此后,您必须准备课程,
class Task extends BaseDataMapper
{
protected $_table = "tasks";
protected $_fields = ['task_name', 'start_date', 'completed_date'];
}
然后-所有的魔术都在这里发生! -您完全不必编写插入代码!而是创建一个新的类实例,为其属性分配值,然后调用insert()
方法:
include 'pdo.php';
$task = new Task($pdo);
$task->task_name = $taskName;
$task->start_date = $startDate;
$task->completed_date = $completedDate;
$user->insert();
这不是最复杂的例子,这并不是多余的。当您必须使用不同的参数多次执行查询时,准备好的语句的优势会发挥更大的作用。
以下示例将从value
的所有行中提取1到10:
$value = 1;
$stmt = $pdo->prepare("INSER INTO (first, second, third) VALUES (?, ?, ?);");
$rowsToInsert = [["first", "second", "third"]];
foreach ($rowsToInsert as $row) {
array_map($row, function($v, $i) use ($stmt) {
$stmt->bindValue($i + 1, $v);
});
$stmt->execute();
}
此外,您也可以自由使用其他PHP逻辑来绑定参数:
$params = [
":first" => $first,
":second" => $second,
":third" => $third
];
$sql = sprintf(
"INSERT INTO (first, second, third) VALUES (%s);",
implode(" ", array_keys($params))
);
$stmt = $pdo->prepare($sql);
foreach ($params as $name => $value) {
$stmt->bindValue($name, $value);
}
$stmt->execute();