Coraz częściej na różnych stronach traktujących o programowaniu jak i literaturze można spotkać się z pojęciem framework’a, zatem co to takiego? Ciężko jest zdefiniować jakąś jednolitą definicje, najprostszym i możliwie najdokładniej oddającym istotę frameworka jest stwierdzenie, że jest on “szkieletem”, który posiada kod ułatwiający tworzenie, rozwój i testowanie właściwej aplikacji.
Frameworki nie są specjalną nowością i pisze się je w wielu językach, część z nich jest już bardzo dojrzała i wręcz zalecana do używania. Dobrym przykładem jest tutaj .NET Framework – po co powstał, gdzie i jak jest wykorzystywany? O tym w następnym wpisie.
Czasem zdarza się, że jakiś wyjątek pozostaje nie obsłużony, albo to z braku bloku try{}, bądź też braku pasujących blok catch(){}. Wtedy taki wyjątek zostaje surowo wyświetlony na ekranie. My jednak tego nie chcemy bądź dla zwiększenia bezpieczeństwa(może znajdować się tam jakieś hasło, ect), lub nie chcemy zasypywać użytkownika jakimiś nie zrozumiałymi dla niego błędami(lepiej wyświetlić jakąś prostą stronę z błędem), albo po prostu dla ułatwienia pracy deweloperowi. PHP5 udostępnia nam mechanizm umożliwiający zaopiekowanie się nie chcianymi wyjątkami. Funkcja stworzona do tego nazywa się set_exception_handler(), zademonstruje prosty przykład jak jej użyć.
<?php define( 'DEV', true ); function ExceptionHandler( $Ex ) { if( DEV == true ) { // Można zrobić coś dużo bardziej rozbudowanego :) echo 'Nie kochany wyjątek: '.$Ex->getMessage()."\n"; } else { // Zapis wyjątku do pliku, albo cos die( 'Przepraszamy za usterki!' ); } } set_exception_handler( 'ExceptionHandler' ); throw new Exception( 'UFO :)' ); echo 'Nie wykonany kod, ponieważ trafił nam się nie obłożony wyjątek'; ?>
Jeżeli ustawimy stałej DEV wartość true, będą pojawiać się nie złapane wyjątki – deweloper poprawi i po płaczu.
Jeżeli oddajemy już kod od działania ustawiamy false, wtedy użytkownik nie zobaczy wyjątku (w którym mogły by być jakieś hasła, lubo cokolwiek) tylko zostanie pokazana mu informacja jakaś informacja(przeproszenia, ect), można dopisać logowanie nie obsłużonych wyjątków dzięki temu deweloper będzie mógł obsłużyć wszystkie wyjątki czytając log.
Ponieważ wyjątki są obiektami, możemy tworzyć własne obiekty poprzez dziedziczenie po klasie Exception. Nasuwa się tutaj pytanie, poco tworzyć własne wyjątki? Bardzo ważna jest nazwa klasy, ponieważ to po niej będziemy wyłapywać odpowiednie wyjątki w blokach catch(){}. Nazwa powinna także odzwierciedlać naturę problemu lub gdzie wystąpił.
Stwórzmy sobie własny wyjątek odpowiedzialny za poinformowanie nas, iż wystąpił problem w jądrze naszego systemy. Odpowiednia nazwa dla tego wyjątku będzie SystemException. Uczynimy go odpowiedzialnym za wyrzucanie wszelkich problemów napotkanych w naszej klasie System.
<?php class SystemException extends Exception {} class System { protected $Router = null; public $sPath = './router.php'; public function runRouter() { if( !file_exists( $this->sPath ) ) { throw new SystemException( 'Plik z routerem nie istnieje! Ścieżka “'.$this->sPath.'”' ); } include $sPath; $this->Router = new Router(); } } $System = new System(); try { $System->runRouter(); } catch( SystemException $Ex ) { // Nie udało sie włączyć routera, ponieważ plik nie istnieje $System->sPath = './Istniejacy/Router.php'; $System->runRouter(); } catch( Exception $Ex ) { echo 'Standardowy wyjątek'; } ?>
Należy zwrócić uwaga na kolejność bloków catch(){}, gdyż jak zostanie już znaleziony wyjątek pasujący do danego bloku catch(){}, dalsze boki nie będą już przeszukiwane (podobnie jak to ma miejsce switch(), a catch( Excpetion $Ex ) można traktować jak odpowiednik default).
Czemu jakbym dał na początku blok catch( Exception $Ex ){} to wyjątek został by nie poprawnie obsłużony? Ponieważ każdy wyjątek powinien dziedziczyć po Exception. Ustawiając na samym początku bloki catch(){} poszukujący właśnie tego wyjątku(Exception), będzie zawsze on uznany za ten najbardziej pasujący, co oczywiście nie jest prawda. Dlatego należy pamiętać aby bloki catch(){} określających najmniejszy błąd umieszczać na samej gorze i stopniowo przechodzić w dół, aż w końcu dotrzemy do wyjątku Exception.
Wracając do samego kodu, działa on następująco. Wewnątrz bloku try{} uruchamiamy metodę klasy, która może zwrócić wyjątek. U nas metoda sprawdza czy istnieje plik, tak się akurat dzieje że plik nie istnieje, zostaje zwrócony wyjątek (i zatrzymane wykonywanie metody, tak jak by tam było return). Ponieważ kod był objęty blokiem try{} poszukiwany jest blok catch(){} pasujący do zwróconego wyjątku, tam następuje obsługa wyjątku. W klasie system zostaje zmieniona ścieżka na istniejącą i ponownie zostaje uruchomiona metoda System::runRouter(). Tym razem operacja jest wykonana poprawnie i nie zostaje zwrócony wyjątek (musisz pamiętać tylko aby plik istniał, także nie zdziw się jeżeli zobaczysz komunikat wyjątku jak odpalisz przykład – to nie błąd :-) )
Oczywiście możemy robić także bardziej skomplikowane konstrukcje. Przykładowo w klasie Router może zostać także zostać zgłoszony wyjątek gdy coś będzie powodować problemy.
Napisze prosty przykład.
<?php class RouterException extends Exception{} class Router { public function __construct() { // Prowadzimy jakąś działalność, po czym okazuje się // że coś nie działa throw new RouterException( 'Ojojoj...' ); } } class NewSystem extends System { public function runRounter() { try { parent::runRouter(); } catch( SystemException $SystemEx ) { // Wywalany wyjątek, ponieważ juz mamy jego obsługę niżej throw $SystemEx; } catch( RouterException $RouterEx ) { // Próbujemy rozwiązać problem naszego Router'a // Jeżeli sie nie uda to możemy zdecydować sie na wyrzucenie wyjątku , np: throw new $RouterEx; } } } $System = new System(); try { $System->runRouter(); } catch( SystemException $Ex ) { // Nie udało sie włączyć routera, ponieważ plik nie istnieje $System->sPath = './Istniejacy/Router.php'; $System->runRouter(); } catch( Exception $Ex ) { // Tutaj zostanie obsłużony RouterException, ponieważ u góry nie ma nic pasującego do niego echo 'Wystąpił problem: ',$Ex; } ?>