Wie vermeide ich Codewiederholungen mit vorbereiteten PHP-SQL-Anweisungen?

Lesezeit: 7 Minuten

Wie vermeide ich Codewiederholungen mit vorbereiteten PHP SQL Anweisungen
Basj

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?

  • 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


Wie vermeide ich Codewiederholungen mit vorbereiteten PHP SQL Anweisungen
Ihr gesunder Menschenverstand

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

  • Vielen Dank, auch interessante Antwort! Ich gehe für $pdo->prepare('INSERT INTO tasks VALUES(null, ?, ?, ?)')->execute([$taskName, $startDate, $completedDate]); was sauber ist.

    – Basj

    18. Oktober 19 um 9:05 Uhr

  • PS: Ein Trick dabei ist, nicht alle Spaltennamen zu schreiben INSERT INTO tasks(col1, col2, col3, ...) VALUES (?, ?, ?, ...) aber eben INSERT INTO tasks VALUES(?, ?, ?, ...). Dies funktioniert nur beim Einfügen alle Spalten, aber gibt es einen ähnlichen Trick beim Einfügen alle Spalten außer der id integer primary key autoincrement Spalte (für diese wollen wir keine ? aber wir wollen, dass es automatisch ausgefüllt wird)

    – Basj

    18. Oktober 19 um 9:47 Uhr

  • @Basj das ist genau das, was in meiner Antwort gesagt wurde, also gibt es eine null Wert für die ID. Sie können kein Feld in der Werteklausel für diese Syntax weglassen, egal ob es sich um einen Primärschlüssel handelt oder nicht.

    – Ihr gesunder Menschenverstand

    18. Oktober 19 um 9:54 Uhr


  • Bravo! Sie haben es in ungefähr 12.000 Zeichen weniger gesagt als mein Versuch, es zu erklären … Interessanterweise ist die Notwendigkeit, ein Objekt in der zu durchlaufen insert() Methode hat sich gerade in dem Projekt vorgestellt, an dem ich arbeite. Sprechen Sie rechtzeitig!

    – Tim Morton

    18. Oktober 19 um 18:56 Uhr

1643212448 174 Wie vermeide ich Codewiederholungen mit vorbereiteten PHP SQL Anweisungen
Code-Geist

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

  • Vielen Dank für Ihre Antwort. Um eine Ähnlichkeit mit der ursprünglichen Frage zu haben, wäre es interessant, sie mit INSERT anstelle von SELECT zu haben, da mit INSERT die Anweisung an lokale Variablen gebunden werden muss (ich habe das Tag sql-insert).

    – Basj

    17. Oktober 19 um 20:00 Uhr


  • Außerdem hat das von Ihnen bereitgestellte Beispiel nur eine Variable valuewird die Redundanz deutlicher, wenn Sie viele Variablen haben.

    – Basj

    17. Oktober 19 um 20:02 Uhr

  • PS: In Ihrer aktualisierten Version haben Sie immer noch :first, $first und INSERT INTO (first...): Das ist die Redundanz, von der ich sprach.

    – Basj

    17. Oktober 19 um 20:03 Uhr

  • @Basj Sie könnten auch sagen, dass es überflüssig ist, es einzugeben $ vor jeder Variablendeklaration. Es ist einfach so, wie es ist. In meiner neuen Bearbeitung habe ich ein Einfügungsbeispiel hinzugefügt, und selbst wenn Sie versuchen, es kurz und nicht “überflüssig” zu halten, müssen Sie etwas Code eingeben.

    – Codegeist

    17. Oktober 19 um 20:07 Uhr

.

648220cookie-checkWie vermeide ich Codewiederholungen mit vorbereiteten PHP-SQL-Anweisungen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy