try...catch fängt "mysqli_sql_exception" nicht korrekt, aber nur auf bestimmtem Server

  • PHP

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von Marcus Gräfe.

    try...catch fängt "mysqli_sql_exception" nicht korrekt, aber nur auf bestimmtem Server

    Ich verwende PHP 8.0 und habe nachfolgenden Code. In diesem hole ich mir die IDs aus einer Tabelle und lösche anschließend jeden Datensatz einzeln. Sollte dieser in einer anderen Tabelle verwendet werden/verknüpft sein (ich habe mit Constraints, also Fremdschlüsseln gearbeitet), so gibt es eine Fehlermeldung, die ich aber abfange und ignoriere, da ich nur wissen möchte, welche Datensätze tatsächlich verwendet werden (die abgefangene Fehlermeldung lautet "Cannot delete or update a parent row: a foreign key constraint fails (`db`.`machine`, CONSTRAINT `machine_images` FOREIGN KEY (`img_id`) REFERENCES `images` (`id`) ON UPDATE CASCADE)").

    Der Code macht auf meinem lokalen PC mit Wampserver keine Probleme (d. h. es gibt keine Ausgabe einer Fehlermeldung, weil alles abgefangen wird), aber auf einem Ubuntu-System (auf einem vServer) gibt PHP mir die oben genannte Fehlermeldung immer aus. Hat jemand eine Idee, warum das so ist?

    PHP-Quellcode

    1. <?php
    2. try {
    3. $result = $db->query('SELECT id FROM images');
    4. $c = 0;
    5. $stm = $db->prepare('DELETE FROM images WHERE id = ?');
    6. $stm->bind_param('i', $id);
    7. while ($row = $result->fetch_assoc()) {
    8. try {
    9. $id = $row['id'];
    10. $stm->execute();
    11. if ($stm->affected_rows > 0) {
    12. $c++;
    13. }
    14. }
    15. catch (mysqli_sql_exception $e) {
    16. // Fehler beim Löschen: Wenn Fremdschlüsselverletzung, dann wird das Bild verwendet, andere Fehler weitergeben
    17. if ($e->getCode() != 1451) throw $e;
    18. }
    19. }
    20. $stm->close();
    21. echo 'success';
    22. }
    23. catch (Exception $e) {
    24. // Ein Fehler ist aufgetreten
    25. echo $e->getMessage();
    26. }
    27. ?>

    (dieser Code ist natürlich abgespeckt und ergibt so eher weniger Sinn)

    Und ja, der auftretende Fehlercode im inneren catch ist wirklich 1451, trotzdem wird der Fehler weitergegeben.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

    Lokal (Windows 10): PHP 8.0.7, MariaDB 10.5.10
    Online (Ubuntu 20): PHP 8.0.17, MariaDB 10.3.34

    Ich habe übrigens gerade festgestellt, dass im inneren catch, also bei catch (mysqli_sql_exception $e), der Fehler durchaus ankommt (also grundsätzlich wird der Fehler schon abgefangen), aber das 2. catch bekommt den auch (das ist das große Problem). Ich habe im inneren den Code durch das hier ersetzt, um das zu prüfen:

    PHP-Quellcode

    1. error_log($e->getCode());
    2. error_log($e->getMessage());
    3. //if ($e->getCode() != 1451) throw $e;
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    So? if (strval($e->getCode()) != '1451') throw $e;

    Bringt auch nichts. Ich hatte es ja auch schon ganz ohne throw versucht (siehe Post #3), dann wird die Exception aber trotzdem vom äußeren catch abgefangen.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Wie wäre es mit set_exception_handler?

    Ungetestet:

    PHP-Quellcode

    1. function foobar(§ex) {
    2. if ($ex instanceof mysqli_sql_exception) {
    3. echo $ex->getMessage();
    4. die();
    5. } else {
    6. throw $ex;
    7. }
    8. }
    9. set_exception_handler('foobar');


    Edit:
    Ansonsten könntest du dir mal auf beiden Systemen mysqli_driver::$report_mode anschauen/vergleichen.
    Schau mal hier: stackoverflow.com/questions/64…r-catch-in-a-nested-try-c

    Aus Link

    PHP-Quellcode

    1. ​Inside the inner catch, throw() - NOT recommended, I've seen several issues with PHP when doing this. Or set a flag to throw just after the inner catch.
    2. Here's an example throwing the same exception (or you could throw a different one).
    3. try {
    4. $ex = null;
    5. try {
    6. //how can I make the main try catch fail if this try catch fails?
    7. } catch(Exception $e){
    8. $ex = $e;
    9. error_log();
    10. }
    11. if ($ex) {
    12. throw $ex;
    13. }
    14. } catch(Exception $e){
    15. error_log();
    16. }
    NB. Es ist doch schön, wenn man lesbare Namen vergibt. Siehe auch [VB.NET] Beispiele für guten und schlechten Code (Stil).
    @slice Klingt unnötig kompliziert, aber werde ich mal versuchen.
    Und das hier steht vor dem Aufbauen meiner MySQL-Verbindung, somit müsste das auf beiden Systemen identisch sein: mysqli_report(MYSQLI_REPORT_ALL ^ MYSQLI_REPORT_INDEX); // Fehlerbehandlung (alle Fehler melden außer Index-Fehler)

    @INOPIAE Leider bringt das auch nichts, weil auch ohne das throw mein Problem besteht. Innerhalb des 1. catch müsste die Exception abgefangen werden (wird sie ja quasi auch), geht dann aber weiter, auch ohne throw. Ein Auslagern des throw würde also keinen Unterschied machen.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Hi,

    es wäre auch möglich, dass hier der prepare() schon fehlschlägt, die Exception kommt also womöglich gar nicht aus dem "inneren" try-catch.
    Und ja, wenn im inneren catch nicht nochmal ein throw ausgelöst wird, wird die Exception einfach "verschluckt" und nicht an einen äußeren Catch weiter gegeben.

    Ggf. kannst du ja temporär mit SET FOREIGN_KEY_CHECKS=0; workarounden.


    Grüße
    Link :thumbup:
    Hello World
    @Link Ich habe die Zeilen 3–6 (bezogen auf meinen Code aus dem 1. Post) testweise in einen try-catch-Block gepackt, um einen Fehler schon vorher abzufangen. Das ändert nichts. Die Fehlermeldung müsste da aber auch anders sein.

    Dann habe ich nach jeder Zeile ein error_log(__LINE__); gemacht, um den Verlauf der Aufrufe zu sehen. Der Fehler kommt definitiv beim $stm->execute();. Dann geht er das innere catch durch (dort gibt es keine weiteren Fehler) und springt danach zum äußeren catch.

    Die Fremdschlüsselüberprüfung zu deaktivieren ist kein Workaround für mich, denn diese Überprüfung ist ja genau das, worauf ich hier setze.

    Bei dem von @slice vorgeschlagenen set_exception_handler bin ich gerade unsicher, ob mir das überhaupt was bringen würde. In der Hilfe steht nämlich:

    Execution will stop after the callback is called.

    Das darf bei mir nicht sein. Ich möchte den einen bestimmten Fehler ignorieren, nur andere Fehler sollen das Programm stoppen (wobei, selbst das nicht, denn die sollen nur den aktuellen Part überspringen, danach kommen noch weitere Teile, die andere Dinge machen).

    EDIT: Es ist übrigens egal, ob ich im inneren catch eine mysqli_sql_exception oder einfach das Grundobjekt Throwable abfange, macht keinen Unterschied.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

    Hi,

    @Marcus Gräfe ok aber das mit dem Überspringen des aktuellen Parts wird ja bei anderen Fehlern als 1451 nicht funktionieren, denn in dem Fall schmeißt du ja nochmal ne Exception, woraufhin er natürlich komplett aussteigt und der äußere Catch die nächstgelegene Stelle ist, die diese Ausnahme wiederum abfängt.
    Wenn du im inneren Catch kein throw hast, wird die auch nicht an die äußere weiter gegeben. Es sei denn das Objekt der Exception ist inkompatibel zum Catch, was aber 1) nicht der Fall ist da mysqli_sql_exception ein quasi Grandchild von \Exception ist und und du ja 2) bereits korrekt geprüft hast, indem du den inneren Catch auf \Throwable geändert hast, was \Exception ja sowieso implementiert. Wobei du hier testhalber auch einfach mal beide catches auf \Throwable abändern kannst, einfach nur um sonst irgendwelche Merkwürdigkeiten auszuschließen.

    Lass im äußeren Catch mal den Exception trace mit raus.

    Was unwahrscheinlich aber trotzdem möglich ist, ist, dass dein Code sich in einem Namespace befindet und es ebenfalls eine class Exception in diesem Namespace gibt, in diesem Fall würde er die benutzen und nicht die absolute base \Exception mit dem vorangestellten Slash. Und selbst falls das so sein sollte, sehe ich nicht, dass das überhaupt mit diesem speziellen Problem zu tun hat.


    Link :thumbup:
    Hello World

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Link“ ()

    Link schrieb:

    woraufhin er natürlich komplett aussteigt und der äußere Catch die nächstgelegene Stelle ist, die diese Ausnahme wiederum abfängt.

    Genau, er würde bei nicht zu ignorierenden Fehlern den äußeren Catch aufrufen, dann die Fehlermeldung ausgeben (in meinem echten Code in "schön") und dann mit dem nächsten Part weitermachen, der hier nicht gezeigt wird, also quasi in Zeile 29. Das ist schon so beabsichtigt. Da die anderen Parts völlig andere Dinge machen, muss und darf ich das Programm nicht beenden, wenn im 1. Part ein Fehler auftritt. Nur der 1. Part soll dann abgebrochen werden.

    Link schrieb:

    Wenn du im inneren Catch kein throw hast, wird die auch nicht an die äußere weiter gegeben.

    Richtig, so müsste PHP das handhaben, macht(e) es aber augenscheinlich nicht (aber siehe unten!).

    Link schrieb:

    in diesem Fall würde er die benutzen und nicht die absolute base \Exception mit dem vorangestellten Slash.

    Das mit dem Namespace war das allererste, was mir beim Googeln untergekommen ist. Das war's nicht.

    Aber ich habe seit gerade eben die Lösung!


    Die aktuelle Datenbank-Sitzung scheint die Exception noch gespeichert zu haben. Nach Zeile 17 springt er, sofern es Fehler 1451 war, wieder in die While-Schleife (Zeile 7). Und bei fetch_assoc() kommt dann derselbe Fehler wieder. Der wird dann natürlich nur vom äußeren Catch gefangen. fetch_assoc() kann aber unmöglich genau diesen Fehler auslösen. Da wird einfach irgendwas gepuffert. Aber eben nur in MariaDB 10.3, nicht in 10.5, falls es damit zu tun hat.

    Meine konkrete Lösung: $stm->reset(); im inneren Catch. Das genaue Warum würde mich aber schon noch interessieren. Dennoch hake ich den Thread jetzt als Erledigt ab.

    Danke an alle für die Hilfe!
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum