Manejo de errores en PHP cuando se usa MVC

12

He estado usando Codeigniter mucho recientemente, pero una cosa que me pone nerviosa es manejar errores y mostrarlos al usuario. Nunca he sido bueno en el manejo de errores sin que se vuelva desordenado. Mi principal preocupación es cuando devuelvo errores al usuario.

¿Es una buena práctica usar excepciones y lanzar / atrapar excepciones en lugar de devolver 0 o 1 de las funciones y luego usar if / else para manejar los errores? Por lo tanto, es más fácil informar al usuario sobre el problema.

Tiendo a alejarme de las excepciones. Mi tutor de Java en la universidad hace unos años me dijo que "las excepciones no deberían usarse en el código de producción, es más para la depuración". Tengo la sensación de que estaba mintiendo.

Pero, un ejemplo, tengo un código que agrega un usuario a una base de datos. Durante el proceso, más de 1 cosa podría salir mal, como un problema de la base de datos, una entrada duplicada, un problema del servidor, etc. Cuando ocurre un problema durante el registro, el usuario debe saberlo.

¿Cuál es la mejor manera de manejar los errores en PHP, teniendo en cuenta que estoy usando un marco MVC?

James Jeffery
fuente

Respuestas:

14

¿Es una buena práctica usar excepciones y lanzar / atrapar excepciones en lugar de devolver 0 o 1 de las funciones y luego usar if / else para manejar los errores? Por lo tanto, es más fácil informar al usuario sobre el problema.

¡No no no!

No mezcle excepciones y errores. Las excepciones son, bueno, excepcionales. Los errores no son. Cuando le pide a un usuario que ingrese una cantidad de un producto, y el usuario ingresa "hola", es un error. No es una excepción: no hay nada excepcional al ver una entrada no válida del usuario. ¿Por qué no puede usar excepciones en casos no excepcionales, como al validar la entrada? Otras personas ya lo explicaron y mostraron una alternativa válida para la validación de entrada.

Esto también significa que al usuario no le importan sus excepciones , y mostrar las excepciones es hostil y peligroso . Por ejemplo, una excepción durante la ejecución de una consulta SQL a menudo revela la consulta en sí. ¿Estás seguro de que quieres correr el riesgo de mostrar ese mensaje a todos?

más de 1 cosa podría salir mal, como un problema de la base de datos, una entrada duplicada, un problema del servidor, etc. Cuando ocurre un problema durante el registro, el usuario debe saberlo.

Incorrecto. Como usuario, no necesito conocer los problemas de su base de datos, entradas duplicadas, etc. Realmente no me importan sus problemas. Lo que necesito saber es que ingresé un nombre de usuario que ya existe. Como ya se dijo, una entrada incorrecta de mi parte debe provocar un error, no una excepción.

¿Cómo generar esos errores? Depende del contexto. Para un nombre de usuario ya usado, me gustaría ver aparecer una pequeña bandera roja cerca del nombre de usuario, incluso antes de enviar el formulario, diciendo que el nombre de usuario ya está usado. Sin JavaScript, la misma bandera debe aparecer después del envío.

Un ejemplo de un error habilitado para AJAX

Para otros errores, mostraría una página completa con un error, o elegiría otra forma de informar al usuario que algo salió mal (por ejemplo, un mensaje que aparecerá y luego se desvanecerá en la parte superior de la página). La pregunta se relaciona más con la experiencia del usuario que con la programación.

Desde el punto de vista de los programadores, dependiendo del tipo de error, lo propagará de diferentes maneras. Por ejemplo, en el caso de un nombre de usuario ya tomado, una solicitud de AJAX http://example.com/?ajax=1&user-exists=Johndevolverá un objeto JSON que indica:

  • Que el usuario ya existe,
  • El mensaje de error para mostrar al usuario.

El segundo punto es importante: desea asegurarse de que el mismo mensaje aparezca tanto al enviar el formulario con JavaScript deshabilitado como al escribir un nombre de usuario duplicado con JavaScript habilitado. ¡No desea duplicar el texto del mensaje de error en el código fuente del lado del servidor y en JavaScript!

