Der-Dennis: PDO / MySQL: INSERT- und SELECT- Problem bei serialisierten Obj.

Hallo zusammen,

ich habe ein Problem, bei dem ich nicht weiterkomme. Ich weiß allerdings nicht, ob ich Themenbereich und Betreff richtig gewählt habe, da ich einfach keine Ahnung mehr habe, woran es liegen könnte. Daher bei einer Antwort bitte ändern, wenn es nicht passt.

Zur Ausgangslage: Ein bestendes Projekt soll um ein "Cronjob-Verwaltungs-System" ergänzt werden. Sprich: Es soll nur einen einzigen Cronjob geben, der eine Datei aufruft. Anschließend führt das (PHP-)Skript alle anstehenden Aufgaben aus, wobei eine gegebene Skript-Laufzeit-Begrenzung beachtet wird - es werden also nicht alle, sondern nur so viele Aufgaben wie möglich abgearbeitet.

Zur "Technik": Alle einzelnen, speziellen Aufgaben implementieren ein Interface, welches sie für den Controller ausführbar macht. Dieser Controller wird von dem Cronjob aufgerufen.
Beim erstellen einer neuen Aufgabe wird das Aufgaben-Objekt erstellt, serialisiert und mittels PDO in die Datenbank geschrieben. Dies alles soll eine unbegrenzte Möglichkeit von Aufgaben gewährleisten.

Zum Problem: Es funzt nicht :-) Also:

Mittels folgender Methode werden die Objekte in die Datenbank geschrieben,

  
public function saveSerialized(CronjobModel $cronjob)  
{  
	// Dies folgt in späterer Erläuterung  
	// var_dump($cronjob);  
  
	$db = $this->getDatabase();  
  
	$sql = "INSERT INTO cronjobs(cronjob) VALUES(:cronjob)";  
  
	$query = $db->prepare($sql);  
	$query->bindParam(':cronjob', serialize($cronjob));  
	$query->execute();  
}  

um mittels folgender Methode wieder aus ihr geholt zu werden:

  
public function load()  
{  
 	$db = $this->getDatabase();  
 		  
 	$sql = "SELECT * FROM cronjobs";  
 		  
 	$query = $db->prepare($sql);  
 	$query->execute();  
 		  
 	$queue = array();  
  
 	while ($row = $query->fetchObject()) {  
 		// Dies folgt in späterer Erläuterung  
 		// var_dump(unserialize($row->cronjob));  
  
 		array_push($queue, unserialize($row->cronjob));  
 	}  
 		  
 	return $queue;  
}  

