hawkmaster: PDO Problem mit Prepared Statement, Cannot pass parameter 2 by r

Hallo zusammen,
ich mache gerade meine ersten Versuche mit Prepared Statements.
Ich wie in vielen Beispielen gezeigt folgenden Insert vorbereitet.

$dbInsertdata = $DBO->prepare("INSERT INTO datak (jobID,kName,kValue) VALUES  (:jobid, :kname, :kvalue)");

$dbInsertdata->bindParam(':jobid', $_SESSION['jobID_s']);
$dbInsertdata->bindParam(':kname', "JobLogError");
$dbInsertdata->bindParam(':kvalue', "no");
$dbInsertdata->execute();

Wenn ich nun dies ausführen möchte bekomme ich eine Fehlermeldung:
Fatal error: Cannot pass parameter 2 by reference ...  line 897

gemeint bzw. Zeile 897 ist dies:
$dbInsertdata->bindParam(':kname', "JobLogError");

Ich habe nun schon zigmal alles überprüft und mit den Beispielen verglichen aber ich komme nicht auf die Ursache.

vielen Dank und viele Grüße
hawk

  1. echo $begrüßung;

    $dbInsertdata->bindParam(':kname', "JobLogError");
    Fatal error: Cannot pass parameter 2 by reference ...  line 897

    Ein Ausdruck kann nicht als Referenz übergeben werden, das geht nur mit Variablen. Es ist auch wenig sinnvoll, konstante Werte jedes Mal neu zu übergeben. Schreib diese direkt in das Statement.

    echo "$verabschiedung $name";

    1. Hallo zusammen,
      danke dir für deine Hilfe.

      Ein Ausdruck kann nicht als Referenz übergeben werden, das geht nur mit Variablen. Es ist auch wenig sinnvoll, konstante Werte jedes Mal neu zu übergeben. Schreib diese direkt in das Statement.

      hmm, so ganz kapiert habe ich das nicht was du meinst.
      kannst du mir ein Beispiel zeigen was du mit "direkt in das Statement" meinst?

      Oder sollte ich bei Strings als Value dann anstatt bindParam => bindValue verwenden?

      vielen Dank und viele Grüße
      hawk

      1. echo $begrüßung;

        Ein Ausdruck kann nicht als Referenz übergeben werden, das geht nur mit Variablen. Es ist auch wenig sinnvoll, konstante Werte jedes Mal neu zu übergeben. Schreib diese direkt in das Statement.
        hmm, so ganz kapiert habe ich das nicht was du meinst.

        Zu Referenzen im Allgemeinen gibt es ein Handbuchkapitel: References Explained
        Wenn eine Funktion eine Referenz übergeben haben möchte, wie das beim zweiten Parameter von bindParam() der Fall ist, dann muss eine Variable übergeben werden. PHP übergibt dann nicht den Inhalt der Variable sondern nur einen Verweis darauf.

        kannst du mir ein Beispiel zeigen was du mit "direkt in das Statement" meinst?

        Du hast eine konstante Zeichenkette und keinen variablen Wert. Du kannst diese konstante Zeichenkette direkt notieren und musst sie nicht jedes Mal als Parameter übergeben.

        $dbInsertdata = $DBO->prepare("INSERT INTO datak (jobID,kName,kValue) VALUES  (:jobid, 'JobLogError', 'no')");
          $dbInsertdata->bindParam(':jobid', $_SESSION['jobID_s']);
          $dbInsertdata->execute();

        Oder sollte ich bei Strings als Value dann anstatt bindParam => bindValue verwenden?

        bindValue() wäre eine Möglichkeit, solche konstanten Werte zu übergeben. Aber sinnvoll ist das nicht. Prepared Statements wurden erfunden, um eine Abfrage mehrfach, aber mit wechselnden Parametern aufzurufen. Die Query muss dann nur einmal geparst werden und nicht bei jedem Aufruf erneut. Bei nur einer Ausführung pro Instanz (Scriptaufruf) kann man diesen Vorteil von P.S. nicht ausnutzen.

        Aufgrund dieses vorgesehenen Anwendungszwecks ist es verständlich, wenn man davon ausgeht, nur variable Werte übergeben zu können, und alles was feststeht beim Prepare abzuhandeln. Man kann sogar weitergehen und bei weiteren Ausführungen (in der selben Instanz) nur den angebundenen Variablen neue Inhalte zu geben und anschließend execute() auszuführen (ohne vorher prepare() und bindParam() erneut auszuführen). Das vorbereitete Statement referenziert ja immer noch auf die gleiche Variablen. Das execute() verwendet dann einfach den neuen Inhalt.

        echo "$verabschiedung $name";

        1. Hallo dedlfix,
          vielen herzlichen Dank für deine Erklärung.
          Jetzt ist es mir klar was du meinst und das ist auch natürlich viel praktischer so.

          Noch eine Frage weil es dazu passt.

          In den meisten Manuals steht man sollte "addslashes" verwenden wenn man Datenbankeinträge macht bei denen Daten per Post GET usw. übergeben werden und wo event. Hochkomma etc. vorkommen könnten. Beispiel O'reilly.

          Wenn ich nun solch einen Insert mit Prepared Statements mache, kann ich dann dieses "addslashes" weglassen. Soweit ich mich erinnere, sagtest du mal das das dann auch quotiert wird?

          vielen Dank und viele Grüße
          hawk

          1. Moin!

            In den meisten Manuals steht man sollte "addslashes" verwenden wenn man Datenbankeinträge macht bei denen Daten per Post GET usw. übergeben werden und wo event. Hochkomma etc. vorkommen könnten. Beispiel O'reilly.

            Diese Information ist böse, falsch und gefährlich! Mach das nie!

            addslashes() fügt ohne Berücksichtigung der tatsächlichen Anforderungen der verwendeten Datenbank und deren aktueller Connectionparameter vor allen Anführungszeichen einen Slash ein.

            Dummerweise sind Anführungszeichen nicht unter allen Umständen die einzigen Zeichen, die auf diese Weise escaped werden müssen, und ein Slash ist auch nicht bei allen Datenbanken das korrekte Escape-Zeichen!

            Eigentlich alle Datenbanken bringen in ihrer Client-API eine Funktion zum korrekten Escapen mit. MySQL bietet das noch viel zu unbekannte mysql_real_escape_string().

            Mit Prepared Statements ist diese Diskussion allerdings überflüssig, da das Escaping nur dazu dienen soll, den Datenteil korrekt vom SQL-Programmteil zu trennen, wenn man sich sein SQL-Statement zusammenmischt. Bei Prepared Statements geschieht die Trennung automatisch dadurch, dass im SQL-Teil nur Platzhalter angegeben und die Daten über einen separaten Kanal hinzugefügt werden.

            - Sven Rautenberg

            --
            "Love your nation - respect the others."
            1. Hallo Sven,
              vielen Dank für deine Hilfe und gute erklärung.

              Diese Information ist böse, falsch und gefährlich! Mach das nie!

              Leider findet man sogar im original php.net manual diese Infos so:
              http://nl3.php.net/addslashes
              An example use of addslashes() is when you're entering data into a database. For example, to insert the name O'reilly into a database, you will need to escape it. Most databases do this with a \ which would mean O'reilly. This would only be to get the data into the database, the extra \ will not be inserted. Having the PHP directive  magic_quotes_sybase set to on will mean ' is instead escaped with another '.

              Mit Prepared Statements ist diese Diskussion allerdings überflüssig, da das Escaping nur dazu dienen soll, den Datenteil korrekt vom SQL-Programmteil zu trennen, wenn man sich sein SQL-Statement zusammenmischt. Bei Prepared Statements geschieht die Trennung automatisch dadurch, dass im SQL-Teil nur Platzhalter angegeben und die Daten über einen separaten Kanal hinzugefügt werden.

              Das bedeutet also wenn ich folgendes mache:

              $dbSelectFromUser = $DBO->prepare("SELECT userid FROM usertab WHERE usr = :user");
              $dbSelectFromUser->bindParam(':user', $USER);
              $dbSelectFromUser->execute();

              dann kann nichts passieren?
              Auch wenn $USER eine POST Variable wäre oder aus einem Textfeld kommen würde?

              vielen Dank und viele Grüße
              hawk

              1. echo $begrüßung;

                Leider findet man sogar im original php.net manual diese Infos so:
                http://nl3.php.net/addslashes
                An example use of addslashes() is when you're entering data into a database. For example, to insert the name O'reilly into a database, you will need to escape it. Most databases do this with a \ which would mean O'reilly. This would only be to get the data into the database, the extra \ will not be inserted. Having the PHP directive  magic_quotes_sybase set to on will mean ' is instead escaped with another '.

                Da steht, dass das ein Beispiel für _eine_ Datenbank ist. Es steht nicht dabei, _welches_ System konkret gemeint ist. Es ist leider auch nicht erwähnt, dass jedes DBMS seine eigenen Maskier-Regeln hat.

                Mit Prepared Statements ist diese Diskussion allerdings überflüssig, [...]
                Das bedeutet also wenn ich folgendes mache:
                $dbSelectFromUser = $DBO->prepare("SELECT userid FROM usertab WHERE usr = :user");
                $dbSelectFromUser->bindParam(':user', $USER);
                $dbSelectFromUser->execute();
                dann kann nichts passieren?
                Auch wenn $USER eine POST Variable wäre oder aus einem Textfeld kommen würde?

                Du scheinst es immer noch nicht verstanden zu haben, wozu das Maskieren dient, und warum es bei Prepared Statements nicht benötigt wird. (Genauer gesagt: bei den Parametern, die per bind…() an ein bereits vorbereitetes Statement gebunden werden. Fügt man Werte direkt in das SQL-Statement ein gelten selbstverständlich die DBMS-spezifischen Regeln zur Notation von Strings.)

                Maskieren dient dazu, bestimmte Zeichen mit besonderer Bedeutung so zu kennzeichnen, dass sie in einen bestimmten Kontext, der diesen Zeichen eine besondere Bedeutung zuspricht, ihre besondere Bedeutung verlieren (z.B. String-Begrenzungszeichen). Die gebundenen Daten eines P.S. werden jedoch in einem Kontext transportiert, der keine solchen besonderen Zeichen kennt. Es ist deshalb völlig belanglos, welche Zeichen in den zu transportierenden Daten stehen, weil die Daten nicht nach diesen Zeichen durchsucht werden, um sie beispielsweise an ebenjenen Zeichen aufzutrennen.

                Es ist wie mit rohen Eiern. Transportierst du sie in einer dafür speziell entworfenen Verpackung benötigen sie keine weitere Transportsicherung. Nur wenn du sie in einam allgemeinen Kontext transportierst, solltest du sie gut einpacken.

                echo "$verabschiedung $name";

                1. Hallo dedlfix,
                  danke dir für deine Erklärung.

                  Maskieren dient dazu, bestimmte Zeichen mit besonderer Bedeutung so zu kennzeichnen, dass sie in einen bestimmten Kontext, der diesen Zeichen eine besondere Bedeutung zuspricht, ihre besondere Bedeutung verlieren (z.B. String-Begrenzungszeichen). Die gebundenen Daten eines P.S. werden jedoch in einem Kontext transportiert, der keine solchen besonderen Zeichen kennt. Es ist deshalb völlig belanglos, welche Zeichen in den zu transportierenden Daten stehen, weil die Daten nicht nach diesen Zeichen durchsucht werden, um sie beispielsweise an ebenjenen Zeichen aufzutrennen.

                  vielleicht kann ich einfach nicht ganz deinen Ausführungen folgen?
                  Mir sind manchmal praktische Beispiel lieber.

                  Nur nochmals zum Verständnis:
                  Bleiben wir doch nochmals beim Hochkomma ' das in einem Namen oder ähnlichem vorkommen kann. Wenn man einen "normalen" Insert mit mysql() macht, dann bricht doch der Insert ab, wenn ein solchen Zeichen vorkommt und nicht maskiert oder behandelt ist.
                  Liegt das nun an der MySQL Datenbank ansich oder an der MySQL Funktion?
                  Was ist denn an der P.S. anders? Letztendlich macht es doch auch genau den gleichen Insert oder?

                  vielen Dank und viele Grüße
                  hawk

                  1. Hallo,

                    Bleiben wir doch nochmals beim Hochkomma ' das in einem Namen oder ähnlichem vorkommen kann. Wenn man einen "normalen" Insert mit mysql() macht, dann bricht doch der Insert ab, wenn ein solchen Zeichen vorkommt und nicht maskiert oder behandelt ist.
                    Liegt das nun an der MySQL Datenbank ansich oder an der MySQL Funktion?
                    Was ist denn an der P.S. anders? Letztendlich macht es doch auch genau den gleichen Insert oder?

                    Der Hintergrund ist folgender:

                    Deine Aufgabenstellung ist, eine neue Zeile in eine MySQL-Tabelle einzufügen. PHP bietet Dir dafür 2 Wege:

                    1. Einen SQL-Befehl an die Datenbank zu senden.
                     2. Einen SQL-Befehl mit zusätzlichen Informationen an die Datenbank zu
                        senden.

                    Stell Dir nun vor, Deine Tabelle heißt "tabelle", hat ein Feld "feld", in das Du den Text "hochkomma '" einfügen willst.

                    Betrachten wir zuerst Fall 1. Dort sendest Du eine einzige Zeichenkette (also einen String) an den MySQL-Server. Diese müsste in dem Fall so aussehen:

                    INSERT INTO tabelle (feld) VALUES ('hochkomma \'')

                    Was macht nun der MySQL-Server? Er verarbeitet den String, genauso wie ein Browser HTML verarbeitet oder PHP den Sourcecode Deiner Seite. Beim Verarbeiten dieses Strings erhält er folgende Informationen:

                    * Du willst etwas in eine Tabelle einfügen (INSERT INTO)
                     * Der Name der Tabelle ist "tabelle"
                     * Du willst folgendes Feld füllen: "feld"
                     * Der Wert des Felds ist: "hochkomma '"

                    Wozu also in diesem Fall der ? Weil der MySQL-Server nur einen String als Befehl zu Gesicht bekommt, muss er Wissen, wo *innerhalb* dieses Befehlsstrings denn tatsächlich Strings im SQL-Sinne auftauchen. Diese werden in SQL-Befehlen durch ' begrenzt. Wenn in dem String selbst jetzt ein ' vorkommen soll, muss es (im Falle von MySQL, bei anderen DBs ist das anders) mit \ maskeirt werden, damit MySQL nicht annimmt, dass der String schon zuende ist.

                    Betrachten wir nun Fall 2, mit Prepared Statements: Dort sendest Du *zwei* Zeichenketten an den MySQL-Server. Die eine ist:

                    INSERT INTO tabelle (feld) VALUES (:wert)

                    Die andere ist:

                    hochkomma '

                    Wenn der Server nun den SQL-Befehl verarbeitet, dann erfährt er folgendes:

                    * Du willst etwas in eine Tabelle einfügen (INSERT INTO)
                     * Der Name der Tabelle ist "tabelle"
                     * Du willst folgendes Feld füllen: "feld"
                     * Der Wert des Felds ist der Inhalt des zweiten mitgesendeten Strings

                    Das heißt: Die Verarbeitung des SQL-Befehls kann erfolgen, ohne dass der MySQL-Server auch nur einen *EINZIGEN* SQL-String aus dem Befehl extrahieren muss. Denn der Befehl enthält nur einen Hinweis für den Server "such Dir den String aus einer anderen Quelle" - und diese andere Quelle ist dann eben der zweite String, den Du dem MySQL-Server in Deiner Anfrage mitschickst.

                    Wird das ganze jetzt etwas klarer?

                    Viele Grüße,
                    Christian

                    1. Hallo Christian,

                      recht herzlichen Dank für deine ausführliche und super verständliche Erklärung anhand von Beispielen. Jetzt ist es mir wirklich klarer geworden was da im Hintergrund passiert.

                      vielen Dank und viele Grüße
                      hawk