Der-Dennis: Frage zum Wiki-Artikel „PHP MySQL API“

problematische Seite

Hallo zusammen,

ich schreibe derzeit einen Übersichtsartikel zu den PHP-MySQL APIs (bin aber noch am Anfang). Ein paar Beispiele sind da schon drin. Mir fällt aber gerade auf, dass da besser wohl noch die Fehlerbehandlung mit hinzu genommen werden sollte. Wie würdet ihr das machen, sagen wir beim mysqli-Beispiel?

<?php
    $host = 'example.com';
    $user = 'Benutzername';
    $pass = 'Passwort';
    $name = 'Datenbankname';
 
    $mysqli = mysqli_connect($host, $user, $pass, $name);

    $usergroup = mysqli_real_escape_string($mysqli, $_POST['usergroup']);

    $query = sprintf("SELECT firstname, lastname FROM users WHERE usergroup = '%s'", $usergroup);
    $result = mysqli_query($mysqli, $query);

    while ($row = mysqli_fetch_assoc($result)) {
        printf("%s %s\n", $row['firstname'], $row['lastname']); // Kontextwechsel beachten!
    }
?>

Hintergrund meiner Frage ist, dass das einigermaßen übersichtlich bleiben soll. Die ganzen die()-Funktionen in Tutorials sind aber auch nicht gerade das Gelbe vom Ei. Gibt es da vielleicht irgendein gutes Zwischending? Würde mich über konkrete Vorschläge freuen.

