Obsługa błędów w PHP podczas korzystania z MVC

12

Ostatnio używam Codeigniter, ale denerwuje mnie obsługa błędów i wyświetlanie ich użytkownikowi. Nigdy nie byłem dobry w obsłudze błędów bez bałaganu. Moją główną troską jest zwracanie błędów użytkownikowi.

Czy dobrą praktyką jest używanie wyjątków i rzucanie / łapanie wyjątków zamiast zwracania 0 lub 1 z funkcji, a następnie używanie if / else do obsługi błędów. Dzięki temu łatwiej jest poinformować użytkownika o problemie.

Mam tendencję do odchodzenia od wyjątków. Mój nauczyciel Java na uniwersytecie kilka lat temu powiedział mi, że „nie należy używać wyjątków w kodzie produkcyjnym, lecz raczej do debugowania”. Mam wrażenie, że kłamał.

Ale na przykład mam kod, który dodaje użytkownika do bazy danych. Podczas procesu więcej niż 1 rzecz może się nie udać, na przykład problem z bazą danych, zduplikowany wpis, problem z serwerem itp. Gdy problem wystąpi podczas rejestracji, użytkownik musi o tym wiedzieć.

Jaki jest najlepszy sposób radzenia sobie z błędami w PHP, biorąc pod uwagę, że używam frameworka MVC.

James Jeffery
źródło

Odpowiedzi:

14

Czy dobrą praktyką jest używanie wyjątków i rzucanie / łapanie wyjątków zamiast zwracania 0 lub 1 z funkcji, a następnie używanie if / else do obsługi błędów. Dzięki temu łatwiej jest poinformować użytkownika o problemie.

Nie nie nie!

Nie mieszaj wyjątków i błędów. Wyjątki są, cóż, wyjątkowe. Błędy nie są. Gdy poprosisz użytkownika o podanie ilości produktu, a użytkownik wpisze „cześć”, oznacza to błąd. To nie jest wyjątek: nie ma nic wyjątkowego w widzeniu nieprawidłowych danych wejściowych od użytkownika. Dlaczego nie możesz używać wyjątków w wyjątkowych przypadkach, na przykład podczas sprawdzania poprawności danych wejściowych? Inne osoby już to wyjaśniły i pokazały prawidłową alternatywę dla sprawdzania poprawności danych wejściowych.

Oznacza to również, że użytkownik nie dba o twoje wyjątki , a pokazanie wyjątków jest zarówno nieprzyjazne, jak i niebezpieczne . Na przykład wyjątek podczas wykonywania zapytania SQL często ujawnia samo zapytanie. Czy na pewno chcesz zaryzykować przedstawienie takiej wiadomości wszystkim?

więcej niż jedna rzecz może się nie udać, na przykład problem z bazą danych, zduplikowany wpis, problem z serwerem itp. Gdy problem wystąpi podczas rejestracji, użytkownik musi o tym wiedzieć.

Źle. Jako użytkownik nie muszę znać problemów z bazą danych, zduplikowanych wpisów itp. Naprawdę nie dbam o twoje problemy. Co mam zrobić, trzeba wiedzieć, że wszedłem nazwę użytkownika, który już istnieje. Jak już powiedziano, nieprawidłowe dane wejściowe ode mnie muszą powodować błąd, a nie wyjątek.

Jak wyprowadzić te błędy? To zależy od kontekstu. W przypadku już używanej nazwy użytkownika chciałbym zobaczyć małą czerwoną flagę pojawiającą się w pobliżu nazwy użytkownika, nawet przed przesłaniem formularza z informacją, że nazwa użytkownika jest już używana. Bez JavaScript ta sama flaga musi pojawić się po przesłaniu.

Przykład błędu z włączoną obsługą AJAX

W przypadku innych błędów wyświetlisz całą stronę z błędem lub wybierz inny sposób poinformowania użytkownika, że ​​coś poszło nie tak (na przykład komunikat, który się pojawi, a następnie zniknie u góry strony). Pytanie dotyczy zatem bardziej wrażeń użytkownika niż programowania.

