'trigger_error' vs 'lancia Eccezione' nel contesto dei metodi magici di PHP

13

Sto avendo un dibattito con un collega sull'uso corretto (se esiste) di trigger_error nel contesto dei metodi magici . In primo luogo, penso che trigger_error debba essere evitato tranne per questo caso.

Supponiamo di avere una classe con un metodo foo()

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

Ora diciamo che vogliamo fornire la stessa identica interfaccia ma usare un metodo magico per catturare tutte le chiamate di metodo

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

Entrambe le classi sono identiche nel modo in cui rispondono a foo() , ma differiscono quando chiamano un metodo non valido.

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

La mia tesi è che i metodi magici dovrebbero chiamare trigger_error quando viene catturato un metodo sconosciuto

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;
        }
    }
}

Quindi entrambe le classi si comportano (quasi) identicamente

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

Il mio caso d'uso è un'implementazione ActiveRecord. Io uso __call per catturare e gestire metodi che essenzialmente fanno la stessa cosa ma hanno modificatori come Distinct o Ignore , ad es.

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

o

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

Metodi come where() , from() , groupBy() , ecc. sono hardcoded.

Il mio argomento è evidenziato quando si chiama accidentalmente insret() . Se la mia implementazione del record attivo ha codificato tutti i metodi, sarebbe un errore.

Come ogni buona astrazione, l'utente dovrebbe essere inconsapevole dei dettagli di implementazione e affidarsi esclusivamente all'interfaccia. Perché l'implementazione che utilizza i metodi magici dovrebbe comportarsi diversamente? Entrambi dovrebbero essere un errore.

    
posta chriso 01.04.2011 - 01:57
fonte

4 risposte

7

Prendi due implementazioni della stessa interfaccia ActiveRecord ( select() , where() , ecc.)

class ActiveRecord1 {
    //Hardcodes all methods
}

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

Se chiami un metodo non valido sulla prima classe, ad es. ActiveRecord1::insret() , il comportamento PHP predefinito è di attivare un errore . Una chiamata / metodo non valida non è una condizione che un'applicazione ragionevole vorrebbe intercettare e gestire. Certo, puoi prenderlo in linguaggi come Ruby o Python dove un errore è un'eccezione , ma altri (JavaScript / qualsiasi linguaggio statico / altro?) Falliranno.

Ritorno a PHP - se entrambe le classi implementano la stessa interfaccia, perché non dovrebbero mostrare lo stesso comportamento?

Se __call o __callStatic rilevano un metodo non valido, devono generare un errore per simulare il comportamento predefinito della lingua

$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);

Non sto discutendo se gli errori dovrebbero essere usati su eccezioni (non dovrebbero farlo al 100%), tuttavia credo che i metodi magici di PHP siano un'eccezione - gioco di parole voluta :) - a questa regola nel contesto di la lingua

    
risposta data 01.04.2011 - 03:54
fonte
3

Lanciamo la mia opinione supponente là fuori, ma se usi trigger_error ovunque, allora stai facendo qualcosa di sbagliato. Le eccezioni sono la strada da percorrere.

Vantaggi delle eccezioni:

  • Possono essere catturati. Questo è un enorme vantaggio e dovrebbe essere l'unico di cui hai bisogno. Le persone possono effettivamente provare qualcosa di diverso se si aspettano che ci sia una possibilità che qualcosa vada storto. Errore non ti danno questa opportunità. Anche l'impostazione di un gestore di errori personalizzato non regge il confronto con la semplice cattura di un'eccezione. Per quanto riguarda i tuoi commenti nella tua domanda, quale applicazione 'ragionevole' dipende interamente dal contesto dell'applicazione. Le persone non possono prendere eccezioni se pensano che non accadrà mai nel loro caso. Non dare una scelta alle persone è una cosa cattiva ™.
  • Tracce dello stack. Se qualcosa va storto, sai dove e in quale contesto il problema si è verificato. Hai mai provato a rintracciare l'errore che deriva da alcuni dei metodi principali? Se chiami una funzione con troppi parametri, ottieni un errore inutile che evidenzia l'inizio del metodo che stai chiamando e lascia completamente fuori da dove sta chiamando.
  • Chiarezza. Combina i due precedenti e ottieni un codice più chiaro. Se si tenta di utilizzare un gestore di errori personalizzato per gestire gli errori (ad esempio per generare tracce di stack per errori), tutta la gestione degli errori è in una funzione, non dove gli errori vengono effettivamente generati.

Affrontare le tue preoccupazioni, chiamare un metodo che non esiste può essere una possibilità valida . Questo dipende interamente dal contesto del codice che stai scrivendo, ma ci sono alcuni casi in cui ciò può accadere. Affrontando il vostro caso d'uso esatto, alcuni server di database possono consentire alcune funzionalità che altri non lo fanno. L'utilizzo di try / catch e di eccezioni in __call() rispetto a una funzione per verificare le capacità è un argomento completamente diverso.

L'unico caso d'uso che posso pensare per l'utilizzo di trigger_error è per E_USER_WARNING o inferiore. Trigger un E_USER_ERROR sebbene sia sempre un errore secondo me.

    
risposta data 01.04.2011 - 03:13
fonte
3

Gli errori standard di PHP dovrebbero essere considerati obsoleti. PHP fornisce una classe ErrorException integrata per la conversione di errori, avvisi e notifiche in eccezioni con una traccia di stack completa e corretta. Lo usi in questo modo:

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

Usando questo, questa domanda diventa discutibile. Gli errori integrati ora generano eccezioni e quindi anche il tuo codice.

    
risposta data 01.04.2011 - 05:55
fonte
0

IMO, questo è un caso d'uso perfettamente valido per 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);

Utilizzando questa strategia, ottieni il parametro $errcontext se esegui $exception->getTrace() nella funzione handleException . Questo è molto utile per determinati scopi di debug.

Sfortunatamente, funziona solo se utilizzi trigger_error direttamente dal tuo contesto, il che significa che non puoi utilizzare una funzione / metodo wrapper per aliasare la funzione trigger_error (quindi non puoi fare qualcosa come function debug($code, $message) { return trigger_error($message, $code); } se vuoi i dati di contesto nella traccia).

Ho cercato un'alternativa migliore, ma finora non ne ho trovato nessuno.

    
risposta data 24.07.2014 - 17:47
fonte

Leggi altre domande sui tag