Esta es en realidad la técnica utilizada por los sitios web de Stack Exhange. Por ejemplo, si trato de votar mi propia respuesta, la respuesta AJAX contiene el error para mostrar:

{"Success":false,"Warning":false,"NewScore":0,"Message":"You can't vote for your own post.",
"Refresh":false}

También puede elegir otro enfoque y preestablecer los errores en la página HTML antes de que se complete el formulario. Pros: no tiene que enviar el mensaje de error en la respuesta AJAX. Contras: ¿qué pasa con la accesibilidad? Intente navegar por la página sin CSS y verá aparecer todos los posibles errores.

Arseni Mourzenko
fuente
Agradezco la respuesta. Esto es exactamente con lo que estoy luchando. ¿Tiene algún recurso para informar errores, especialmente en términos de experiencia del usuario?
James Jeffery
Bueno, como dije, realmente depende del error, y la notificación de errores al usuario está fuertemente vinculada a la interfaz de usuario. También destaqué dos formas principales de informar errores: integración estrecha (una bandera roja habilitada para AJAX cerca de la entrada con un valor incorrecto) y errores de página completa, mucho menos amigables, utilizados para casos más severos. ¿No responde esto a tu pregunta?
Arseni Mourzenko
2
+1 por ser más un problema de experiencia del usuario y no técnico
Charles Sprayberry
2
Mierda, MainMa. Simplemente mierda. Los códigos de error son tan 80s y 90s. Las excepciones son una forma mucho más limpia de manejar circunstancias especiales como entradas incorrectas (ValidationException por ejemplo). No está obligado a mostrar todas las excepciones al usuario. He visto mejores respuestas tuyas.
Falcon
2
Y en caso de que no lo supiera: puede controlar qué excepciones desea presentar al usuario y cuáles no desea presentar. Así que eso no es realmente una discusión en absoluto.
Falcon
13

¿Es una buena práctica usar excepciones y lanzar / atrapar excepciones en lugar de devolver 0 o 1 de las funciones y luego usar if / else para manejar los errores? Por lo tanto, es más fácil informar al usuario sobre el problema.

¡Si si si!

Si desea tener un código limpio, debe usar casi exclusivamente excepciones y no molestarse en usar códigos de error. Los códigos de error no tienen sentido. Casi siempre están vinculados a alguna constante numérica que no revela mucha información. Puede hacer que su código sea ilegible y dificultará la propagación de datos junto con el error.

Sin embargo, las excepciones son clases y pueden contener cualquier información que desee. Entonces el usuario ingresó una entrada incorrecta, como 'abc' para un campo numérico. Con un código de error, no podría propagar esta información al controlador del error sin muchas burbujas. Algo que las excepciones proporcionan de forma gratuita. Además, las excepciones le permiten tener valores de retorno significativos en funciones y métodos sin dejar de tener una forma elegante de fallar. Aún mejor, ¡las excepciones se propagan directamente al lugar donde desea manejarlas! Imagine la cantidad de código de espagueti que necesitará para propagar un código de error con datos significativos a un controlador de una o dos capas arriba.

Además, las excepciones expresan mucho más semánticamente que los códigos de error. Los códigos de error conducen a un código de espagueti donde el manejo de excepciones conduce a un código limpio.