Danke und Gruß
Dennis

  1. problematische Seite

    Tach!

    ich schreibe derzeit einen Übersichtsartikel zu den PHP-MySQL APIs (bin aber noch am Anfang). Ein paar Beispiele sind da schon drin. Mir fällt aber gerade auf, dass da besser wohl noch die Fehlerbehandlung mit hinzu genommen werden sollte. Wie würdet ihr das machen, sagen wir beim mysqli-Beispiel?

    Zumindest andeuten. Wie auf Fehler reagiert werden soll, ist Sache des Verwenders und seines konkreten Anwendungsfalles.

    Hintergrund meiner Frage ist, dass das einigermaßen übersichtlich bleiben soll.

    Eigentlich wäre Exception werfen die übersichtlichste Art. Danach kommt vorzeitiges return. Aber wenn du die Datenbankabfrage nicht in eine Funktion steckst, bleibt wohl nur verschachtelte if-Abfragen zu verwenden.

    Die ganzen die()-Funktionen in Tutorials sind aber auch nicht gerade das Gelbe vom Ei.

    Das sind faulige Eier.

    Vergiss nicht, die Aushandlung der Zeichenkodierung einzubauen.

    dedlfix.

    1. problematische Seite

      Hallo dedlfix,

      danke für die Rückmeldung.

      Mir fällt aber gerade auf, dass da besser wohl noch die Fehlerbehandlung mit hinzu genommen werden sollte.

      Zumindest andeuten. Wie auf Fehler reagiert werden soll, ist Sache des Verwenders und seines konkreten Anwendungsfalles.

      Gut, so hatte ich mir das auch gedacht.

      Hintergrund meiner Frage ist, dass das einigermaßen übersichtlich bleiben soll.

      Eigentlich wäre Exception werfen die übersichtlichste Art. Danach kommt vorzeitiges return.

      Exception werfen fände ich grundsätzlich auch am besten.

      Aber wenn du die Datenbankabfrage nicht in eine Funktion steckst, bleibt wohl nur verschachtelte if-Abfragen zu verwenden.

      Wäre das Auslagern der einzelnen Teile in Funktionen oder sogar eine Klasse für solch einen Übersichtsartikel sinnvoll? Ich finde das allgemein schon, will den Leser (dürften wohl Anfänger sein) aber auch nicht mit zuviel auf einmal erschlagen. Ich weiß auch nicht, was da ein gutes Maß sein könnte. Ich hab diese, ich nenne sie mal „low level“-, Funktionen für DB-Abfragen schon ewig nicht mehr verwendet. Deshalb dauert meine Recherche auch länger als gedacht. Aber da irgendein abgespecktes ORM Framework vorzustellen bzw. nachzubauen kann ja auch nicht Sinn der Sache sein. Schwierig.

      Vergiss nicht, die Aushandlung der Zeichenkodierung einzubauen.

      Hab ich schon auf der Liste stehen. Aber wie macht man das am Geschicktesten? Wenn ich selbst mit sowas arbeite weiß ich, dass ich vorher alles auf UTF-8 setze und dann Ruhe habe. Wie ist das aber bspw., wenn ich im Beispielskript die DB-Kodierung z.B. auf UTF-8 setze, der Benutzer die Datei aber anders gespeichert hat? Oder z.B. PHP auf eine andere Zeichenkodierung eingestellt ist? Kann da sicherheitstechnisch irgendwas schief gehen?
      Wenn ja, gibt es eine Möglichkeit, das kurz und knapp so hinzubiegen, dass es zumindest nicht „gefährlich“ ist? Geht bspw. sowas wie

      $charset = 'utf-8';
      ini_set('default_charset', $charset);
      
      $m = mysqli_connect(/* ... */);
      mysqli_set_charset($m, str_replace('-', '', $charset));
      

      oder

      $m = mysqli_connect(/* ... */);
      mysqli_set_charset($m, str_replace('-', '', ini_get('default_charset')));
      

      ? Und alle anderen Einstellungen sind nicht relevant?

      Tu mich da wie gesagt gerade etwas schwer. Der Artikel dürfte auf jeden Fall deutlich ausführlicher werden als gedacht, aber das muss ja nicht schlimm sein.

      Gruß
      Dennis

      1. problematische Seite

        Tach!

        Wäre das Auslagern der einzelnen Teile in Funktionen oder sogar eine Klasse für solch einen Übersichtsartikel sinnvoll? Ich finde das allgemein schon, will den Leser (dürften wohl Anfänger sein) aber auch nicht mit zuviel auf einmal erschlagen. Ich weiß auch nicht, was da ein gutes Maß sein könnte. Ich hab diese, ich nenne sie mal „low level“-, Funktionen für DB-Abfragen schon ewig nicht mehr verwendet. Deshalb dauert meine Recherche auch länger als gedacht. Aber da irgendein abgespecktes ORM Framework vorzustellen bzw. nachzubauen kann ja auch nicht Sinn der Sache sein. Schwierig.

        Was ist denn das Ziel? Nur vorzustellen, wie man damit grundlegend umgeht? Erstmal einfach und geradeaus für das Verständnis. Dann vielleicht zeigen, wie man es auch/besser machen kann. Vielleicht eine einfache Klasse für das generelle Connection-Handling, und eine Methode, die Query entgegennimmt und Ergebnis zurückgibt. Am Ende noch erwähnen, dass es ORMs gibt. Prepared Statements wären gut, aber die lassen sich nicht sehr anwenderfreundlich mit mysqli verwenden (Binding will echte Variablen-Referenzen, Übergabe als Array geht nur mit Kopfständen). PDO ist da angenehmer.

        Vergiss nicht, die Aushandlung der Zeichenkodierung einzubauen.

        Hab ich schon auf der Liste stehen. Aber wie macht man das am Geschicktesten? Wenn ich selbst mit sowas arbeite weiß ich, dass ich vorher alles auf UTF-8 setze und dann Ruhe habe. Wie ist das aber bspw., wenn ich im Beispielskript die DB-Kodierung z.B. auf UTF-8 setze, der Benutzer die Datei aber anders gespeichert hat?

        Der Anwender muss sowieso die Verbindungsdaten anpassen, da muss die Kodierung ein Parameter davon sein.

        Oder z.B. PHP auf eine andere Zeichenkodierung eingestellt ist?

        Geht doch noch gar nicht.

        Kann da sicherheitstechnisch irgendwas schief gehen?

        Nicht mit den auf ASCII aufbauenden Kodierungen (ISO-8859-* und UTF-8). Irgendwelche asiatischen sind bei den kritischen Zeichen mehrdeutig.

        Wenn ja, gibt es eine Möglichkeit, das kurz und knapp so hinzubiegen, dass es zumindest nicht „gefährlich“ ist? Geht bspw. sowas wie

        Lass das dem Anwender bewusst werden, dass er die Zeichenkodierung beachten muss - auch beim Speichern seiner eigenen Dateien (in Richtung Browser ganz zu schweigen). Ich würde da keinen Automatismus einbauen, der zu irgendwelchen Ergebnissen aufgrund irgendeiner unbeachteten Einstellung führen kann. default_charset sorgt jedenfalls nicht dafür, dass PHP komplett mit der Zeichenkodierung arbeitet, geschweige denn dass Code- und Daten-Dateien und andere Datenquellen diese Kodierung verwenden. Wir haben doch da im Wiki was, darauf kann man für die schmutzigen Details verweisen.

        dedlfix.

        1. problematische Seite

          Prepared Statements wären gut, aber die lassen sich nicht sehr anwenderfreundlich mit mysqli verwenden (Binding will echte Variablen-Referenzen, Übergabe als Array geht nur mit Kopfständen). PDO ist da angenehmer.

          Seit PHP 5.6 gibt es einen spread-Operator, damit wird der call_user_func_array-Hack überflüssig.

          mysqli_stmt_bind_param($stmt, $types, ...$params);
          
          1. problematische Seite

            Tach!

            Prepared Statements wären gut, aber die lassen sich nicht sehr anwenderfreundlich mit mysqli verwenden (Binding will echte Variablen-Referenzen, Übergabe als Array geht nur mit Kopfständen). PDO ist da angenehmer.

            Seit PHP 5.6 gibt es einen spread-Operator, damit wird der call_user_func_array-Hack überflüssig.

            Wenn ich mich recht erinnere, war da noch mehr als nur call_user_func_array() aufzurufen. Die Elemente im Array zählten nicht automatisch als Referenz. Die musste man erstmal händisch zu Referenzen umbiegen. Bekommt der Splat das Kunststück zur Zufriedenheit mysqlis hin?

            dedlfix.

            1. problematische Seite

              Wenn ich mich recht erinnere, war da noch mehr als nur call_user_func_array() aufzurufen. Die Elemente im Array zählten nicht automatisch als Referenz. Die musste man erstmal händisch zu Referenzen umbiegen. Bekommt der Splat das Kunststück zur Zufriedenheit mysqlis hin?

              Oh, ich merke erst jetzt, dass mysqli_stmt_bind_param mit Referenz-Parameter arbeitet. Wieso eigentlich? Verändert die Funktion die Parameter?

              Der spread-Operator scheint das aber richtig zu handhaben:

              function setFoos (&$foo1, &$foo2) {
                 $foo1 = 'Foo!';
                 $foo2 = 'Foo!';
              }
              
              $foos = ['Bar!', 'Baz!'];
              
              setFoos(...$foos);
              
              var_export($foos); // array ( 0 => 'Foo!', 1 => 'Foo!', )
              
              
              1. problematische Seite

                Hallo 1unitedpower,

                Oh, ich merke erst jetzt, dass mysqli_stmt_bind_param mit Referenz-Parameter arbeitet. Wieso eigentlich? Verändert die Funktion die Parameter?

                ist nicht genau das der Unterschied zwischen bind_param() und bind_value()? bind_value() kennt mysqli zwar nicht, aber die Übergabe als Referenz ist glaube ich dafür gedacht, dass das selbe Statement mehrfach hintereinander abgesetzt werden kann. Bei PDO ist das jedenfalls auch so, das scheint sich aber selbst um die Referenz zu kümmern. Kann mich damit aber auch vertun.

                Gruß
                Dennis

                1. problematische Seite

                  Aus dem PDO Manual zu bindParam:

                  „Binds a PHP variable to a corresponding named or question mark placeholder in the SQL statement that was used to prepare the statement. Unlike PDOStatement::bindValue(), the variable is bound as a reference and will only be evaluated at the time that PDOStatement::execute() is called.

                  Most parameters are input parameters, that is, parameters that are used in a read-only fashion to build up the query. Some drivers support the invocation of stored procedures that return data as output parameters, and some also as input/output parameters that both send in data and are updated to receive it.“

                  Scheint also für Rückgabewerte von Stored Procedures gedacht, wenn ich das richtig verstehe.

                  Gruß
                  Dennis

                  1. problematische Seite

                    Tach!

                    Scheint also für Rückgabewerte von Stored Procedures gedacht, wenn ich das richtig verstehe.

                    bind_param ist der Hinweg, bind_result für das Resultset. PDO kennt noch bind_value für feste Werte.

                    dedlfix.

                    1. problematische Seite

                      Hallo dedlfix,

                      Scheint also für Rückgabewerte von Stored Procedures gedacht, wenn ich das richtig verstehe.

                      bind_param ist der Hinweg, bind_result für das Resultset. PDO kennt noch bind_value für feste Werte.

                      ja, so hatte ich das auch immer verstanden. Mir war nur nicht bewusst, dass der eigentliche Anwendungsfall von bind_param ist, Rückgabewerte von Stored Procedures für den nachfolgenden Hinweg (execute) zu verwenden und für alles andere eigentlich bind_value gedacht bzw. ausreichend ist. Zumindest verstehe ich die Dokumentation an der Stelle so. Oder wie ist das gemeint?

                      Gruß
                      Dennis

                      1. problematische Seite

                        Tach!

                        Mir war nur nicht bewusst, dass der eigentliche Anwendungsfall von bind_param ist, Rückgabewerte von Stored Procedures für den nachfolgenden Hinweg (execute) zu verwenden

                        Nö, der Anwendungsfall ist, lokale Variablen zu binden für irgendwelche Statements.

                        1. Prepare
                        2. Variablen Binden
                        3. Werte in die Variable schreiben
                        4. Execute

                        Beispielsweise beim Insert und bei mehreren zu schreibenden Datensätzen würde man 3. und 4. wiederholen.

                        dedlfix.

                        1. problematische Seite

                          Hallo dedlfix,

                          Nö, der Anwendungsfall ist, lokale Variablen zu binden für irgendwelche Statements.

                          1. Prepare
                          2. Variablen Binden
                          3. Werte in die Variable schreiben
                          4. Execute

                          Beispielsweise beim Insert und bei mehreren zu schreibenden Datensätzen würde man 3. und 4. wiederholen.

                          na gut, dann nehme ich das so hin. Kann ich eh besser mit leben, so hatte ich das bisher auch verstanden und dementsprechend verwendet :-) Da scheine ich dann einfach das Manual an der Stelle zu missinterprieren.

                          Gruß
                          Dennis

              2. problematische Seite

                Tach!

                Oh, ich merke erst jetzt, dass mysqli_stmt_bind_param mit Referenz-Parameter arbeitet. Wieso eigentlich? Verändert die Funktion die Parameter?

                Die Funktion bindet nur die Variablen an das Statement. Deren Inhalt wird erst beim Execute ausgelesen. Dazu braucht es die Referenz. (Und wenn das Execute mehrfach laufen soll, muss man zwischendrin die Variableninhalte ändern.)

                Man braucht das ja nur sehr selten. Meist will man einfach nur einen Datensatz schreiben. Trotzdem muss man sich mit dem Gebinde rumärgern. Dass dann auch rückwirkend Referenzen im Array sind (vermutlich jedenfalls), macht die Sache nicht einfacher.

                Der spread-Operator scheint das aber richtig zu handhaben:

                function setFoos (&$foo1, &$foo2) {
                   $foo1 = 'Foo!';
                   $foo2 = 'Foo!';
                }
                
                $foos = ['Bar!', 'Baz!'];
                
                setFoos(...$foos);
                
                var_export($foos); // array ( 0 => 'Foo!', 1 => 'Foo!', )
                

                Mach mal var_dump(), da muss dann auch die Referenz zu sehen sein.

                dedlfix.

                1. problematische Seite

                  Die Funktion bindet nur die Variablen an das Statement. Deren Inhalt wird erst beim Execute ausgelesen. Dazu braucht es die Referenz. (Und wenn das Execute mehrfach laufen soll, muss man zwischendrin die Variableninhalte ändern.)

                  Urgh - ein Kontrollfluss-Spaghetti-Monster.

                  Der spread-Operator scheint das aber richtig zu handhaben:

                  function setFoos (&$foo1, &$foo2) {
                     $foo1 = 'Foo!';
                     $foo2 = 'Foo!';
                  }
                  
                  $foos = ['Bar!', 'Baz!'];
                  
                  setFoos(...$foos);
                  
                  var_export($foos); // array ( 0 => 'Foo!', 1 => 'Foo!', )
                  

                  Mach mal var_dump(), da muss dann auch die Referenz zu sehen sein.

                  An welcher Stelle? Hätte der Spread-Operator die Parameter per Value übergeben, dann hätte das var_export ja array ( 0 => 'Bar!', 1 => 'Baz!', ) ausgespuckt.

                  1. problematische Seite

                    Tach!

                    Mach mal var_dump(), da muss dann auch die Referenz zu sehen sein.

                    An welcher Stelle? Hätte der Spread-Operator die Parameter per Value übergeben, dann hätte das var_export ja array ( 0 => 'Bar!', 1 => 'Baz!', ) ausgespuckt.

                    Also, die bind-Funktion beschwert sich nicht über den Splat-Operator. Damit sind dann wohl auch Statements mit unbekannter Anzahl an Parametern ausführbar.

                    Allerdings, und das ist der Unterschied zu deinem Beispiel, bekomme ich mit var_dump() nach der bind-Funktion eine Ausgabe à la

                    array(2) {
                      [0]=>
                      &int(42)
                      [1]=>
                      &int(23)
                    }
                    

                    Bei deinem Beispiel fehlen die Referenz-&. Was auch immer das bind da anders macht ...

                    dedlfix.

            2. problematische Seite

              Wenn ich mich recht erinnere, war da noch mehr als nur call_user_func_array() aufzurufen. Die Elemente im Array zählten nicht automatisch als Referenz. Die musste man erstmal händisch zu Referenzen umbiegen.

              Kann es sein, dass das noch in einer PHP-Version mit call-time pass-by-reference war? Also als man noch solchen Code schreiben konnte:

              foo(&$bar);
              
              1. problematische Seite

                Tach!

                Wenn ich mich recht erinnere, war da noch mehr als nur call_user_func_array() aufzurufen. Die Elemente im Array zählten nicht automatisch als Referenz. Die musste man erstmal händisch zu Referenzen umbiegen.

                Kann es sein, dass das noch in einer PHP-Version mit call-time pass-by-reference war? Also als man noch solchen Code schreiben konnte:

                foo(&$bar);
                

                Weiß ich nicht, aber so musste man das nicht machen, sondern das Array durchlaufen und die Elemente als Referenzen anlegen. Irgendsowas in der Art:

                for ($i = 0; $i < count($params); $i++) {
                  $params[$i] = & $params[$i];
                }
                array_unshift($params, str_repeat('s', count($params)));
                call_user_func_array(array($stmt, 'bind_param'), $params);
                

                dedlfix.

        2. problematische Seite

          Hallo dedlfix,

          in Ordnung. Dann mache ich erstmal auf dem Level weiter und erweitere das anschließend hintenraus. Kodierung soll dann manuell gesetzt werden. Dann spreche ich die Themen nur kurz und allgemein an und verweise einfach immer wieder auf den Kontextwechselartikel.

          Gruß
          Dennis