`trigger_error` vs` throw Exception` im Kontext der magischen Methoden von PHP

13

Ich habe eine Debatte mit einem Kollegen über den korrekten Gebrauch (falls vorhanden) von trigger_errorim Zusammenhang mit magischen Methoden . Erstens denke ich, dass trigger_errordies bis auf diesen einen Fall vermieden werden sollte .

Angenommen, wir haben eine Klasse mit einer Methode foo()

class A {
    public function foo() {
        echo 'bar';
    }
}

Nehmen wir nun an, wir möchten die exakt gleiche Schnittstelle bereitstellen, verwenden jedoch eine magische Methode, um alle Methodenaufrufe abzufangen

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        }
    }
}

$a = new A;
$b = new B;

$a->foo(); //bar
$b->foo(); //bar

Beide Klassen reagieren auf die gleiche Weise foo(), unterscheiden sich jedoch beim Aufrufen einer ungültigen Methode.

$a->doesntexist(); //Error
$b->doesntexist(); //Does nothing

Mein Argument ist, dass magische Methoden aufgerufen werden sollten, trigger_errorwenn eine unbekannte Methode abgefangen wird

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        default:
            $class = get_class($this);
            $trace = debug_backtrace();
            $file = $trace[0]['file'];
            $line = $trace[0]['line'];
            trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
            break;
        }
    }
}

Damit verhalten sich beide Klassen (fast) identisch

$a->badMethod(); //Call to undefined method A::badMethod() in [..] on line 28
$b->badMethod(); //Call to undefined method B::badMethod() in [..] on line 32

Mein Anwendungsfall ist eine ActiveRecord-Implementierung. Ich benutze __call, um Methoden zu fangen und zu handhaben, die im Wesentlichen dasselbe tun, aber Modifikatoren wie Distinctoder haben Ignore, z

selectDistinct()
selectDistinctColumn($column, ..)
selectAll()
selectOne()
select()

oder

insert()
replace()
insertIgnore()
replaceIgnore()

Methoden wie where(), from(), groupBy()usw. sind hartcodiert.

Mein Argument wird hervorgehoben, wenn Sie versehentlich anrufen insret(). Wenn meine aktive Datensatzimplementierung alle Methoden fest codiert, wäre dies ein Fehler.

Wie bei jeder guten Abstraktion sollte der Benutzer die Implementierungsdetails nicht kennen und sich ausschließlich auf die Schnittstelle verlassen. Warum sollte sich die Implementierung, die magische Methoden verwendet, anders verhalten? Beides sollte ein Fehler sein.

chriso
quelle

Antworten:

7

Nehmen Sie zwei Implementierungen derselben Active Schnittstelle ( select(), where()usw.)

class ActiveRecord1 {
    //Hardcodes all methods
}

class ActiveRecord2 {
    //Uses __call to handle some methods, hardcodes the rest
}

Wenn Sie eine ungültige Methode für die erste Klasse aufrufen ActiveRecord1::insret(), besteht das standardmäßige PHP-Verhalten darin, einen Fehler auszulösen . Ein ungültiger Funktions- / Methodenaufruf ist keine Bedingung, die eine vernünftige Anwendung abfangen und verarbeiten möchte. Sicher, Sie können es in Sprachen wie Ruby oder Python abfangen, in denen ein Fehler eine Ausnahme darstellt, aber andere (JavaScript / irgendeine statische Sprache / mehr?) Schlagen fehl.

Zurück zu PHP - Wenn beide Klassen dasselbe Interface implementieren, warum sollten sie dann nicht dasselbe Verhalten zeigen?

Wenn __calloder __callStaticeine ungültige Methode erkennen, sollten sie einen Fehler zu imitieren das Standardverhalten der Sprache auslösen

$class = get_class($this);
$trace = debug_backtrace();
$file = $trace[0]['file'];
$line = $trace[0]['line'];
trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);

Ich bin nicht der Meinung, ob Fehler über Ausnahmen verwendet werden sollten (sie sollten es nicht zu 100% sein), aber ich glaube, dass die magischen Methoden von PHP eine Ausnahme - Wortspiel beabsichtigt :) - zu dieser Regel im Kontext der Sprache darstellen