Además, es fácil olvidarse de verificar los códigos de estado. En lenguajes como Java, se ve obligado a manejar excepciones (algo que, por ejemplo, C # falla).

¿Cuál es la mejor manera de manejar los errores en PHP, teniendo en cuenta que estoy usando un marco MVC?

Use excepciones y manejelas en sus controladores.

Halcón
fuente
Estoy muy de acuerdo contigo !! Aunque las excepciones no se aplican en PHP como en otros idiomas, es bueno saber que muchas personas las están incorporando ...
David Conde
66
Estoy de acuerdo en que, en la mayoría de los casos, los códigos de error carecen de sentido. Sin embargo, ¡lanzar excepciones de todas formas es extremadamente malo! Las excepciones deben reservarse solo para circunstancias excepcionales. Las excepciones causan un flujo de programa impredecible, pueden hacer que el código sea difícil de seguir (y por lo tanto mantener), y en PHP conllevan una penalización de rendimiento bastante significativa en comparación con IF / THEN / ELSE. Tiendo a preferir métodos para devolver verdadero en caso de éxito, falso en caso de fracaso, y solo arrojar una excepción de algo que va a ir muy mal.
GordonM
6

Considere esta práctica pequeña clase:

class FunkyFile {               

    private $path;
    private $contents = null;

    public function __construct($path) { 
        $this->setPath($path); 
    }

    private function setPath($path) {
        if( !is_file($path) || !is_readable($path) ) 
            throw new \InvalidArgumentException("Hm, that's not a valid file!");

        $this->path = realpath($path);
        return $this; 
    }

    public function getContents() {
        if( is_null($this->contents) ) {
            $this->contents = @file_get_contents( $this->path );
            if($this->contents === false) 
                throw new \Exception("Hm, I can't read the file, for some reason!");                                 
        }

        return $this->contents;            
    }

}

Es un uso perfecto de excepciones. Desde la FunkyFile'sperspectiva, no se puede hacer absolutamente nada para remediar la situación si la ruta es inválida o file_get_contentsfalla. Una situación verdaderamente excepcional;)

¿Pero hay algún valor para que su usuario sepa que ha tropezado con una ruta de archivo incorrecta, en algún lugar de su código? Por ejemplo:

class Welcome extends Controller {

    public function index() {

        /**
         * Ah, let's show user this file she asked for
         */                 
        try {
            $file = new File("HelloWorld.txt");
            $contents = $file->getContents();   
            echo $contents;
        } catch(\Exception $e) {
            log($e->getMessage());

            echo "Sorry, I'm having a bad day!"; 
        }                           
    }        
}

Además de decirle a la gente que está teniendo un mal día, sus opciones son:

  1. Retroceder

    ¿Tiene otra forma de obtener la información? En mi sencillo ejemplo anterior, no parece probable, pero considere un esquema de base de datos maestro / esclavo. Es posible que el maestro no haya respondido, pero tal vez, solo tal vez, el esclavo todavía está ahí afuera (o viceversa).

  2. ¿Es culpa del usuario?

    ¿El usuario envió una entrada defectuosa? Bueno, cuéntale sobre eso. Puede ladrar un mensaje de error o ser amable y acompañar ese mensaje de error con un formulario para que pueda escribir la ruta correcta.

  3. ¿Es tu culpa?

    Y por usted, me refiero a todo lo que no sea el usuario, por lo que varía desde que escribe una ruta de archivo incorrecta hasta algo que sale mal en su servidor. Estrictamente hablando, es hora de un error HTTP 503 , ya que, bueno, el servicio no está disponible. CI tiene una show_404()función, puede construir fácilmente show_503().

Un consejo, debe tener en cuenta las excepciones deshonestas. CodeIgniter es un código desordenado, y nunca se sabe cuándo aparecerá una excepción. Del mismo modo, puede olvidarse de sus propias excepciones, y la opción más segura es implementar un controlador de excepciones catch all En PHP puedes hacerlo con set_exception_handler :

function FunkyExceptionHandler($exception) {
    if(ENVIRONMENT == "production") {
        log($e->getMessage());
        show_503();
    } else {
        echo "Uncaught exception: " , $exception->getMessage(), "\n";
    }   
}

set_exception_handler("FunkyExceptionHandler");

Y también puede encargarse de los errores no autorizados , a través de set_error_handler . Puede escribir el mismo controlador que para las excepciones o, alternativamente, convertir todos los errores ErrorExceptiony dejar que su controlador de excepciones se ocupe de ellos:

function FunkyErrorHandler($errno, $errstr, $errfile, $errline) {
    // will be caught by FunkyExceptionHandler if not handled
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

set_error_handler("FunkyErrorHandler");
Yannis
fuente
Esto fue realmente informativo, ¡salud!
James