30 07 2010

Trochę inne spojrzenie na rozszerzenie exception

Wiadomo, mylić się jest rzeczą ludzką; tak więc jako pierwszą z biblioteki underscore “zbuduję” klasę przechwytywania wyjątków. Jako, że jednym z założeń jest jak najmniej klas, a klasy mają praktycznie istnieć samodzielnie, mogę się narazić wielu programistom tym co napiszę.

kategoria: Tworzymy bibliotekę autor: Michał Juszczak

Narażenie będzie spowodowane dołączeniem obsługi loga i błędów w bibliotece.

Zabieramy się do pracy.

Klasa rozszerzająca standardowe Exception. Z poprzedniego artykułu odnośnie ustawień mamy konfigurację:

class _E extends Exception

static private $_classSettings  =   array(
    'errorSave' => 'file|screen', 'errorPath' => '',
    'errorFiletemp'=> '{ -date-}{ -time-}.{ -session-}.error',
    'logEnabled' => true, 'logSave' => 'file|screen',
    'logPath' => '', 'logFiletemp' => '{ -time-}_{ -date-}_{ -session-}',
    'logType' => 'debug', //info, log, debug(all)
    'single' => false, 'output' => 'none', //file|screen
    'exPath' => '', 'exFiletemp' => '{ -time-}_{ -date- }_{ -session- }'
);

Odpowiednio będą to:

errorSave
Wyjście błędów –- file lub screen mogą być oba (może być puste)
errorPath
Gdzie zapisywać (ścieżka do katalogu) błędy
errorFiletemp
W przypadku różnych nazw plików – nazwa tymczasowa, dopuszczalne zmienne to { -date-} - data, { -time-} - czas, { -session-} - nr sesji
logEnabled
Czy logować komunikaty z aplikacji
logSave
Wyjście loga – file lub screen (może być puste)
logType
Rodzaj loga do zapisu. Możliwe wartości info, log, debug. Ostatni wyświetla wszystkie komunikaty
logPath
Ścieżka do katalogu gdzie zapisać pliki loga
logFiletemp
Szablon nazwy pliku loga
single
Czy zapisywać wyjątek do jednego pliku (true) czy do różnych (false)
output
Wyjście wyjątków znowu file lub screen
exPath
Ścieżka do katalogu gdzie zapisać pliki wyjątków
exFiletemp
Szablon nazwy pliku wyjątków

Wartości klasy:

//logi i błędy
static private $_l              =   array();
static private $_e              =   array();
   
//błędy przekazane przez error handler
static private $_et             =   array (
    E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parsing Error',
    E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error',
    E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error',
    E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error',
    E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice',
    E_STRICT => 'Runtime Notice',
    E_RECOVERABLE_ERROR => 'Catchable Fatal Error'
);

Mamy przygotowane podstawy, teraz należy to oprogramować. Każde utworzenie instancji _E będzie równoznaczne z rzutowaniem wyjątku. Konstruktor klasy jest jedynym słusznym rozwiązaniem:


public function __construct($msg = null, $no = 0, Exception $exc = null)
{
    parent::__construct($msg, $no, $exc);
       
    // dane z wyjątku w czytelnej formie, dodajemy datę,
    // info o pliku, linii, backtrace, co kto lubi
    // tej metody nie pokazuje, każdy utworzy swoją własną
    $_view                      =   $this->_createView();

    //zapisz wyjątek do logu błędów i zapisz/wyświetl całość
    self::e('_E', 'Exception: '.$msg);
    if (stripos(self::$_classSettings['output'], 'file')!==false) {
        //zapis do pliku danych, metody nie muszę prezentować
        $this->_file($_view);
    }
    if (stripos(self::$_classSettings['output'], 'screen')!==false) {
        //wyświetlenie wyjątku
        echo $_view;
    }
}
    
// no i jakieś pobranie dodatkowe tych informacji
//
public function get()
{
    return $this->_createView();
}

// a tutaj utworzenie widoku
//
private function _createView()
{
    $_time                      =   explode(" ", microtime());
    $_time                      =   $_time[0] + $_time[1];
    $_head                      =   sprintf(
        "%s \t\t %s \n %s (%s) in file %s (%s)\n",
        date('Y/m/d H:i:s'), $_time, $this->message,
        $this->code, $this->file, $this->line
    );
    return $_head;
}
       

I to wszystko co do wyjątku. W konfiguracji klasy wybieramy czy wyjątek na ekran czy do pliku ma być skierowany. Pozostało jeszcze oprogramować logi i błędy.


//dodanie błędu: klasa z jakiej pochodzi, bład, typ błedu
static public function e($class, $error, $phperror='')
{
    $phperror                   =   ($phperror=='')?'Framework':$phperror;
    list($_usec, $_sec)         =   explode(" ", microtime());
    $_error                     =   date("Y-m-d H:i:s")." ";
    $_error                    .=   sprintf('%2f', ($_usec + $_sec))."\t";
    $_error                    .=   str_pad($phperror, 15)."\t";
    $_error                    .=   str_pad($class." - ".$error, 100)."\n";
    self::$_e[]                 =   $_error;
}

//error handler
static public function err($errno, $errmsg, $filename, $linenum)
{
    // jak kto woli - przekierowac blad
    self::e(
        $filename."[".$linenum."]", $errno." - ".$errmsg, 
        self::$_et[$errno]
    );
    // czy od razu rzutowac blad
    throw new _E($errmsg, $errno);
}
    
