In den meisten Beispielen von SQL PHP vorbereitete Anweisungen, die ich sehe, wie zum Beispiel:
$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,
]);
die Feldnamen werden fast wiederholt … 4 mal!
- einmal nach dem
INSERT INTO(...)
: task_name
(Spaltenname in SQL)
- einmal nach dem
VALUES(...)
: :task_name
- Einmal im Wörterbuchschlüssel:
:task_name
- einmal im Wörterbuchwert:
$taskName
(lokale Variable)
Ich verstehe, dass jedes davon eine andere Bedeutung hat, aber trotzdem ist diese Redundanz wirklich ärgerlich: Wenn wir etwas in der Abfrage ändern wollen, müssen wir es 4 Mal ändern!
Wie kann man eine besser vorbereitete Anweisung haben, die so viel Redundanz in PHP vermeidet?
Das ist eine sehr gute Frage und ich habe mehrere Antworten darauf.
Rohes PHP
Zunächst einmal können Sie mehrere Tricks anwenden, um die Ausführlichkeit zu reduzieren, z. B. das Weglassen der fields-Klausel in der Abfrage (und das Hinzufügen von Standardwerten in der values-Klausel für die fehlenden Felder) und die Verwendung von Positionsplatzhaltern:
$sql="INSERT INTO tasks VALUES(null, ?, ?, ?)";
$this->pdo->prepare($sql)->execute([$taskName, $startDate, $completedDate]);
Ich nenne sie Tricks, weil sie nicht immer anwendbar sind.
Beachten Sie, dass Sie für alle Spalten in der Tabelle einen Wert angeben müssen. Es könnte einfach ein sein null
value oder, um es für das ausgelassene Feld zu 100 % äquivalent zu machen, könnten Sie es als haben DEFAULT(field_name)
Daher wird ein in der Tabellendefinition definierter Standardwert eingefügt.
Eine Hilfsfunktion
Die nächste Stufe wäre die Erstellung einer Hilfsfunktion für Einfügungen. Dabei muss man sein akut bewusst des SQL-Injektion durch Feldnamen.
Daher muss eine solche Hilfsfunktion eine eigene Hilfsfunktion haben:
function escape_mysql_identifier($field){
return "`".str_replace("`", "``", $field)."`";
}
Mit einer solchen Funktion können wir eine Hilfsfunktion erstellen, die einen Tabellennamen akzeptiert und ein Datenarray enthält field name => value
Paare:
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));
}
Ich verwende hier absichtlich keine benannten Platzhalter, weil es den Code kürzer macht, es könnten Zeichen sein, die in Platzhalternamen nicht erlaubt sind, während sie für die Spaltennamen, ein Leerzeichen oder einen Bindestrich zum Beispiel, vollkommen gültig sind; und auch, weil es uns generell egal ist, wie es drinnen funktioniert.
Jetzt wird Ihr Einfügecode
prepared_insert($this->pdo, 'tasks',[
'task_name' => $taskName,
'start_date' => $startDate,
'completed_date' => $completedDate,
]);
mit so vielen Wiederholungen entfernt
Ein Baby-ORM
Allerdings gefällt mir die obige Lösung auch nicht, da sind einige Macken drin.
Um die Notwendigkeit der Automatisierung zu erfüllen, würde ich lieber ein einfaches ORM erstellen. Lassen Sie sich von dem Begriff nicht erschrecken, es ist nicht so monströs, wie manche es sich vorstellen. Ich habe kürzlich ein vollständiges Arbeitsbeispiel veröffentlicht, damit Sie es auch für Ihren Fall verwenden können, insbesondere wenn Sie bereits OOP verwenden.
Einfach einwerfen insert()
Methode
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);
}
Danach müssen Sie Ihre Klasse vorbereiten,
class Task extends BaseAcctiveRecord
{
protected $_table = "tasks";
protected $_fields = ['task_name', 'start_date', 'completed_date'];
}
und dann – all die Magie passiert hier! – Sie müssen den Einfügecode überhaupt nicht schreiben! Erstellen Sie stattdessen einfach eine neue Instanz Ihrer Klasse, weisen Sie ihren Eigenschaften Werte zu und rufen Sie dann einfach die auf insert()
Methode:
include 'pdo.php';
$task = new Task($pdo);
$task->task_name = $taskName;
$task->start_date = $startDate;
$task->completed_date = $completedDate;
$user->insert();
Dies ist nicht überflüssig, es ist das am wenigsten komplexe Beispiel.
Die Vorteile von Prepared Statements kommen stärker zum Tragen, wenn Sie eine Abfrage mehrfach mit unterschiedlichen Parametern ausführen müssen.
Das folgende Beispiel ruft alle Zeilen nach ab value
von 1 bis 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();
}
Es steht Ihnen auch frei, andere PHP-Logik zu verwenden, um Parameter zu binden:
$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();
.
Verwenden Sie keine Namen, verwenden Sie stattdessen Platzhalter (?). Ich persönlich verwende gerne die Namen, weil es glasklar wird, wie die Dinge zusammenhängen.
– David
17. Oktober 19 um 19:57 Uhr
@Dave kannst du ein Beispiel posten? Bei vielen Variablen wird die Redundanz sichtbar.
– Basj
17. Oktober 19 um 20:06 Uhr
Aus diesem Grund verwenden Sie einen ORM (Object-Relational Mapper). Anstatt hässliche, injektionsanfällige SQL-Strings manuell aufzubauen, verwenden Sie geeignete Objekte und legen ihre Eigenschaften und ihren Status fest. Dann weisen Sie das ORM an, die Änderungen beizubehalten, und es generiert und führt das gesamte SQL für Sie aus.
– Ian Kemp
17. Oktober 19 um 20:16 Uhr
Erstellen Sie Ihr eigenes CRUD-Objekt, in das Ihr Datenbankobjekt eingefügt wird. Dann könnte Ihr Code Ihren eigenen Custom aufrufen
update()
Methode. Dann könnten Sie statt all dem Zeug, das Sie nervt, so etwas wie sehen$tableName->update($id, [‘field’=>’value’]
– Tim Morton
17. Oktober 19 um 21:29 Uhr
Ich werde versuchen, es zu erreichen; nicht sicher, wie viel Zeit ich zur Verfügung habe. Oh, und ich meinte einfügen, was so einfach sein könnte wie
$tableName->insert(['field1'=>'value1','field2'=>'value2']);
– Tim Morton
17. Oktober 19 um 21:42 Uhr