且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-12-10 15:54:37

这是一个很好的问题,我有几个答案.

That's a very good question and I have several answers for it.

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

First of all, you can use several tricks to reduce the verbosity, like omitting the fields clause in the query (and adding default values in the values clause for the missing fields) and using positional placeholders:

$sql = 'INSERT INTO tasks VALUES(null, ?, ?, ?)';
$this->pdo->prepare($sql)->execute([$taskName, $startDate, $completedDate]);

我称它们为技巧,因为它们并不总是适用.

I call them tricks because they aren't always applicable.

请注意,您必须为表中的所有列提供一个值.它可能只是一个 null 值,或者,为了使其 100% 等效于省略的字段,您可以将其设置为 DEFAULT(field_name) 这样它就会插入一个默认值在表定义中定义.

Note that you must provide a value for all the columns in the table. It could be simply a null value or, to make it 100% equivalent for the omitted field, you could have it as DEFAULT(field_name) so it will insert a default value defined in the table definition.

下一级是为插入创建一个辅助函数.执行此操作时,必须敏锐地意识到SQL 注入> 通过字段名称.

The next level would be creation of a helper function for inserts. When doing this, one must be acutely aware of the SQL Injection through field names.

因此,这样的辅助函数必须有自己的辅助函数:

Hence, such a helper function must have a helper function of its own:

function escape_mysql_identifier($field){
    return "`".str_replace("`", "``", $field)."`";
}

有了这样一个函数,我们可以创建一个辅助函数,它接受一个表名和一个包含field name =>的数据数组.值对:

Having such a function we can create a helper function that accepts a table name and a data array contains field name => value pairs:

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));
} 

我故意不在这里使用命名占位符,因为它使代码更短,占位符名称中可能存在不允许使用的字符,而对于列名称,例如空格或破折号完全有效;也因为我们通常不关心它在内部是如何工作的.

I am intentionally not using named placeholders here because it makes the code shorter, there could be characters disallowed in placeholder names while being perfectly valid for the column names, a space or a dash for example; and also because we generally don't care how it works inside.

现在你的插入代码会变成

Now your insert code will become

prepared_insert($this->pdo, 'tasks',[
    'task_name' => $taskName,
    'start_date' => $startDate,
    'completed_date' => $completedDate,
]);

删除了这么多重复

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

However I don't like the above solution either, there are some quirks in it.

为了满足自动化的需要,我宁愿创建一个简单的 ORM.不要被这个词吓到了,它不像某些图片那样可怕.我最近发布了一个完整的工作示例,因此您也可以将其用于您的案例,尤其是考虑到您已经在使用 OOP.

To fulfill the need for the automation, I would rather create a simple ORM. Don't be scared by the term it is not as monstrous as some picture it. I have a complete working example posted recently so you can use it for your case as well, especially given you are already using 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);
}

之后,您必须准备课程,

After that you will have to prepare your class,

class Task extends BaseAcctiveRecord
{
    protected $_table = "tasks";
    protected $_fields = ['task_name', 'start_date', 'completed_date'];
}

然后——所有的魔法都发生在这里!- 您根本不必编写插入代码! 只需创建类的新实例,为其属性赋值,然后调用 insert() 方法:

and then - all the magic happens here! - you won't have to write the insert code at all! Instead just create a new instance of your class, assign values to its properties and then just call the insert() method:

include 'pdo.php';
$task = new Task($pdo);
$task->task_name = $taskName;
$task->start_date = $startDate;
$task->completed_date = $completedDate;
$user->insert();