// pobranie na ekran, do pliku albo zwrócenie jako wartość
// dowolność oprogramowania
//
static public function eGet($asValue=true)
{
}

static public function l($class, $text, $type='debug')
{
    if (self::$_classSettings['logEnabled']==false) {
        return;
    }
    $type                       =   trim(strtolower($type));
    //check log type
    if ((self::$_classSettings['logType']!=='debug') 
        OR (self::$_classSettings['logType']!=='all')
    ) {
        if ($type!==self::$_classSettings['logType']) {
            return;
        }
    }
    $_date                      =   date("Y-m-d");
    $_hour                      =   date("H:i:s");
    $_time                      =   explode(" ", microtime());
    $_time                      =   $_time[0] + $_time[1];
    self::$_l[]                 =   array(
        'type' => $type, 'time' => $_time, 'date' => $_date,
        'hour' => $_hour, 'class' => $class, 'text' => $text
    );
}

Dlaczego takie rozwiązanie

Rozwiązanie to ma kilka zalet (możliwe, że tylko ja je widzę). Po pierwsze, przechwytywanie wyjątków wykorzystywane będzie w każdej pozostałej klasie; oprócz tego dobrze by było dołączać logowanie błędów i zwykły log. Dzięki jednej klasie zmniejszam ilość wymaganych podpięć plików. Zmniejszona została zajętość zasobów poprzez wspólne wykorzystanie metod prywatnych do zapisu do pliku, formatowania, czy też przetwarzania nazwy pliku.

Utworzenie metod statycznych do logowania nie wymaga tworzenia obiektu, wystarczy wpisać _E::l lub _E::e by działało. Nazwy metod skrócone, by łatwiej je było zapamiętać. W kilku bibliotekach/frameworkach często się zastanawiałem, czy wpisywało się np addError czy też errorAdd. Zaznaczam, tworzę bibliotekę dla siebie, a nie dla Całego Świata. Dzielę się przemyśleniami, gdyż ktoś może je wykorzystać.

Projekt

To jest projekt, szkic klasy. Niedługo po fazie testów będzie do pobrania paczka core klas biblioteki underscore. Zaznaczam, iż biblioteka jest połączeniem własnych rozwiązań z fragmentami rozwiązań podpatrzonymi we frameworkach KohanaCodeIgniter, dodatkowo ostatnio odkryłem Fat-Free Framework i przeglądam jego składnię (może coś wykorzystam).

Dodaj komentarz:

Komentarze podlegają moderacji, nie dopuszczam komentarzy spamujących, z wyzwiskami, wulgaryzmami, oszczerstwami. Rozumiem przez to również nie akceptowanie komentarzy, których treść jest prawnie zabroniona. Pozostałe komentarze, nawet takie które będą sprzeczne z moimi poglądami są akceptowane. Należy czekać na akceptację, każdy komentarz zostanie sprawdzony przeze mnie a następnie ukaże się na stronie.

Komentarze:

Ostatnie wiadomości:

Ostatnie komentarze:

  • (2010-10-26) Nie wiem, że metody statyczne są szybsze i nie wiem, skąd bierzesz tak...
    NEWS Tworzenie biblioteki Underscore: Nie wiem, że metody statyczne są szybsze i nie wiem, skąd bierzesz takie rewelacje, bo odkąd pamiętam, zawsze było na odwrót. Nawet przed chwilą zrobiłem szybki benchmark i nic się nie zmieniło w tym względzie. Ponadto z tego, co piszesz (zwłaszcza o krótkości kodu), wydaje mi się, że nie rozumiesz sensu programowania obiektowego. Robienie metod typu wordCount() to głupota do kwadratu - przecież po to się robi obiekty, by te metody pracowały w ich kontekście i wykonywały coś na nich, a nie działały tak sobie w próżni. O wiele lepsze wyniki wydajnościowe osiągniesz dobrymi algorytmami i odpowiednią architekturą, a nie robieniem sobie pseudoobiektowego śmietnika. Ponadto co z takimi kwestiami, jak niezawodność i elastyczność? Zyx (grrrr, mój nick jest trzyliterowy!)
  • (2010-10-26) Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przera...
    NEWS Konfiguracja klas i obiektów - metoda settings: Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przerabiałem spory kawałek czasu temu przy okazji tworzenia Open Power Libs 2.0 i do dziś odbija mi się to czkawką. Po pierwsze, używasz do tego elementów statycznych, czyli już na wejściu utrudniasz sobie testowanie i wprowadzasz elementy niedeterminizmu, które mogą być źródłem podobnych problemów, jakie powoduje "global". Po drugie, Twoja funkcja do ładowania konfiguracji też jest niepraktyczna, bowiem wymaga uprzedniego załadowania wszystkich możliwych klas nawet, jeśli ich nie używamy, gdyż inaczej oberwiemy wyjątkiem. Dobrym, sprawdzonym rozwiązaniem jest idea wstrzykiwania zależności. PS. Masz u mnie minusa za nałożenie limitu min. 5 znaków na długość nicka, który jest idiotyczny, jako że jest całkiem sporo osób, które mają czteroliterowe nicki, a trzyliterowe takie, jak mój, też się trafiają. Zyx (grrrr, mój nick jest trzyliterowa!!!)

Kategorie:

Tworzenie stron internetowych Tworzymy bibliotekę Zarządzanie jakością Zarządzenie przedsiębiorstwem Blog