Autor wpisu: Jacek Skirzyński, dodany: 20.09.2013 21:29, tagi: php
Wyjątki są lepszą alternatywą dla obsługi błędów w aplikacjach od wywoływania trigger_error czy zwracania jakiegoś umówionego kodu w wypadku wystąpienia błędu. W związku z tym w ramach kontynuacji wątku z poprzedniego wpisu dziś kilka słów na temat wyjątków w PHP z nastawieniem na obsługę niewyłapanych przez catch wyjątków.
Obsługa wyjątków
Klasyczne podejście do obsługi wyjątków zakłada użycie throw i try/catch (od PHP 5.5 można również używać finally). Może to wyglądać w taki sposób:
<?php
$id = 1;
// kod wykonany bez wzgledu na wystapienie wyjatku
echo 'punkt 1' . PHP_EOL;
try
{
echo 'punkt 2' . PHP_EOL;
// kod wykonany bez wzgledu na wystapienie wyjatku
if (is_int($id)) {
echo 'punkt 3' . PHP_EOL;
// zrob cos dla podanego ID
} else {
echo 'punkt 4' . PHP_EOL;
throw new Exception('Błędne ID');
}
echo 'punkt 5' . PHP_EOL;
// kod wykonany TYLKO jezeli nie bylo wyjatku
} catch (Exception $ex) {
echo 'punkt 6' . PHP_EOL;
// kod wykonany tylko w razie wystapienia wyjatku
// np. "uratowanie zaistniałej sytuacji", zapis do logu, e-mail do admina
}
echo 'punkt 7' . PHP_EOL;
// kod wykonany bez wzgledu na wystapienie wyjatku
Dla wartości $id = 1; przebieg będzie wyglądał następująco, punkty: 1, 2, 3, 5, 7. Natomiast dla $id nie będącego liczbą całkowitą wykonanie podąży ścieżką: 1, 2, 4, 6, 7.
Powyższy przykład jest mocno uproszczony, ale można zaobserwować pewną rzecz – wyjątki pozwalają na łatwe poinformowanie o jakimś problemie jak również jego obsługę i w dodatku są luźno powiązane. W jednym fragmencie wykonanie kodu jest przerywane poprzez rzucenie wyjątku i nie ma potrzeby zajmowania się jego obsługą. Tym zajmuje się inny fragment kodu, który w bloku try/catch zamyka wywołania mogące sprawiać problemy.
Większego sensu nabiera to w momencie, kiedy w różnych warstwach aplikacji ma miejsce rzucenie wyjątku i jego złapanie. Dodatkowo wyjątki mogą być rzucane również z poziomu bloku catch, na przykład warstwa utrwalania danych łapie wyjątek związany z błędem zapisu do bazy, wykonuje cofnięcie transakcji i ponownie wrzuca wyjątek. Następnie jest on łapany w „wyższej” warstwie aplikacji i w ramach jego obsługi jest pokazywany komunikat dla użytkownika o „problemach technicznych z wykonaniem operacji”. Kolejnym przykładem jest stosowanie w aplikacji bibliotek zewnętrznych – autor biblioteki umieszcza w kodzie tylko rzucanie wyjątków. Natomiast złapaniem i obsługą tych wyjątków zajmuje się programista pracujący nad projektem, w którym dana biblioteka została wykorzystana.
Przechwytywanie nie wyłapanych wyjątków
W ramach ludzkiego błędu, zmiany w kodzie biblioteki lub innego powodu może się pojawić rzucenie wyjątku w miejscu, które nie było na to przygotowane. W efekcie zamiast strony/aplikacji w przeglądarce pojawia się komunikat:
Fatal error: Uncaught exception '[typ_wyjatku]' with message '[komunikat_wyjatku]' in [sciezka_pliku] on line [numer_linii]
Sposobem na takie sytuacje jest utworzenie i zarejestrowanie własnej funkcji obsługującej globalnie przypadki nie złapania rzuconych wyjątków – chodzi o funkcję set_exception_handler. Oprócz wywołania tej funkcji należy wcześniej zdefiniować funkcję, która będzie odpowiadała za faktyczną obsługę wyjątków. Poniżej przykład:
<?php
function obsluzWyjatek($wyjatek) {
echo 'Wyjatek [' . get_class($wyjatek) .']: '. $wyjatek->getMessage() . PHP_EOL;
}
set_exception_handler('obsluzWyjatek');
echo 'punkt 1' . PHP_EOL;
throw new UnexpectedValueException('Podales zla wartosc');
echo 'punkt 2' . PHP_EOL;
Uruchomienie tego kodu spowoduje wyświetlenie:
punkt 1 Wyjatek [UnexpectedValueException]: Podales zla wartosc
Co istotne nie zostanie wykonany żaden kod po rzuceniu wyjątku (tutaj: wypisanie „punkt 2″) – w zwykłym podejściu „pole rażenia” wyjątku (czyli operacje, które nie zostaną wykonane jeżeli wyjątek się pojawi) jest określone blokiem try. natomiast w tym wypadku go nie ma, więc wykonanie całej reszty skryptu zostaje udaremnione. Wyjątkiem jest uruchomienie zdefiniowanej funkcji obsługującej nieprzechwycone wyjątki. Co się w niej znajdzie zależy od programisty, natomiast najważniejszy jest fakt, że można się przed takimi sytuacjami zabezpieczyć i zamiast błędu PHP pokazać użytkownikowi chociażby własną wersję „blue screen”. Można również nieco rozbudować obsługę wyjątków w funkcji w zależności od typu (w przykładzie został użyty wyjątek UnexpectedValueException). W przeciwieństwie do wielu instrukcji catch wyłapujących wyjątki o określonych, coraz bardziej ogólnych typach, funkcja obsługi jest jedna dla wszystkich typów.
Linki:
- wyjątki w PHP
set_exception_handler()– manualrestore_exception_handler()– manual
Autor wpisu: bastard13, dodany: 20.09.2013 13:58, tagi: php, oop
interface PseudoEnum{ const SUCCESS = 1; const FAILURE = 2;}Dobre rozwiązanie? No cóż, osobiście od dawna bolałem nad tym, że takie konstrukcje nie pozwalają nam na jedną z jakże istotnych funkcjonalności, które w przypadku zwykłych obiektów PHP posiada, a które są bezproblemowe w językach, gdzie coś takiego jak enum istnieje. O czym mowa? Chodzi o typowanie. Pomimo cudownych i deskryptywnych nazw, te stałe, jakby na to nie patrzyć, w gruncie rzeczy są int'ami i w chwili, gdy któraś metoda mogła przyjąć wartość tylko i wyłącznie taką, jaka została zadeklarowana wcześniej w naszym pseudo-enumie, to musieliśmy tworzyć do tego celu specjalne metody sprawdzające.Nie mogę powiedzieć, że było to zadowalające rozwiązanie, ale jakoś z tym żyliśmy.Czytaj więcej »
Autor wpisu: Jacek Skirzyński, dodany: 18.09.2013 21:36, tagi: php
Błędy zdarzają się wszędzie i każdemu, natomiast raczej nie jest wskazane „chwalenie się” nimi przed użytkownikami aplikacji.
Konfiguracja
Podstawowyą kwestią jest ich ukrycie na środowisku produkcyjnym przed oczami użytkownika, tym bardziej że w stosie wywołań mogą się pojawić wrażliwe dane użytkownika/aplikacji/systemu/inne. Do ukrycia „wpadki” używa się właściwości display_errors (php.ini). Właściwość pozwala włączyć lub wyłączyć pokazywanie błędów oraz skierować je na odpowiednie wyjście:
Off– wyłączenie pokazywania błędów (do użycia na „produkcji”)stderr– przekierowanie błędów naSTDERR(działa tylko z CGI/CLI)On/stdout– pokazanie błędów/przekierownie na standardowe wyjście, czyli pokazanie komunikatów w treści strony wyświetlonej użytkownikowi (przydatne w trakcie programowania)
Jednak ukrycie komunikatów błędów w żaden sposób nie rozwiązuje faktycznego problemu w aplikacji. Do tego potrzebne są komunikaty błędów i to tych znaczących. Do filtrowania komunikatów służy dyrektywa error_reporting. Jest to poziom raportowania błędów, które będą wyświetlane/pokazywane – możliwych wartości jest cała masa, a dodatkowo można je łączyć – lista stałych.
Obie dyrektywy (w zależności od uprawnień czy konfiguracji środowiska) mogą zostać ustawione na poziomie pliku php.ini, definicji vhosta, pliku .htaccess, czy w samym kodzie PHP. Ostatni wariant wygląda następująco:
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
var_dump(ini_get('display_errors')); // pokaże: string '1' (length=1)
var_dump(ini_get('error_reporting')); // pokaże: string '22519' (length=5)
W PHP wartości dyrektyw można zmieniać i odczytywać funkcjami ini_set() / ini_get(). Dla dyrektywy error_reporting jest dedykowana funkcja – wywołana z argumentem zmienia wartość, natomiast bez argumentu zwraca obecną wartość, nie zmienia to jednak faktu, że można używać standardowych funkcji. Oczywiście nie wszystkie dyrektywy można zmieniać w powyższy sposób – tutaj znajduje się lista dyrektyw z oznaczeniem sposobu zmiany wartości i opis poziomów.
Wywołanie błędu
<?php
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;
Do wywołania błędu z poziomu kodu służy funkcja trigger_error() – pierwszy argument to własny komunikat, natomiast drugi to typ błędu. W zależności od tego jak bardzo błąd jest poważny wykonywanie skryptu jest kontynuowane lub przerywane, co widać poniżej w wyniku wykonania powyższego kodu:
punkt 1
Notice: Test wywolania bledu in /home/cim/public_html/Testy/error.php on line 12
Call Stack
# Time Memory Function Location
1 0.0001 230360 {main}( ) ../error.php:0
2 0.0002 231520 trigger_error ( ) ../error.php:12
punkt 2
Fatal error: Test wywolania bledu in /home/cim/public_html/Testy/error.php on line 14
Call Stack
# Time Memory Function Location
1 0.0001 230360 {main}( ) ../error.php:0
2 0.0003 231520 trigger_error ( ) ../error.php:14
Po wystąpieniu błedu typu „Notice” wykonywanie przebiega dalej (np. pokazanie „punkt 2″), natomiast błąd typu „Fatal error” przerywa wykonanie (brak wyświetlenia „punkt 3″).
Przechwytywanie
Błąd został zgłoszony i przy odpowiedniej konfiguracji został wyświetlony na ekranie. Docelowo jednak efekt ma być zupełnie inny, błędy powinny być zgłaszane do odpowiednich osób. PHP posiada przydatną funkcję – error_log – która wspomaga wysyłanie e-maili, dodawnie komuniaktów błędów do systemowego logu, tworzenie własnego logu. Jednak można również samemu wysyłać e-maile (poprzez serwer SMTP) czy zapisywać do pliku.
W obu przypadkach należy w jakiś sposób przechwycić informację o wystąpieniu błędu. Realizuje się to poprzez utworzenie własnej funkcji i zarejestrowanie jej za pomocą set_error_handler().
<?php
define('LOG_SEPARATOR', ';');
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
function obsluga($kodBledu, $komunikat, $plik, $linia, $zmienne) {
$elementy = array(
date('H:i:s'),
$kodBledu,
$komunikat,
$plik,
$linia,
//print_r($zmienne, true)
);
error_log(implode(LOG_SEPARATOR, $elementy) . PHP_EOL, 3, 'logi_'. date('Ymd') .'.log');
}
set_error_handler('obsluga');
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania notice', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;
Powyższy kod spowoduje, że na ekranie zostanie wyświetlone tylko: „punkt 1, punkt 2, punkt 3″. Natomiast zarejestrowana funkcja przechwytująca informację o błędzie obsłuży w sposób „cichy” testowe wywołania błędów. Do funkcji przekazywane są takie informacje jak: poziom błędu, komunikat oraz plik i linia, w których błąd został znaleziony/zgłoszony i zmienne towarzyszące kontekstowi wywołania.
W tym przypadku dodatkowo dane zostały zapisane do pliku (generowanego osobno dla każdego dnia) z logami, gdzie poszczególne dane (oddzielone średnikami) zawarte są w jednym wierszu razem z godziną wystąpienia problemu. W 13 wierszu znajduje się przekazanie do pliku z logami zrzutu zmiennych, które w tej chwili jest nie aktywne, ponieważ zaburzałoby jednowierszowy podział dla zdarzeń. Jednak warto zapisywać również te dane, ponieważ pomagają w odnalezieniu źródła problemu.
Przy rozwiązywaniu problemu lub diagnozie błędu, który występuje w bardzo specyficznych okolicznościach przydatna jest funkcja debug_backtrace(). Zwraca ona tablicę ze stosem wywołań od uruchomienia skryptu do wywołania jej samej – dla każdego wywołania podane są szczegółowe informacje pozwalające prześledzić kolejne kroki wykonania skryptu.
Zamiana na wyjątki
Ostatnią kwestią, którą poruszę jest zamiana wystąpień klasycznych ostrzeżeń/błędów na wyjątki. W PHP istnieje specjalnie zdefiniowany w tym celu typ wyjątku – ErrorException. Sposób użycia został pokazany poniżej poprzez modyfikację poprzedniego przykładu.
<?php
define('LOG_SEPARATOR', ';');
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
function obsluga($kodBledu, $komunikat, $plik, $linia) {
throw new ErrorException($komunikat, $kodBledu, 0, $plik, $linia);
}
set_error_handler('obsluga');
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania notice', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;
Natomiast efekt uruchomienia będzie się prezentował następująco (tylko początek):
punkt 1 Fatal error: Uncaught exception 'ErrorException' with message 'Test wywolania notice' in ...
Funkcja przechwytująca błędy przekazała swoje parametry do wywołania wyjątku ErrorException, wyjątek został rzucony… i niestety zakończyło to się błędem, ponieważ wyjątek nie został złapany. Żeby temu zaradzić należałoby każde miejsce, w którym błąd jest zgłaszany ręcznie zamknąć w bloku try/catch i/lub zdefiniować globalną funkcję obsługującą nieprzechwycone wyjątki. To jest jednak temat na jeden z następnych wpisów.
Klasyczne błędy generowane przez interpreter lub zgłąszane ręcznie pozwalają na zakomunikowanie programiście, że coś jest nie tak i pomagają w rozwiązaniu problemu. Jednak spełniają rolę tylko informacyjną – zapisanie do logu, wysłanie powiadomienia na e-mail czy pokazanie użytkownikowi strony z informacją o błędzie aplikacji. Natomiast w oparciu o mechanizm obsługi błędów nie ma możliwości reagowania na konkretne i przewidywalne problemy (jeżeli pojawia się wywołanie trigger_error() to programista przewidział ewentualny problem). Rozwiązaniem jest wykorzystywanie wyjątków, które mogą być przechwytywane globalnie (tak jak błędy) oraz lokalnie dając programiście szanę na obsługę konkretnego zaistniałego problemu (a nie tylko w oparciu o poziom błędu).
Autor wpisu: Łukasz Socha, dodany: 08.09.2013 14:23, tagi: css
W dobie coraz większej ilości wszelakich urządzeń mobilnych tworzenie stron dostosowanych pod różne rozdzielczości jest już praktycznie standardem. Dziś opiszę jedną z technik wykorzystywanych w RWD (Responsive web design), a jest nią Fluid layout.
Krótko mówiąc, Fluid layout polega na tworzeniu „płynnych” układów stron. Możemy zrobić tak, że lewa kolumna (np. nawigacja) będzie mieć stałą szerokość, natomiast środek z treścią strony będzie dostosowywać się do rozdzielczości urządzenia. Przeanalizujmy przykład.
<div class="main">
<div id="left">
Sidebar
</div>
<div id="center">
<h1>Title</h1>
<p>Lorem ipsum...</p>
</div?
</div>
.main{
width:80%;
margin: 0 auto;
}
#left{
width:200px;
float: left;
}
#center{
margin-left: 250px;
}
I to by było na tyle
. Jest to prosta w użyciu technika. Dla lewej kolumny wystarczy nadać atrybut float: left. Natomiast dla środkowej kolumny musimy ustawić lewy margines wynoszący co najmniej tyle co szerokość lewej kolumny. Jak to wygląda możecie zobaczyć na tej stronie. W przykładzie ze strony wykorzystano padding, ale w obu sposobach efekt jest taki sam.
Autor wpisu: Jacek Skirzyński, dodany: 07.09.2013 23:18, tagi: php
Pliki projektu mogą być różnie rozlokowane w katalogach a ich łączenie odbywa się z wykorzystaniem funkcji include()/require() i ich wariantów *_once()1. Podawanie ścieżek bezwzględnych zupełnie mija się z celem ponieważ inna będzie na komputerze programisty inna na hostingu. Natomiast zmienianie ich przy przegrywaniu jest stratą czasu i narażaniem się na ewentualne problemy (przy zapomnieniu o zmianie czy błędzie w ścieżce).
Rozwiązaniem tego problemu jest dyrektywa konfiguracyjna include_path i używanie ścieżek względnych.
Bieżącą wartość dyrektywy można sprawdzić w następujący sposób:
<?php print get_include_path(); // u mnie: .:/usr/share/php:/usr/share/pear
Znak kropki w wyniku odnosi się do bieżącego katalogu, natomiast dwukropek to separator ścieżek (można ustawić ich wiele). /usr/share/php i /usr/share/pear to ścieżki, które będą przeszukiwane przy próbie dołączenia plików z użyciem ścieżki względnej.
Jest już przykład odczytania wartość include_path, teraz czas na zmianę wartości i odpowiedź na pytanie: po co to robić?
Do zmiany wartości służy funkcja set_include_path() z pomocą wspomnianej funkcji do odczytu bieżącej wartości oraz stałej PATH_SEPARATOR.
<?php
// wariant 1
set_include_path('/jakas/sciezka/do/katalogu');
print get_include_path();
// pokaże: /jakas/sciezka/do/katalogu
// wariant 2
set_include_path(get_include_path() . PATH_SEPARATOR . '/jakas/sciezka/do/katalogu');
print get_include_path();
// pokaże: .:/usr/share/php:/usr/share/pear:/jakas/sciezka/do/katalogu
Wariant pierwszy pokazuje jak ustawić tylko swoje wartości include_path nadpisując przy tym poprzednie (należy uważać z takim użyciem, ponieważ usunięcie znaku kropki może spowodować wiele komplikacji). Wariant drugi jest dużo mniej inwazyjny i zamiast usuwania poprzednich wartości zwyczajnie dodaje do nich nową oddzielając ją za pomocą stałej (separatorem są znaki dwukropka).
Ostatnią kwestią jest przydatność możliwości zmiany tej dyrektywy. Jedną z sytuacji jest używanie pakietów PEAR, których nie ma globalnie na hostingu i jest jakiś problem z ich zainstalowaniem przez administratora. Rozwiązaniem może być utworzenie w obrębie konta własnego zbioru pakietów PEAR i dodanie ścieżki do tego katalogu do include_path. Późniejsze użycie tych pakietów jest takie samo jak tych dostępnych globalnie.
Autor wpisu: Jacek Skirzyński, dodany: 05.09.2013 23:45, tagi: php
W najbliższym czasie odbędzie się konferencja PHPCon 2013. A dokładnie to 25-27 października 2013 w Szczyrku. Jest to chyba największa w kraju konferencja związana z językiem PHP a dodatkowo przyjeżdżają też prelegenci z zagranicy. W ubiegłbym tygodniu zakończyło się głosowanie na tegoroczną agendę, z którą można się zapoznać tutaj. Wszyscy zainteresowani konferencją mogą uzyskać więcej informacji na stronie konferencji, a nawet zapisać się – rejestracja uczestników jest jeszcze otwarta.
Jednak oprócz informacji na temat tegorocznego PHPCon chcę też zwrócić uwagę na materiały konferencyjne z poprzednich edycji, ponieważ na prawdę są warte uwagi.
Linki:
Kanał ATOM