Z punktu widzenia programistów, w zależności od rodzaju błędu, będziesz go propagował na różne sposoby. Na przykład w przypadku już podanej nazwy użytkownika żądanie AJAX http://example.com/?ajax=1&user-exists=Johnzwróci obiekt JSON wskazujący:

  • Że użytkownik już istnieje,
  • Komunikat o błędzie do wyświetlenia użytkownikowi.

Drugi punkt jest ważny: chcesz mieć pewność, że ten sam komunikat pojawi się zarówno podczas przesyłania formularza z wyłączoną obsługą JavaScript, jak i wpisywania zduplikowanej nazwy użytkownika z włączoną obsługą JavaScript. Nie chcesz powielać tekstu komunikatu o błędzie w kodzie źródłowym po stronie serwera i w JavaScript!

Jest to właściwie technika stosowana przez strony Stack Exhange. Na przykład, jeśli próbuję głosować własną odpowiedź, odpowiedź AJAX zawiera błąd do wyświetlenia:

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

Możesz także wybrać inne podejście i wstępnie ustawić błędy na stronie HTML przed wypełnieniem formularza. Plusy: nie musisz wysyłać komunikatu o błędzie w odpowiedzi AJAX. Minusy: co z dostępnością? Spróbuj przeglądać stronę bez CSS, a zobaczysz wszystkie możliwe błędy.

Arseni Mourzenko
źródło
Doceniam odpowiedź. Właśnie o to walczę. Czy masz jakieś zasoby na temat zgłaszania błędów, szczególnie pod względem komfortu użytkowania?
James Jeffery
Cóż, tak jak powiedziałem, to naprawdę zależy od błędu, a zgłaszanie błędów użytkownikowi jest ściśle powiązane z interfejsem użytkownika. Podkreśliłem także dwa główne sposoby zgłaszania błędów: ścisłą integrację (czerwona flaga z obsługą AJAX przy wejściu z niewłaściwą wartością) i błędy na całej stronie, o wiele mniej przyjazne, stosowane w bardziej poważnych przypadkach. Czy to nie odpowiada na twoje pytanie?
Arseni Mourzenko
2
+1, ponieważ jest to bardziej problem z wrażeniami użytkownika, a nie problem techniczny
Charles Sprayberry
2
Bullshit, MainMa. Poprostu bzdura. Kody błędów to lata 80. i 90. Wyjątki są znacznie bardziej czystym sposobem radzenia sobie ze specjalnymi okolicznościami, takimi jak nieprawidłowe dane wejściowe (na przykład ValidationException). Nie musisz wyświetlać każdemu użytkownikowi wyjątku. Widziałem twoje lepsze odpowiedzi.
Falcon
2
I na wypadek, gdybyś nie wiedział: możesz kontrolować, które wyjątki chcesz przedstawić użytkownikowi, a które nie. Więc to wcale nie jest argument.
Falcon
13

Czy dobrą praktyką jest używanie wyjątków i rzucanie / łapanie wyjątków zamiast zwracania 0 lub 1 z funkcji, a następnie używanie if / else do obsługi błędów. Dzięki temu łatwiej jest poinformować użytkownika o problemie.

Tak tak tak!

Jeśli chcesz mieć czysty kod, powinieneś prawie wyłącznie używać wyjątków i nie zawracać sobie głowy używaniem kodów błędów. Kody błędów są bez znaczenia. Prawie zawsze są powiązane z pewną stałą liczbową, która nie ujawnia wielu informacji. Może sprawić, że Twój kod będzie nieczytelny i utrudni to propagowanie danych obok błędu.

Wyjątkiem są jednak klasy i mogą zawierać dowolne informacje. Więc użytkownik wprowadził błędne dane, takie jak „abc” dla pola liczbowego. Dzięki kodowi błędu nie byłoby możliwe propagowanie tych informacji do modułu obsługi błędu bez dużej ilości propagacji. Coś, co wyjątki przewidują za darmo. Ponadto wyjątki pozwalają uzyskać znaczące wartości zwracane w funkcjach i metodach, a jednocześnie mogą elegancko zawieść. Co więcej, wyjątki są propagowane do miejsca, w którym chcesz je obsłużyć! Wyobraź sobie ilość kodu spaghetti potrzebnego do rozpropagowania kodu błędu z sensownymi danymi do obsługi jedną lub dwiema warstwami powyżej.

