Přejít k navigační liště

Zdroják » PHP » Nette Framework: Odvšivujeme

Nette Framework: Odvšivujeme

Články PHP, Různé

Říká se, že v každém programu je alespoň jedna chyba. Používáte nástroje, které vám pomohou chyby zavčasu odhalit? V druhém dílu obsáhlého seriálu o Nette Frameworku vám takové nástroje a techniky představíme. Možná se budete divit, proč je už dávno nepoužíváte ve svých projektech.

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í.

Disclaimer: Autor článku je hlavním vývojářem frameworku Nette.

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é:

  1. dopřejeme sluchu chybám, které PHP generuje
  2. 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á:

Nette 201

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:

Chybová hláška

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

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_bac­ktrace()

if (defined('DEBUG') && DEBUG) {
        Debug::fireLog(new Exception('Byl volán Converter::convert()'));
} 
Zalogování vzniklé výjimky

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

Výpis pole s převodem jednotek

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.

Používáte Laděnku?

Komentáře

Subscribe
Upozornit na
guest
47 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
alblaho

Čert vem Nette, Grundl prostě umí psát a to je hlavní :-)

Martin Soukup

Škoda, že se pan Grudl zabývá jazykem PHP, který se po nocích pokouší přiohnout k obrazu svému. Zbytečně ho to zdržuje od opravdové tvůrčí práce. Jakým geniálních počinů by byl schopen, kdyby používal nějaký jiný jazyk, který má takovéhle vychytávky přímo v základu!

Článku dávám velké plus.

Hoween

David ždímá PHP proto, že se to z toho jazyka dá vyždímat a dostává ho tak do použitelného stavu. Ne každý má k dispozici hosting s Pythonem, nebo Ruby, natož nějaký Java aplikáč. PHP tady je a ještě dlouho bude. A nezávisle na tom, jak je to dobré / špatné, je dobře, že i pro PHP existují dobré frameworky.

Architekt

Ale má to smysl, ztrávit 80% času bojem s dětskými nemocemi PHP, když ten čas může věnovat užitečnější činnosti? Ve výsledku je to velká škoda, že je Nette napsané v PHP. Protože bez ohledu na to, jak je Nette dobrý framework, žádný rozumný člověk nepostaví větší/náročnější aplikaci na PHP. Mimochodem hostingy s Pythonem či Ruby jsou k dispozici za pár kč měsíčně.

Čelo

Smysl to má. Je škoda, že ho nevidíte.

Hoween

Ano ano, všichni ti Centrumové, Facebookové, Seznamové, vBulletinové jsou pitomci, protože Architekt prohlásil, že PHP je špatné :-D

dc

to mate tak preco v onych casoch sa zdimalo C ked existoval Pascal ktory mal niektore veci daleko lepsie vymyslene.Preco dneska sa stale busi c++ a snazia sa v nom lepit nedostatky a pozostatky z cias davno minulych ked je tu java/c# a ine ? Asi preto ze to niekoho bavi alebo preto ze kazdy jazyk ma aj svoje pozitiva.

alblaho

Sorry, ale tahle analogie pěkně kulhá. Plain C, C++, Java, všichni mají své místo.

PHP je mizerný jazyk s excelentní podporou hostingu. To je prostě fakt se kterým se každý z nás musí vyrovnat :-). Grundl se s tím vyrovnal tak, že si udělal framework.

Martin Malý

Jen na okraj: David je Grudl, nikoli GruNdl… Sice to s věcí až tak nesouvisí, význam je jasný i tak, ale myslím, že by to tu mělo zaznít. Pokud tedy není zkomolení jména úmyslné – doufám že ne.

alblaho

Za komolení jména pana Grudla se omlouvám. Občas něco blbě přečtu a pak se toho držím.

dc

no da sa to postavit aj tak ze sorry ale java (myslena implementacia nie jazyk) je mizerna a zbytocna.Naco mi je nieco interpretovane z bytecodu (aj ked dnes je situacia lepsia) ked tu mam kompilovane jazyky ? A vyhody typu multiplatformovost alebo ze to bezi v sandboxe niesu zrovna padne argumenty.
PHP neni ziadny zazrak ale ma tiez svoje miesto.

alblaho

Vždyť já říkám, že i PHP má své místo – především je použitelné na webové aplikace :-)

To s tou Javou jsou samozřejmě kecy. Ten bajtkód se neinterpretuje, ale překládá (zaběhu). No a multiplatformnost je věc šetřící $.

Ladislav Thon

Prča je, když člověk poštve jstack na nějakou svou běžící aplikaci a zjistí, že většina jeho kódu je interpretovaná, protože HotSpot ji nepovažuje za hodnu své pozornosti :-) Interpretace bajtkódu, i na zásobníkovém stroji, je ovšem většinou rychlá dost, notabene na webu.

