'trigger_error' vs 'throw Exception' en el contexto de los métodos mágicos de PHP

13

Estoy teniendo un debate con un colega sobre el uso correcto (si corresponde) de trigger_error en el contexto de métodos mágicos . En primer lugar, creo que se debe evitar trigger_error excepto para este caso.

Supongamos que tenemos una clase con un método foo()

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

Ahora digamos que queremos proporcionar la misma interfaz pero usar un método mágico para capturar todas las llamadas a métodos

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

Ambas clases son iguales en la forma en que responden a foo() pero difieren cuando se llama a un método no válido.

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

Mi argumento es que los métodos mágicos deben llamar a trigger_error cuando se detecta un método desconocido

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

Para que ambas clases se comporten (casi) de forma idéntica

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

Mi caso de uso es una implementación de ActiveRecord. Uso __call para capturar y manejar métodos que esencialmente hacen lo mismo pero tienen modificadores como Distinct o Ignore , por ejemplo,

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

o

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

Los métodos como where() , from() , groupBy() , etc. están codificados.

Mi argumento se resalta cuando llama accidentalmente a insret() . Si mi implementación de registro activo codificara todos los métodos, sería un error.

Al igual que con cualquier buena abstracción, el usuario debe desconocer los detalles de la implementación y confiar únicamente en la interfaz. ¿Por qué la implementación que utiliza métodos mágicos debería comportarse de manera diferente? Ambos deben ser un error.

    
pregunta chriso 01.04.2011 - 01:57

4 respuestas

7

Tome dos implementaciones de la misma interfaz de ActiveRecord ( select() , where() , etc.)

class ActiveRecord1 {
    //Hardcodes all methods
}

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

Si llama a un método no válido en la primera clase, por ejemplo, ActiveRecord1::insret() , el comportamiento predeterminado de PHP es activar un error . Una llamada de función / método no válida no es una condición que una aplicación razonable querría detectar y manejar. Claro, puede detectarlo en idiomas como Ruby o Python, donde un error es una excepción , pero otros (JavaScript / ¿algún idioma estático / más?) Fallarán.

Volver a PHP: si ambas clases implementan la misma interfaz, ¿por qué no deberían mostrar el mismo comportamiento?

Si __call o __callStatic detectan un método no válido, deberían generar un error para imitar el comportamiento predeterminado del idioma

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

No estoy discutiendo si se deben usar los errores sobre las excepciones (no deberían hacerlo al 100%), sin embargo, creo que los métodos mágicos de PHP son una excepción (juego de palabras :): a esta regla en el contexto de el idioma

    
respondido por el chriso 01.04.2011 - 03:54
3

Voy a lanzar mi opinión, pero si usas trigger_error en cualquier lugar, entonces estás haciendo algo mal. Las excepciones son el camino a seguir.

Ventajas de las excepciones:

  • Se pueden atrapar. Esta es una gran ventaja y debería ser la única que necesitas. Las personas pueden probar algo diferente si creen que existe la posibilidad de que algo salga mal. Los errores no te dan esta oportunidad. Incluso la configuración de un controlador de errores personalizado no contiene una vela para simplemente detectar una excepción. Con respecto a sus comentarios en su pregunta, lo que es una aplicación "razonable" depende completamente del contexto de la aplicación. Las personas no pueden detectar excepciones si creen que nunca sucederá en su caso. No dar a las personas una opción es una cosa mala ™.
  • Rastros de pila. Si algo sale mal, sabes dónde y en en qué contexto se produjo el problema. ¿Alguna vez ha intentado rastrear de dónde provienen los errores de algunos de los métodos principales? Si llama a una función con muy pocos parámetros, obtiene un error inútil que resalta el inicio del método al que está llamando y omite totalmente desde dónde está llamando.
  • Claridad. Combina los dos anteriores y obtendrás un código más claro. Si intenta utilizar un controlador de errores personalizado para manejar los errores (por ejemplo, para generar trazas de la pila de errores), todo el control de errores se realiza en una función, no en el lugar en el que realmente se generan los errores.

Responder a sus inquietudes, llamar a un método que no existe puede ser una posibilidad válida . Esto depende completamente del contexto del código que está escribiendo, pero hay algunos casos en que esto puede suceder. Dirigiéndose a su caso de uso exacto, algunos servidores de bases de datos pueden permitir algunas funciones que otros no. El uso de try / catch y las excepciones en __call() frente a una función para verificar las capacidades es un argumento completamente diferente.

El único caso de uso que se me ocurre al usar trigger_error es para E_USER_WARNING o inferior. Activar un E_USER_ERROR aunque siempre es un error en mi opinión.

    
respondido por el Matthew Scharley 01.04.2011 - 03:13
3

Los errores estándar de PHP deben considerarse obsoletos. PHP proporciona una clase ErrorException incorporada para convertir errores, advertencias y avisos en excepciones con un seguimiento de pila adecuado. Lo usas así:

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 eso, esta pregunta se vuelve discutible. Los errores incorporados ahora generan excepciones, por lo que su propio código también debería hacerlo.

    
respondido por el Wayne 01.04.2011 - 05:55
0

OMI, este es un caso de uso perfectamente válido para 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);

Usando esta estrategia, obtienes el parámetro $errcontext si haces $exception->getTrace() dentro de la función handleException . Esto es muy útil para ciertos propósitos de depuración.

Desafortunadamente, esto solo funciona si usa trigger_error directamente desde su contexto, lo que significa que no puede usar una función / método de envoltura para alias la función trigger_error (por lo que no puede hacer algo como function debug($code, $message) { return trigger_error($message, $code); } Si desea que los datos de contexto en su seguimiento).

He estado buscando una mejor alternativa, pero hasta ahora no he encontrado ninguna.

    
respondido por el John Slegers 24.07.2014 - 17:47

Lea otras preguntas en las etiquetas