Die Tabelle Cronjobs hat folgenden Aufbau:

  
CREATE TABLE `cronjobs` (  
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  `cronjob` text,  
  PRIMARY KEY (`id`)  
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;  

Das eigentliche Problem ist jetzt, dass die beiden var_dump, die ich oben (auskommentiert) beschrieben habe, unterschiedliche (falsche!) Ergebnisse liefern. Schreibe ich CronjobModel A, B und C in die Datenbank (erster var_dump in der saveSerialized-Methode), erhalte ich im zweiten var_dump (in der load-Methode) CronjobModel A, A und A.

Und das kann ich mir überhaupt nicht erklären. Ich schreibe drei verschiedene, serialisierte Objekte in die Datenbank, erhalte aber dreimal das gleiche, zuerst in die Datenbank eingetragene Objekt.

Ich wäre Euch für jede Anmerkung, jeden Hinweis und jede Erfahrung, wo mein Fehler liegt, sehr dankbar!

Sollte ich etwas vergessen haben zu erwähnen, bitte einfach nachfragen.

Vielen Dank und Gruß, Dennis

  1. Hi,

    Und das kann ich mir überhaupt nicht erklären. Ich schreibe drei verschiedene, serialisierte Objekte in die Datenbank, erhalte aber dreimal das gleiche, zuerst in die Datenbank eingetragene Objekt.

    Ich wäre Euch für jede Anmerkung, jeden Hinweis und jede Erfahrung, wo mein Fehler liegt, sehr dankbar!

            $query->bindParam(':cronjob', serialize($cronjob));  
            $query->execute();
    

    http://www.php.net/manual/en/pdostatement.bindparam.php:
    “Binds a PHP variable to a corresponding named or question mark placeholder in the SQL statement that was use 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.”

    Es ist nicht sinnvoll, das Ergebnis eines Funktionsaufrufes per Referenz binden zu wollen.
    Nutze bindValue.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    1. Hey ChrisB,

      vielen Dank für Deine Antwort

      $query->bindParam(':cronjob', serialize($cronjob));

      $query->execute();

      
      >   
      > <http://www.php.net/manual/en/pdostatement.bindparam.php>:  
      > “Binds a PHP variable to a corresponding named or question mark placeholder in the SQL statement that was use 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.”  
      >   
      > Es ist nicht sinnvoll, das Ergebnis eines Funktionsaufrufes per Referenz binden zu wollen.  
      > Nutze bindValue.  
        
      und vielen Dank für Deinen Hinweis. Das hab ich natürlich mal wieder völlig übersehen. Hab's grad geändert - das Ergebnis ist allerdings leider das gleiche.  
        
      Warum gibt MySQL dabei eigentlich keinen Fehler aus? Wenn ich  
        
      ~~~php
        
      $query->bindParam(':foo', 'bar');  
      
      

      verwende erscheint ja der Fehler: "Fatal error: Cannot pass parameter 2 by reference in ..."

      Das müsste doch das gleiche sein? In beiden Fällen übergebe ich (fälschlicherweise) einen String!?

      Gruß, Dennis

      1. Hi!

        Hab's grad geändert - das Ergebnis ist allerdings leider das gleiche.

        Dein Problem ist nicht nachvollziehbar. Etwas fehlenden Code um deine Auszüge gestrickt, noch drei saveSerialized() ausgeführt, sowie das Ergebnis im DBMS bewundert, erhalte ich eine abc-Ausgabe als Ergebnis von load() - kein aaa.

        Warum gibt MySQL dabei eigentlich keinen Fehler aus?

        Weil das ein PHP-Problem ist. PHP akzeptiert das Funktionsergebnis mit ganz leisem Wehklagen. Man bekommt es nur zu hören, wenn man beim error_reporting zum E_ALL noch E_STRICT hinzunimmt.

        Das müsste doch das gleiche sein? In beiden Fällen übergebe ich (fälschlicherweise) einen String!?

        Jein, einmal ist es ein konstanter Audruck ein anderes Mal ein Funktionsergebnis. Es ist ja auch ein Unterschied, ob man einem Referenzparameter eine Variable oder einen Ausdruck übergibt. Einer Variable kann man einen neuen Wert zuweisen, einem Ausdruck nicht.

        Lo!

        1. Hey dedlfix,

          Hab's grad geändert - das Ergebnis ist allerdings leider das gleiche.

          Dein Problem ist nicht nachvollziehbar. Etwas fehlenden Code um deine Auszüge gestrickt, noch drei saveSerialized() ausgeführt, sowie das Ergebnis im DBMS bewundert, erhalte ich eine abc-Ausgabe als Ergebnis von load() - kein aaa.

          das habe ich auch bereits getestet, allerdings wieder das gleiche Ergebnis - also aaa - erhalten (wobei ich zu meiner Schande gestehen muss, dass ich das noch nicht "stand-alone", sondern nur im Kontext der gesamten Anwendung getestet habe. Das werde ich morgen aber direkt nachholen. Wahrscheinlich war genau das gerade der Wink mit dem Zaunpfahl, dass ich ganz woanders suchen muss und ich viel zu fest daran geglaubt habe, der Fehler müsse an dieser Stelle zu finden sein. Ich melde mich diesbezüglich!)

          Warum gibt MySQL dabei eigentlich keinen Fehler aus?

          Weil das ein PHP-Problem ist. PHP akzeptiert das Funktionsergebnis mit ganz leisem Wehklagen. Man bekommt es nur zu hören, wenn man beim error_reporting zum E_ALL noch E_STRICT hinzunimmt.

          Ich habe error_reporting() auf -1 stehen, erhalte allerdings keinen Fehler!? Woran kann das liegen? Der Wert -1 sollte doch eigentlich alle Fehler anzeigen?
          Ich kann Dir natürlich nur zustimmen, dass im diesem Fall nicht das DBMS, sondern PHP den Fehler ausgeben muss.

          Das müsste doch das gleiche sein? In beiden Fällen übergebe ich (fälschlicherweise) einen String!?

          Jein, einmal ist es ein konstanter Audruck ein anderes Mal ein Funktionsergebnis. Es ist ja auch ein Unterschied, ob man einem Referenzparameter eine Variable oder einen Ausdruck übergibt. Einer Variable kann man einen neuen Wert zuweisen, einem Ausdruck nicht.

          Wieder mal geschrieben ohne nachzudenken... So wie Du es schreibst machts natürlich Sinn!

          Vielen Dank nochmal für Deine Antwort! Ich melde mich, wenn ich das weiter getestet habe und (hoffentlich) eine Erklärung finde.

          Gruß, Dennis

          1. Hi!

            Wahrscheinlich war genau das gerade der Wink mit dem Zaunpfahl, dass ich ganz woanders suchen muss und ich viel zu fest daran geglaubt habe, der Fehler müsse an dieser Stelle zu finden sein.

            Bei hartnäckigen Fehlern hilft oftmals, sie mit dem geringstmöglichen Code nachzubilden. Wenn es wirklich daran lag, kann man sich damit ganz auf das Wesentliche konzentrieren und hat auch gleich eine gute Basis, wenn man den Fehler anderen vorstellen möchte. Andererseits kann sich dann auch zeigen, dass die vermeintliche Stelle ganz in Ordnung ist oder das Problem nur im Zusammenhang mit anderen Codeteilen oder überhaupt dort auftritt.

            Warum gibt MySQL dabei eigentlich keinen Fehler aus?

            Weil das ein PHP-Problem ist. PHP akzeptiert das Funktionsergebnis mit ganz leisem Wehklagen. Man bekommt es nur zu hören, wenn man beim error_reporting zum E_ALL noch E_STRICT hinzunimmt.

            Ich habe error_reporting() auf -1 stehen, erhalte allerdings keinen Fehler!? Woran kann das liegen? Der Wert -1 sollte doch eigentlich alle Fehler anzeigen?

            Ja, display_errors ist auch an? Andere Fehler werden ausgegeben? Keine Syntaxfehler testen, die treten schon beim Parsen und nicht erst zur Laufzeit zu Tage. Schließlich könnten Konfigurationsparameter im Code geändert worden sein. Ein Zugriff auf eine nicht vorhandene Variable ist ein guter Kandidat, um die Meldungsausgabe zu testen.

            Lo!

            1. Hey dedlfix,

              Ich habe error_reporting() auf -1 stehen, erhalte allerdings keinen Fehler!? Woran kann das liegen? Der Wert -1 sollte doch eigentlich alle Fehler anzeigen?

              Ja, display_errors ist auch an? Andere Fehler werden ausgegeben? Keine Syntaxfehler testen, die treten schon beim Parsen und nicht erst zur Laufzeit zu Tage. Schließlich könnten Konfigurationsparameter im Code geändert worden sein. Ein Zugriff auf eine nicht vorhandene Variable ist ein guter Kandidat, um die Meldungsausgabe zu testen.

              display_errors ist an. Beim Aufruf von der nicht vorhandenen Variable $test folgt: Notice: Undefined variable: test in ...

              Bei folgendem Aufruf

                
              class test  
              {  
              	public function nonStatic()  
              	{  
              		return;  
              	}  
              }  
              test::nonStatic();  
              
              

              kommt die Meldung: Strict Standards: Non-static method test::nonStatic() should not be called statically in ...

              Gruß, Dennis

              1. Hi!

                Ich habe error_reporting() auf -1 stehen, erhalte allerdings keinen Fehler!? Woran kann das liegen?
                display_errors ist an. [andere Fehlermeldungen werden angezeigt]

                Dann könnte eigentlich nur noch ein Unterschied in der PHP-Version die Ursache für die Nichtanzeige sein.

                Lo!

                1. Hey dedlfix,

                  Ich habe error_reporting() auf -1 stehen, erhalte allerdings keinen Fehler!? Woran kann das liegen?
                  display_errors ist an. [andere Fehlermeldungen werden angezeigt]

                  Dann könnte eigentlich nur noch ein Unterschied in der PHP-Version die Ursache für die Nichtanzeige sein.

                  bei mir läuft die PHP-Version 5.3.0 und MySQL auf 5.1.37. Was läuft bei Dir? Kann das eventuell noch damit zusammenhängen, dass ich das Attribut

                    
                  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
                  
                  

                  gesetzt habe?

                  Gruß, Dennis

                  1. Hi!

                    Dann könnte eigentlich nur noch ein Unterschied in der PHP-Version die Ursache für die Nichtanzeige sein.
                    bei mir läuft die PHP-Version 5.3.0 und MySQL auf 5.1.37. Was läuft bei Dir?

                    5.3.3. Wenn es da eine Änderung gäbe, müsste da was im Changelog stehen (will ich grad nicht nachsehen). Prüf das aber lieber nochmal mit einem Minimalbeispiel, nicht dass dir wieder alte SVN-Versionen einen Streich spielen.

                    Kann das eventuell noch damit zusammenhängen, dass ich das Attribut
                    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                    gesetzt habe?

                    Nein, das betrifft nur die MySQL-Meldungen, hier geht es aber um einen PHP-Fehler.

                    Lo!

                    1. Hey dedlfix,

                      Prüf das aber lieber nochmal mit einem Minimalbeispiel, nicht dass dir wieder alte SVN-Versionen einen Streich spielen.

                      Du hattest Recht! Mit folgendem Beispiel bekomme ich auch die Meldung "Strict Standards: Only variables should be passed by reference in ..."

                        
                      CREATE TABLE `tabelle` (  
                        `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
                        `wert` text,  
                        PRIMARY KEY (`id`)  
                      ) ENGINE=MyISAM  DEFAULT CHARSET=latin1;  
                      
                      
                        
                      <?php  
                        
                      ini_set('error_reporting', -1);  
                      ini_set('display_errors', 1);  
                        
                      // Datenbank-Zugangsdaten entfernt  
                        
                      $dsn = $type . ':dbname=' . $name . ';host=' . $host;  
                      $pdo = new PDO($dsn, $user, $pass);  
                        
                      class test {}  
                        
                      $objekt = new test();  
                        
                      $sql = "INSERT INTO tabelle (wert) VALUES (:wert)";  
                        
                      $query = $pdo->prepare($sql);  
                      $query->bindParam(':wert', serialize($objekt));  
                      $query->execute();  
                      
                      

                      Dann geht's wohl jetzt weiter an die Fehlersuche und es wird dringend Zeit, dass ich mein SVN-System ändere. Und natürlich, dass ich zuerst kleine Beispiele ausprobiere, bevor ich mich wieder auf einen vermuteten Fehler einschieße :-)

                      Kann das eventuell noch damit zusammenhängen, dass ich das Attribut
                      $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                      gesetzt habe?

                      Nein, das betrifft nur die MySQL-Meldungen, hier geht es aber um einen PHP-Fehler.

                      Das hatten wir ja schon. Sorry! Und vielen Dank für Deine Antwort!

                      Gruß, Dennis

  2. Hallo zusammen,

    ich habe das Problem gelöst und möchte mich ganz herzlich bei allen, die hier mitgelesen haben, bedanken. Ganz besonders natürlich bei ChrisB und dedlfix für die super Hilfe.

    Falls es Euch interessiert, was das Problem war: Ich. Der Depp saß mal wieder vor dem Bildschirm und hat nicht einen, sondern gleich vier Fehler gemacht.

    Fehler 1: Auf dem Server lag eine veraltete Datei. Vor dem hochladen auf den Server hatte ich im SVN mal wieder einen "Update to head" vergessen.

    Fehler 2: In dieser Datei, die ich natürlich nicht beachtet hatte, wurde eine veraltete Methode aufgerufen.

    Fehler 3: In dieser veralteten Methode wurde die Tabelle, in der die einzelnen Aufgaben des Cronjobs stehen, aktualisiert. Und dabei wurde einer der wohl ältesten Fehler beim Umgang mit Datenbanken gemacht: "UPDATE cronjobs SET cronjob = :cronjob". Es wurde also das "WHERE id = :id" vergessen...

    Fehler 4: Der wohl blödeste Fehler: Ich habe Euch meinen Fehler falsch beschrieben. Es gingen nicht die Objekte A, B und C in die Datenbank und A, A und A kamen raus, sondern die Objekte C, C und C. Ich habe einfach nicht aufgepasst, dass die gelieferten Objekte nicht die zuerst-, sondern die zuletzt-eingetragenen waren. Mit diesem Wissen hätte man viel schneller drauf kommen können, dass es ein falsches UPDATE war, da genau das ja das Typische an dem Fehler ist.

    So, nochmals vielen Dank für Eure Hilfe!

    Gruß, Dennis

    1. Hi!

      Fehler 4: Der wohl blödeste Fehler: Ich habe Euch meinen Fehler falsch beschrieben. Es gingen nicht die Objekte A, B und C in die Datenbank und A, A und A kamen raus, sondern die Objekte C, C und C. Ich habe einfach nicht aufgepasst, dass die gelieferten Objekte nicht die zuerst-, sondern die zuletzt-eingetragenen waren. Mit diesem Wissen hätte man viel schneller drauf kommen können, dass es ein falsches UPDATE war, da genau das ja das Typische an dem Fehler ist.

      Ich hätte sicher nicht aufgrund des Abfrageergebnisses auf das fehlerhafte UPDATE getippt, sondern zunächst erst einmal die Ungereimtheit bei einem Kontrollblick auf die Datensätze (mit phpMyAdmin etc.) festgestellt.

      Der SVN-Fehler wäre bei mir so nicht aufgetreten. Das Verzeichnis der Arbeitsversion ist von meinem Entwicklungswebserver aus erreichbar, und da können nicht aus Versehen alte Versionen rumliegen.

      Lo!

      1. Hey dedlfix,

        Ich hätte sicher nicht aufgrund des Abfrageergebnisses auf das fehlerhafte UPDATE getippt, sondern zunächst erst einmal die Ungereimtheit bei einem Kontrollblick auf die Datensätze (mit phpMyAdmin etc.) festgestellt.

        dass dort ein Fehler ist, habe ich ja durch den var_dump erfahren. Nur half es mir nichts, da ich nicht wusste, wo der Fehler liegt.

        In diesem Fall - also im Falle von serialisierten Objekten - ist phpMyAdmin nur bedingt zu gebrauchen, da er die Objekte nicht vollständig anzeigen kann.
        Hierzu ein Beispiel. Vorher, also bei den fehlerhaften Datensätzen, zeigte phpMyAdmin bei mir folgendes an:

        O:16:"CronjobMailModel":10:{s:9:"
        O:16:"CronjobMailModel":10:{s:9:"

        Die richtigen Datensätze sehen im phpMyAdmin so aus:

        O:16:"CronjobMailModel":10:{s:9:"
        O:16:"CronjobMailModel":10:{s:9:"

        Genau das gleiche also.

        Der SVN-Fehler wäre bei mir so nicht aufgetreten. Das Verzeichnis der Arbeitsversion ist von meinem Entwicklungswebserver aus erreichbar, und da können nicht aus Versehen alte Versionen rumliegen.

        Das ist wirklich gut. Das werde ich auch demnächst so umstellen!

        Gruß, Dennis

        1. Hi!

          Ich hätte sicher nicht aufgrund des Abfrageergebnisses auf das fehlerhafte UPDATE getippt, sondern zunächst erst einmal die Ungereimtheit bei einem Kontrollblick auf die Datensätze (mit phpMyAdmin etc.) festgestellt.
          dass dort ein Fehler ist, habe ich ja durch den var_dump erfahren. Nur half es mir nichts, da ich nicht wusste, wo der Fehler liegt.

          Nunja, beim PMA weiß ich, dass er die Daten richtig anzeigt. Bei einer selbst geschriebenen Anzeigeroutine kann sich auch ein Fehler eingeschmuggelt haben. Deshalb verlass ich mich da lieber auf den besser getesteten PMA als auf meine Programmierkünste.

          Apropos, warum nimmst du eigentlich array_push() statt eines kürzer notierten $array[] = ... ?

          In diesem Fall - also im Falle von serialisierten Objekten - ist phpMyAdmin nur bedingt zu gebrauchen, da er die Objekte nicht vollständig anzeigen kann.

          Dann musst du mal im Kopf der Tabelle auf das T mit den beiden Pfeilen nach links und rechts drücken.

          Lo!

          1. Hey dedlfix,

            Nunja, beim PMA weiß ich, dass er die Daten richtig anzeigt. Bei einer selbst geschriebenen Anzeigeroutine kann sich auch ein Fehler eingeschmuggelt haben. Deshalb verlass ich mich da lieber auf den besser getesteten PMA als auf meine Programmierkünste.

            da hast Du schon recht. Nur ist var_dump ja nicht von mir geschrieben ;-)

            Apropos, warum nimmst du eigentlich array_push() statt eines kürzer notierten $array[] = ... ?

            Eine doofe Angewohnheit.

            In diesem Fall - also im Falle von serialisierten Objekten - ist phpMyAdmin nur bedingt zu gebrauchen, da er die Objekte nicht vollständig anzeigen kann.

            Dann musst du mal im Kopf der Tabelle auf das T mit den beiden Pfeilen nach links und rechts drücken.

            So was sehe ich bei mir nicht im PMA. Dafür habe ich aber was ganz herrliches gefunden: Über der Tabelle gibt's nen Link namens "Optionen", wo man sich vollständige Textfelder anzeigen kann... Man, man, man, warum hab ich das nicht vorher schon gesehen? Danke für den Tipp!

            Gruß, Dennis