Axiss

A kolik větších/náročnějších aplikací už jste postavil vy a v čem? A čemu vlastně říkáte větší/náročnější aplikace. Má to nějaké parametry? Já nějak nechápu, proč čtou články a do diskusí o PHP vždy přispívají hlavně ti, kteří to náročné a nepoužitelné PHP nepoužívají. Proč ten čas tady radši nevěnujete nějaké užitečnější činnosti?

skrat

da sa s nette prevadzkovat BDD? alebo aspon TDD?

Borek Bernard

Dá, viz třeba http://www.phpunit.de/.

Anonymní

Nevite nekdo nahodou kde muze byt chyba, kdyz mi firephp nezobrazí á?

A pro redakci: Bylo by mozne tu zmenit obarvovani syntaxe kodu, protoze kdyz kod copy & pastnu do PDT tak se mi nezachovaji odradkovani..?

Anonymní

Soubor ukladam v utf8

Tomáš Fejfar

Tak naposílá PHP správný header.

zkus na začátku skriptu nastavit přes header() správnou hlavičku s UTF-8 kódováním.

Anonymní

To jsem zkousel jako prvni :D

asdf

V tom kódu na začátku nemá být:
$converter->form = 'inch';
ale
$converter->from = 'inch';

asdf

omlouvám se, píšu kraviny, kdybych jenom uměl pořádně číst…

Pichi

Možná by nebylo od věci, aby se zmínka, že to celé je o PHP, objevila pokud možno v perexu a ne až ve čtvrtém odstavci. Čím dřív se člověk dozví, že to nemá číst tím líp.

Hoween

Že Nette je PHP framework je napsané v prvním díle tohoto seriálu. Krom toho si myslím, že nálepka "PHP" je poměrně výmluvná…

Pichi

Nálepky se nenachází, ani v seznamu článků, ani v RSS feedu. Stejně tak se tam nenachází informace, že se jedná o druhý článek nějaké série. Pikantní, že stránky o web vývoji mají takové elementární problémy s použitelností.

wisdom

PHP sucks and this framework fails when attempting to change it. As a matter of fact, with this framework PHP sucks even more and is better usable with none framework.

will

You suck, David & Nette rule! :D

wisdom

I see, only a clueless person thinks that wisdom sucks.

Anonymní

Myslíš, že svému výkřiku dodáš odbornou váhu tím, že ho "světácky" napíšeš v angličtině? Ó, jak jsi kúl!

Anonymní

Vykriky jsou s vykricnikem, treba to co jsi vyblekotal ty. On alespon umi anglicky :)

K. R. Lispnik

Bohužel ani on neumí anglicky zas až tak dobře… :-)

Anonymní

Kde se domnivate, ze je v tech dvou vetach chyba?

Anonymní

Možná třeba with none framework -> without any framework :) asi to není chyba, ale vypadá to přinejmenším divně.

Ladislav Thon

As a matter of fact… člověče, takových fakt bych si vycucal z prstu milióny, ale tak troufalý, abych je prohlašoval za moudra, teda naštěstí nejsem :-)

jan.letko

Uvodom velmi chvalim styl pisania a vysvetlovania, pane mate talent vo vsetkych smeroch.

Tesim sa na MVC cast, drzim palce, super.

Anonymní

Docela by ma zaujimalo p. Grudl v com pisete pre php (ide,editor,nastroje) ?

jirka d.

A SciTE ne? Rozumny kompromis mezi IDE a notepadem?

Anonymní

Hmm Nushpere PhpED som pouzival do minuleho roku.Je sice fajn ale trochu mi vadilo nie uplne dokonale Intelli sense a tiez slaba podpora css/js.
Momentalne pouzivam PDT+aptana css/js editor ktore je skvele co sa tyka funkcionality a moznosti ale zasa rychlost ako aj sprava eclipse (po updatoch sa IDE rado rozpadne) tiez to nieje to prave orechove.
Celkovo mi chyba nejake ucelene IDE.

Srigi

Skus Netbeans v PHP edicii. Vyborna podpora HTML/JS/CSS a PHP samozrejme :D

YangombiUmpakati

clovek si takhle ctivy clanek precte i presto ze uz to vsechno jednou cetl na nette quick startu. jinak koncepce super. bravo! :)

kesspess

Bylo by možné uvedený problém s nedefinovanou konstantou řešit následovně (než kontrolovat inicializaci přes defined())?

define('DEBUG', getenv('REMOTE_ADDR') === '192.168.0.5');

if(DEBUG){
// …
}

snehuliak

Mate tam „class Converter extends Object“

Nemalo tam byt nahodou „class Converter extends NetteObject“, alebo mi nieco uslo?

Dakujem.

Ab

Co je textová noticka?
Ď

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.