Možná byste rádi, aby se tento díl jmenoval Vytvoření Facebooku za 15 minut s Nette a vy byste mohli po jeho přečtení a odeslání pochvalného komentáře rovnou vytvořit dokonalou webovou aplikaci. Samozřejmě, mohlo by tomu tak být. Ale jak vás znám, respektive jak znám sám sebe, tak bychom při programování té dokonalé webové aplikace nasekali mraky chyb. A hlavně těch nenápadných, které se nejhůře hledají.
Přičemž PHP je jazyk na sekání chyb jako stvořený, neboť dává vývojáři značnou volnost. Jak tomu udělat přítrž a jak začít psát programy bez těžko odhalitelných chyb? Stačí si nastavit přísnější laťku a dodržovat určitá pravidla. Zkusím být konkrétní.
Podívejte se na tento jednoduchý prográmek s převodníkem délkových jednotek. Z důvodu ladění budeme chtít, aby při každém převodu byl vypsán backtrace. Protože obsahuje citlivé informace, může být zobrazen jen vývojáři, kterého detekujeme podle IP adresy
// příznak DEBUG nastavíme pouze pro vývojáře
if (getenv('REMOTE_ADDR') === '192.168.0.5') {
define('DEBUG', TRUE);
}
class Converter
{
public $from = 'mm';
public $to = 'mm';
private static $units = array(
'km' => 1000000,
'm' => 1000,
'cm' => 10,
'mm' => 1,
'inch' => 25.4,
'mile' => 1609344,
);
function convert($value)
{
if (DEBUG) {
debug_print_backtrace();
}
return $value * self::$units[$this->from] / self::$units[$this->to];
}
}
$converter = new Converter;
$converter->form = 'inch';
$converter->to = 'cm';
echo $converter->convert(123); // přepočítá 123 palců na centimetry
Projděte kód do všech koutů, najdete nějaký bug? Dvě malé chyby tam jsou, to mohu prozradit: převodník nebude přepočítávat palce, nýbrž milimetry a citlivé debugovací informace se vypíší všem, nezávisle na IP adrese.
Jak je to možné? Jednak jsem omylem místo $converter->from
napsal $converter->form
, což je zrakem téměř nepostižitelná chyba, neboť mozek je uvyklý vídat obojí. Záludnost chyby spočívá hlavně v tom, že na ni PHP nijak neupozorní (z hlediska jazyka to není chyba) a stojí hodně námahy ji vypátrat. Druhou chybou je vyhodnocení nedefinované konstanty, kterou PHP z historických důvodů považuje za řetězec, takže podmínka if (DEBUG)
se vyhodnotí kladně vždy. Naštěstí v tomto případě PHP vygeneruje zprávu E_NOTICE.
Jaká laťka a jaká pravidla mi pomohou najít a eliminovat tyto chyby? To je nejspíš zřejmé:
- dopřejeme sluchu chybám, které PHP generuje
- přimějeme třídu upozorňovat na použití neinicializovaných proměnných
Chyby úrovně E_NOTICE
Na uvedeném příkladě vidíte, že je nutné bedlivě sledovat chyby úrovně E_NOTICE. Vnímám je jako upozornění na nejzávažnější chyby v kódu. Je proto obrovským omylem začátečníků se domnívat, že tyto chyby mohou zahazovat, ať už metodou error_reporting, nebo mnohem hůře pomocí zavináče. To si profík tvořící Facebook za 15 minut přece nemůže dovolit!
Abychom mohli chyby všech úrovní sledovat, musíme psát kód tak, aby je sám negeneroval. V uvedeném příkladu to znamená například přidat kontrolu, jestli převodní jednotky vůbec známe – např. isset(self::$units[$this->from])
. Přičemž nesmíme nikdy používat @ pro zahození chyby (kromě velmi výjimečných situací). A nejde tady jen o náš kód, musíme si totiž vybírat a používat takové knihovny, které tyto pravidla také dodržují (například Nette Framework, Zend Framework, dibi, …).
Striktní třídy
Třídu Converter přimějeme upozorňovat na použití neinicializovaných proměnných pomocí overloadingu, konkrétně implementací magických metod __get() a __set(). Mohou například vyhodit výjimku a překlep $converter->form
bude ihned odhalen.
Není to náhodou seriál o Nette Framework?
Jistě, máte pravdu, na framework zatím řeč nepřišla. Nicméně úvod byl nutný. Filosofie přísnější laťky a zmíněných pravidel se totiž táhne celým frameworkem, její otisk najdeme v jeho každičkém bitu. Je to dokonce vědecky prokázáno. A jak vám potvrdí každý vývojář, který weby s Nette Frameworkem tvoří, striktnost mu ušetřila hodně času, který nemusel zbytečně prosedět u počítače, a mohl jej využít mnohem lépe, třeba nějakým příjemnějším prosezením u počítače.
Minule jsem vám sliboval seznámení s Laděnkou aka třídou NetteDebug
. Společně s ní se přijde představit ještě třída NetteObject
, která má ambici stát se prapředkem všech vašich tříd. Rozšiřuje totiž objektový model PHP o pár vychytávek, jednou z nich je právě zmíněná striktnost v používání nedeklarovaných proměnných. Přidejme tedy na začátek programu:
require 'Nette/loader.php';
// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:
// use NetteDebug, NetteObject;
Debug::enable(); // aktivuje Laděnku
a udělejme ze třídy Converter potomka NetteObject
:
class Converter extends Object
{
...
Zatímco dosud kód tiše (ale chybně) proběhl, teď už je situace jiná:
Laděnka označila místo, kde je překlep.
Třída NetteObject
učinila Converter
striktnějším a na použití nedeklarované proměnné reagoval vyhozením výjimky, kterou NetteDebug
zobrazil. Řádek s fatálním překlepem je odhalen a označen, textová zpráva situaci popisuje slovy: Cannot assign to an undeclared property Converter::$form (nelze zapsat do nedeklarované proměnné Converter::$form). Programátor může promptně zareagovat. Chybu, které by si dlouho nemusel ani všimnout a jen za velkého úsilí by ji odhaloval, dostal srozumitelně naservírovanou na červeném podnose.
Poznámka: předpokládám, že příklady zkoušíte na lokálním HTTP serveru. Pokud používáte ostrý server, místo červené stránky vidíte jen prázdné bílé nic. To je v pořádku, doporučuji se proto buď přesunout na lokální server, nebo řádek Debug::enable()
změnit na Debug::enable(Debug::DEVELOPMENT);
.
Všechny třídy Nette Frameworku jsou potomky třídy NetteObject, takže vám automaticky nabízejí její vyšší komfort. Upozorňování na nedeklarované proměnné přitom není zdaleka jedinou schopností této třídy, ostatní si však nechám do dalších pokračování.
Po opravení form
na from
se dostáváme k chybě s použitím nedefinované konstanty. Laděnka aktivovala reportování všech chyb, proto ji v prohlížeči vidíme:
Protože nejde o fatální chybu, jakou byla předchozí nezachycená výjimka, nepoužívá se k vizualizaci červená stránka, ale jen textová noticka. Ve složitější grafice je možno takovou chybu snadno přehlédnout, dokonce nemusí být viditelná vůbec (leda pohledem do kódu stránky). Laděnka se potutelně usmívá, protože má šikovné řešení.
Jako prohlížeč si otevřete Firefox a stáhněte si do něj pluginy
Ve Firefoxu zapněte Firebug a povolte panel Síť (Net), čímž aktivujete i FirePHP. Otevřete si náš prográmek a klikněte na panel Konzole. Ha! Sem se přesunula chybová hláška.
Spolupráce NetteDebug a Firefoxu
Komunikace mezi Laděnkou a konzolí Firebugu probíhá v HTTP hlavičkách. Před předáním zprávy proto nesmí být odeslán do prohlížeče žádný výstup. To lze zajistit například zavoláním metody ob_start na začátku skriptu. Pokud už k odeslání výstupu došlo, chybové zprávy se budou vypisovat klasicky do stránky.
Opravíme tedy chybu při práci s konstantou a zároveň, když už si Laděnka tak dobře rozumí s Firefoxem, co kdybychom ladící informaci místo do stránky poslali rovnou do konzole Firebugu? K tomu nám poslouží metoda Debug::fireLog()
.
function convert($value)
{
if (defined('DEBUG') && DEBUG) {
Debug::fireLog('Byl volán Converter::convert()');
}
return $value * self::$units[$this->from] / @self::$units[$this->to];
}
Metoda Debug::fireLog()
vypíše do konzole prostou textovou zprávu. Malým trikem tam však můžeme poslat celý backtrace a vytvořit tak plnohodnotnou Firefoxí náhradu za původní debug_print_backtrace()
if (defined('DEBUG') && DEBUG) {
Debug::fireLog(new Exception('Byl volán Converter::convert()'));
}
Ještě je třeba zmínit metodu Debug::dump()
pro výpis (tzv. dumpování) proměnných
<style>
pre span { color: blue }
</style>
<?php
Debug::dump($units);
generující formátovaný a ošetřený HTML výstup

a nebo stopky ukryté v metodě Debug::timer()
Debug::timer(); // zapne stopky
... // časově náročná operace
echo Debug::timer(); // vypíše uplynulý čas v sekundách
Produkční vs. vývojový režim
Jak jste si mohli všimnout, Laděnka umí být poměrně výřečná. Její „červená smrt“ zobrazuje úseky kódu, kde se mohou nacházet citlivé informace. To se nakonec týká všech ladících informacích, které posíláme ven pomocí Debug::dump()
nebo Debug::fireLog()
, a samozřejmě také všech chybových zpráv, které generuje PHP.
Co lze ocenit ve vývojovém prostředí, by způsobilo hotové neštěstí na produkčním serveru. Tam se totiž žádné ladící informace vypsat nesmí. Laděnka se proto umí přepínat mezi produkčním a vývojovým režimem. Vývojový režim jsme používali až dosud a poctivě zobrazoval všechny ladící zprávy. Produkční režim naopak zobrazování úplně všech zpráv vypne. Pokud jste tedy v kódu zapomněli nějaké Debug::dump($obj)
, nemusíte se obávat, na produkčním serveru se nic nevypíše. Nepošlou se ani žádné informace do konzole Firebugu, nezobrazí se ani chybové hlášky PHP. Ty se naopak začnou logovat do souboru.
Možná jste díky předchozí zmínce vytušili, že k přepínání režimů slouží parametr Debug::DEVELOPMENT
. A máte recht! Nicméně její výchozí hodnota se určuje autodetekcí podle IP adresy serveru. V drtivé většině případů tak není potřeba režim nastavovat a správně se rozezná podle toho, jestli aplikaci spouštíte na svém lokálním serveru nebo v ostrém provozu.
Logování chyb
V produkčním režimu Laděnka všechny chyby zaznamenává do textového logu. Pokud neurčíme jinak, půjde o soubor php_error.log
. Logování chyb je nesmírně užitečné. Představte si, že všichni uživatelé vaší aplikace jsou vlastně najatí betatesteři, kteří zdarma odvádějí špičkovou práci v hledání chyb a vy byste byl za hlupáka, kdybyste jejich cenné reporty zahodil bez povšimnutí do odpadkového koše.
Pro skutečného profíka je error log klíčovým dokumentem a chce být ihned informován o každé nové chybě. Laděnka mu v tom vychází vstříc, umí totiž o novém záznamu v logu informovat e-mailem. Dokonce referuje o nezachytitelných fatálních chybách. Aby však nezahltila vývojářovu e-mailovou schránku, pošle vždy pouze jednu zprávu a vytvoří soubor php_error.log.monitor
. Vývojář po přijetí e-mailové notifikace zkontroluje log, opraví aplikaci a smaže monitorovací soubor, čímž se opět aktivuje odesílání e-mailů.
K nastavení jména souboru s error logem a e-mailu vývojáře slouží druhý a třetí parametr metody enable()
:
Debug::enable(NULL, 'logs/php_error.log', 'franta@example.com');
Laděnka k vašim službám
Nette Framework je koncipován jako otevřený framework. To znamená, že i jeho jednotlivé části můžete využívat samostatně ve svých aplikacích. Takovým případem je i Laděnka nebo třída NetteObject. Nic vám nebrání si zpříjemnit život tím, že své třídy podědíte od NetteObject a na začátku každého skriptu spustíte Laděnku. Přitom nemusíte nic dalšího z frameworku využít.
Facebook za 15 minut
Gratuluji, myslím, že jste připraveni na tvorbu dokonalé webové aplikace. Vlastně ještě bychom si měli probrat slavnou architekturu MVC a říct si, proč MVC není tím, za co bývá obvykle považováno. Skvělé téma na příště!
Autor článku je vývojář na volné noze, specializuje se na návrh a programování moderních webových aplikací. Pravidelně pořádá školení pro tvůrce webových aplikací, vyvíjí open-source knihovny Texy, dibi a Nette Framework.
Přehled komentářů