Ponadto wyjątki wyrażają znacznie więcej semantycznie niż kody błędów. Kody błędów prowadzą do kodu spaghetti, a obsługa wyjątków prowadzi do czystego kodu.

Co więcej, łatwo zapomnieć o sprawdzeniu kodów stanu. W językach takich jak Java jesteś zmuszony obsługiwać wyjątki (coś, na przykład C # brakuje).

Jaki jest najlepszy sposób radzenia sobie z błędami w PHP, biorąc pod uwagę, że używam frameworka MVC.

Używaj wyjątków i obsługuj je w kontrolerach.

Sokół
źródło
Bardzo się z tobą zgadzam !! Chociaż wyjątki nie są tak egzekwowane w PHP jak w innych językach, warto wiedzieć, że wiele osób je włącza ...
David Conde
6
Zgadzam się, że w większości przypadków kody błędów są dość pozbawione znaczenia. Jednak rzucanie wyjątków nie chcąc jest wyjątkowo złe! Wyjątki należy rezerwować wyłącznie na wyjątkowe okoliczności. Wyjątki powodują nieprzewidywalny przebieg programu, mogą utrudniać śledzenie kodu (a zatem i utrzymywanie), aw PHP niosą ze sobą dość znaczną obniżkę wydajności w porównaniu do IF / THEN / ELSE. Wolę metody zwracania prawdy w przypadku sukcesu, fałszu w przypadku niepowodzenia, i rzucam tylko wyjątek, że coś idzie rażąco źle.
GordonM,
6

Rozważ tę poręczną małą klasę:

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

}

To doskonałe wykorzystanie wyjątków. Z FunkyFile'sperspektywy absolutnie nic nie można zrobić, aby zaradzić sytuacji, gdy ścieżka jest nieprawidłowa lub file_get_contentszawodzi. Naprawdę wyjątkowa sytuacja;)

Ale czy użytkownik ma jakąkolwiek wartość, aby wiedzieć, że natrafiłeś na niewłaściwą ścieżkę pliku, gdzieś w kodzie? Na przykład:

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

Oprócz informowania ludzi, że masz zły dzień, masz następujące opcje:

  1. Fallback

    Czy masz inny sposób na uzyskanie informacji? W moim prostym przykładzie powyżej nie wydaje się to prawdopodobne, ale rozważmy schemat bazy danych master / slave. Mistrz mógł nie zareagować, ale być może niewolnik wciąż tam jest (lub odwrotnie).

  2. Czy to wina użytkownika?

    Czy użytkownik przesłał nieprawidłowe dane wejściowe? Powiedz jej o tym. Możesz albo wyszczekać komunikat o błędzie, albo być miły i dołączyć ten komunikat o błędzie z formularzem, aby mogła wpisać poprawną ścieżkę.

  3. Czy to twoja wina?

    I przez ciebie rozumiem wszystko, co nie jest użytkownikiem, więc waha się od wpisania niewłaściwej ścieżki pliku, po coś nie tak na twoim serwerze. Ściśle mówiąc, nadszedł czas na błąd HTTP 503 , ponieważ usługa jest niedostępna. CI ma show_404()funkcję, którą można łatwo zbudować show_503().

Porada, należy wziąć pod uwagę nieuczciwe wyjątki. CodeIgniter to niechlujny fragment kodu i nigdy nie wiadomo, kiedy pojawi się wyjątek. Podobnie możesz zapomnieć o własnych wyjątkach, a najbezpieczniejszą opcją jest zaimplementowanie procedury obsługi przechwytywania wszystkich wyjątków. W PHP możesz to zrobić za pomocą modułu obsługi wyjątków 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");

Możesz także zająć się nieuczciwymi błędami za pośrednictwem programu set_error_handler . Możesz napisać ten sam moduł obsługi jak dla wyjątków lub alternatywnie przekonwertować wszystkie błędy ErrorExceptioni pozwolić programowi obsługi wyjątków zająć się nimi:

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
źródło
To było naprawdę pouczające, na zdrowie!
James