如何避免用PHP SQL预准备语句重复代码?

问题描述 投票:1回答:2

在我看到的大多数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中有太多冗余?

php sql sqlite prepared-statement sql-insert
2个回答
1
投票

这是一个非常好的问题,对此我有几个答案。

首先,您可以使用多种技巧来减少冗长程度,例如在查询中省略fields子句(并为缺少的字段在values子句中添加默认值)并使用位置占位符:

原始PHP

$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

但是我也不喜欢上面的解决方案,其中有一些怪癖。

为了满足自动化的需求,我宁愿创建一个简单的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();

-2
投票

这不是最复杂的例子,这并不是多余的。当您必须使用不同的参数多次执行查询时,准备好的语句的优势会发挥更大的作用。

以下示例将从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();
© www.soinside.com 2019 - 2024. All rights reserved.