Как избежать повторения кода с помощью подготовленных PHP SQL операторов? - PullRequest
1 голос
/ 17 октября 2019

В большинстве примеров 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?

Ответы [ 2 ]

2 голосов
/ 18 октября 2019

Это очень хороший вопрос, и у меня есть несколько ответов на него.

Необработанный PHP

Прежде всего, вы можете использовать несколько приемов, чтобы уменьшить многословность, например, опуская предложение fields взапрос (и добавление значений по умолчанию в предложении значений для пропущенных полей) и использование позиционных заполнителей:

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

Я называю их хитростями, потому что они не всегда применимы.

Обратите внимание, чтоВы должны предоставить значение для всех столбцов в таблице. Это может быть просто значение null или, чтобы сделать его эквивалентным для пропущенного поля на 100%, вы можете указать его как DEFAULT(field_name), чтобы оно вставляло значение по умолчанию, определенное в определении таблицы.

Aвспомогательная функция

Следующим уровнем будет создание вспомогательной функции для вставок. При этом необходимо остро осознавать SQL-инъекцию через имена полей .

Следовательно, такая вспомогательная функция должна иметь собственную вспомогательную функцию:

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. Не пугайтесь этого термина, он не так чудовищен, как некоторые представляют его. У меня есть полный рабочий пример , опубликованный недавно, так что вы можете использовать его и для своего случая, особенно если вы уже используете ООП.

Просто добавьте 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();
0 голосов
/ 17 октября 2019

Это не лишний, его наименее сложный пример. Преимущества подготовленных операторов становятся более полезными, когда вам приходится многократно выполнять запрос с различными параметрами.

В следующем примере все строки выбираются по 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();
...