chriso
quelle
1
Entschuldigung, aber ich kaufe es nicht. Warum sollten wir Schafe sein, weil es in PHP vor über einem Jahrzehnt keine Ausnahmen gab, als Klassen zum ersten Mal in PHP implementiert wurden 4.something?
Matthew Scharley
@ Matthew für Konsistenz. Ich debattiere keine Ausnahmen vs. Fehler (es gibt keine Debatte) , oder ob PHP hat Design versagt (es tut), ich bin das Argument , dass in diesem einzigartigen Umstand, für Konsistenz , ist es am besten zu imitieren das Verhalten der Sprache
chriso
Konsistenz ist nicht immer gut. Ein konsistentes, aber schlechtes Design ist immer noch ein schlechtes Design, das man am Ende des Tages nur mühsam verwenden kann.
Matthew Scharley
@Matthew stimmt, aber IMO, das einen Fehler bei einem ungültigen Methodenaufruf auslöst, ist kein schlechtes Design. 1) Viele (die meisten?) Sprachen haben es eingebaut fangen einen ungültigen Methodenaufruf und damit umgehen?
chriso
@chriso: Das Universum ist groß genug, dass es Anwendungsfälle gibt, von denen weder Sie noch ich jemals träumen würden, dass sie passieren. Wenn __call()Sie dynamisches Routing verwenden, ist es wirklich so unvernünftig zu erwarten, dass irgendwo auf der Strecke jemand den Fall behandeln möchte, in dem dies fehlschlägt? Auf jeden Fall dreht sich das im Kreis, das wird mein letzter Kommentar sein. Tun Sie, was Sie wollen. Letztendlich kommt es auf ein Urteil an: Bessere Unterstützung versus Beständigkeit. Beide Methoden haben bei nicht spezieller Handhabung den gleichen Effekt auf die Anwendung.
Matthew Scharley
3

Ich werde meine Meinung da draußen äußern, aber wenn Sie trigger_errorirgendwo verwenden, dann machen Sie etwas falsch. Ausnahmen sind der Weg zu gehen.

Vorteile von Ausnahmen:

  • Sie können gefangen werden. Dies ist ein großer Vorteil und sollte der einzige sein, den Sie brauchen. Die Leute können tatsächlich etwas anderes ausprobieren, wenn sie erwarten, dass die Möglichkeit besteht, dass etwas schief geht. Fehler geben Ihnen diese Gelegenheit nicht. Selbst das Einrichten einer benutzerdefinierten Fehlerbehandlungsroutine kann keine Ausnahme auslösen. In Bezug auf Ihre Kommentare in Ihrer Frage, was eine "vernünftige" Bewerbung ist, hängt ganz vom Kontext der Bewerbung ab. Menschen können keine Ausnahmen fangen, wenn sie denken, dass es in ihrem Fall niemals passieren wird. Menschen keine Wahl zu geben, ist eine schlechte Sache.
  • Stapelspuren. Wenn etwas schief geht, wissen Sie, wo und in welchem ​​Kontext das Problem aufgetreten ist. Haben Sie jemals versucht, herauszufinden, woher ein Fehler bei einigen der Kernmethoden stammt? Wenn Sie eine Funktion mit zu wenigen Parametern aufrufen, erhalten Sie einen unbrauchbaren Fehler, der den Start der aufgerufenen Methode hervorhebt und völlig ausblendet, woher sie aufruft.
  • Klarheit. Kombinieren Sie die beiden oben genannten und Sie erhalten einen klareren Code. Wenn Sie versuchen, einen benutzerdefinierten Fehlerhandler zur Behandlung von Fehlern zu verwenden (z. B. um Stack-Traces für Fehler zu generieren), befindet sich Ihre gesamte Fehlerbehandlung in einer Funktion, nicht dort, wo die Fehler tatsächlich generiert werden.

Das Aufrufen einer Methode, die nicht vorhanden ist, kann eine gültige Möglichkeit sein, um Ihre Bedenken auszuräumen . Dies hängt vollständig vom Kontext des Codes ab, den Sie schreiben. In einigen Fällen kann dies jedoch vorkommen. Unter Berücksichtigung Ihres genauen Anwendungsfalls können einige Datenbankserver Funktionen zulassen, die andere nicht zulassen. Die Verwendung von try/ catchund Ausnahmen in __call()vs. einer Funktion zum Überprüfen von Funktionen ist ein völlig anderes Argument.

Der einzige Anwendungsfall, an den ich denken kann, trigger_errorist für E_USER_WARNINGoder niedriger. Ein E_USER_ERRORObwohl auszulösen ist meiner Meinung nach immer ein Fehler.

Matthew Scharley
quelle
Vielen Dank für die Antwort :) - 1) Ich stimme zu, dass Ausnahmen fast immer für Fehler verwendet werden sollten - alle Ihre Argumente sind gültige Punkte. Allerdings .. Ich denke, dass in diesem Zusammenhang Ihre Argumentation fehlschlägt ..
chriso
2
" Eine Methode aufzurufen, die es nicht gibt, kann eine gültige Möglichkeit sein " - da bin ich völlig anderer Meinung. In jeder Sprache (die ich jemals benutzt habe) führt der Aufruf einer Funktion / Methode, die nicht existiert, zu einem Fehler. Es ist keine Bedingung, die gefangen und gehandhabt werden sollte. Statische Sprachen lassen Sie nicht mit einem ungültigen Methodenaufruf kompilieren, und dynamische Sprachen schlagen fehl, sobald sie den Aufruf erreichen.
chriso
Wenn Sie meine zweite Ausgabe lesen, sehen Sie meinen Anwendungsfall. Angenommen, Sie haben zwei Implementierungen derselben Klasse, eine mit __call und eine mit fest codierten Methoden. Warum sollten sich beide Klassen unabhängig von den Implementierungsdetails unterschiedlich verhalten, wenn sie dieselbe Schnittstelle implementieren? PHP löst einen Fehler aus, wenn Sie eine ungültige Methode mit der Klasse aufrufen, die fest codierte Methoden enthält. Die Verwendung trigger_errorvon __call oder __callStatic imitiert das Standardverhalten der Sprache
chriso
@chriso: Dynamische Sprachen, die nicht PHP sind, schlagen mit einer Ausnahme fehl , die abgefangen werden kann . Ruby wirft zum Beispiel eine, NoMethodErrordie Sie fangen können, wenn Sie dies wünschen. Fehler sind meiner Meinung nach ein riesiger Fehler in PHP. Nur weil der Core eine kaputte Methode zum Melden von Fehlern verwendet, sollte Ihr eigener Code dies nicht bedeuten.
Matthew Scharley
@chriso Ich glaube gerne, dass der einzige Grund, warum der Core immer noch Fehler verwendet, die Abwärtskompatibilität ist. Hoffentlich wird PHP6 ein weiterer Sprung vorwärts sein wie PHP5 und Fehler ganz fallen lassen. Letztendlich führen sowohl Fehler als auch Ausnahmen zu demselben Ergebnis: Eine sofortige Beendigung der Codeausführung. Mit einer Ausnahme können Sie jedoch leichter diagnostizieren, warum und wo.
Matthew Scharley
3

Standard-PHP-Fehler sollten als veraltet angesehen werden. PHP bietet eine integrierte Klasse ErrorException zum Konvertieren von Fehlern, Warnungen und Hinweisen in Ausnahmen mit einer vollständigen ordnungsgemäßen Stapelverfolgung. Du benutzt es so:

function errorToExceptionHandler($errNo, $errStr, $errFile, $errLine, $errContext)
{
if (error_reporting() == 0) return;
throw new ErrorException($errStr, 0, $errNo, $errFile, $errLine);
}
set_error_handler('errorToExceptionHandler');

Damit wird diese Frage streitig. Eingebaute Fehler lösen jetzt Ausnahmen aus, und Ihr eigener Code sollte dies auch tun.

Wayne
quelle
1
Wenn Sie dies verwenden, müssen Sie den Fehlertyp überprüfen, damit aus E_NOTICEs keine Ausnahmen werden. Das wäre schlimm.
Matthew Scharley
1
Nein, es ist gut, aus E_NOTICES Ausnahmen zu machen! Alle Hinweise sind als Fehler zu betrachten. Das ist eine gute Übung, ob Sie sie in Ausnahmen konvertieren oder nicht.
Wayne
2
Normalerweise stimme ich Ihnen zu, aber sobald Sie einen Code von Drittanbietern verwenden, fällt dieser schnell auf den ersten Blick auf.
Matthew Scharley
Fast alle Bibliotheken von Drittanbietern sind E_STRICT | E_ALL sicher. Wenn ich Code verwende, der nicht vorhanden ist, gehe ich hinein und repariere ihn. Ich habe jahrelang ohne Probleme so gearbeitet.
Wayne
Du hast Dual offensichtlich noch nie benutzt. Der Kern ist wirklich gut so, aber viele, viele Module von Drittanbietern sind es nicht
Matthew Scharley
0

IMO, dies ist ein perfekter Anwendungsfall für trigger_error:

function handleError($errno, $errstring, $errfile, $errline, $errcontext) {
    if (error_reporting() & $errno) {
        // only process when included in error_reporting
        return handleException(new \Exception($errstring, $errno));
    }
    return true;
}

function handleException($exception){
    // Here, you do whatever you want with the generated
    // exceptions. You can store them in a file or database,
    // output them in a debug section of your page or do
    // pretty much anything else with it, as if it's a
    // normal variable

    switch ($code) {
        case E_ERROR:
        case E_CORE_ERROR:
        case E_USER_ERROR:
            // Make sure script exits here
            exit(1);
        default:
            // Let script continue
            return true;
    }
}

// Set error handler to your custom handler
set_error_handler('handleError');
// Set exception handler to your custom handler
set_exception_handler('handleException');


// ---------------------------------- //

// Generate warning
trigger_error('This went wrong, but we can continue', E_USER_WARNING);

// Generate fatal error :
trigger_error('This went horrible wrong', E_USER_ERROR);

Mit dieser Strategie erhalten Sie die $errcontext Parameter, wenn Sie dies $exception->getTrace()innerhalb der Funktion tun handleException. Dies ist für bestimmte Debugging-Zwecke sehr nützlich.

Leider funktioniert dies nur, wenn Sie trigger_errordirekt aus Ihrem Kontext verwenden. Dies bedeutet, dass Sie keine Wrapper-Funktion / -Methode verwenden können, um die trigger_errorFunktion als Alias ​​zu verwenden (Sie können also nichts tun, function debug($code, $message) { return trigger_error($message, $code); }wenn Sie die Kontextdaten in Ihrem Trace haben möchten).

Ich habe nach einer besseren Alternative gesucht, aber bisher keine gefunden.

John Slegers
quelle