Tedy alespoň by neměli. PHP je jazyk s poměrně laxním přístupem
k chybám a tudíž vyžaduje od programátora vyvinout větší úsilí při
jejich ošetřování. Nenechte si namluvit opak. Článek je reakcí na dobře míněnou radu
Jakuba Vrány.
Existují dva tradiční způsoby, jak chyby oznamovat:
vyhozením výjimky
návratovou hodnotou
Takřka všechny knihovny dodávané s PHP používají druhý způsob,
protože výjimky byly do jazyka zavedeny až v páté verzi. Což je v mnoha
případech na škodu. Programátor totiž musí neustále otrocky kontrolovat
návratové hodnoty a ošetřovat chybové stavy. A zároveň je tu riziko, že
na to zapomene. Přesněji řečeno, ani ne tak „zapomene“, jako spíš se
na to „vy…kašle“, protože to ve světě PHP platí za normu. Nakonec,
podívejte se třeba do dokumentace fread a spočítejte,
kolikrát v příkladech ošetřili návratovou hodnotu jakékoliv funkce.
Při takovém stylu práce pak není divu, že programátoři považují
výjimky za něco otravného, co se (cituju Jakuba) „nedá
ignorovat“, zatímco „chyby indikované návratovou hodnotou
ignorovat lze a program nejspíš nějak pracovat bude.“ Notykrávo.
Co přesně znamená ono nějak pracovat? Třeba:
// přesouváme z disku na disk
copy('c:/oldfile', 'd:/newfile');
unlink('c:/oldfile');
// pokud první operace selže, soubor se nenávratně smaže
$link = new mysqli('localhost', $user, $pass, 'eshop');
...
$link->query('USE testdata'); // přepneme se z ostré do testovací databáze
$link->query('DELETE FROM orders');
// pokud předchozí operace selže, vymažou se objednávky z ostré databáze
// BTW věřili byste, že výchozí nastavení query() při chybě nevyhodí ani noticku?
// smažeme soubor z adresáře 'test'
ftp_chdir($connection, 'test');
ftp_delete($connection, 'database.sdb');
// pokud předchozí operace selže, vymaže se kupříkladu ostrá databáze
A pak jsou případy, kdy nějak pracovat znamená zařvat
s neošetřitelnou fatální chybou:
// načtení konfigurace
$defaults = array('host' => 'localhost', 'charset' => 'utf8');
$config = parse_ini_file('config.ini');
$config = $config + $defaults;
// pokud předchozí operace selže a vrátí false, skončí sčítání fatální chybou
function saveConfig(array $config) { ... }
$config = parse_ini_file('config.ini');
saveConfig($config);
// pokud předchozí operace selže a vrátí false, skončí opět fatální chybou
mysqli_connect('localhost', $user, $pass)
->query("SET NAMES 'utf8'");
// pokud první operace selže, skončí opět fatální chybou
Schválně, který software má v dokumentaci uvedeno, že se jedná
o voodoo? No jistě, jde o mod_rewrite. Ze
zkušenosti mohu říci, že programátoři se dělí do dvou skupin:
ti, kteří mod_rewrite nerozumí
ti, kteří si myslí, že mod_rewrite rozumí, avšak mýlí se
Do které skupiny patříte vy? Zkuste nahlédnout do svých souborů
.htaccess a podívejte se, zda vám u pravidel pro
přesměrování (příznak
R) nechybí také příznak NE (noescape)?
Vysvětlím na příkladu: do kořenového adresáře webu
www.example.cz vložím soubor .htaccess s pravidlem
pro přesměrování:
RewriteEngine On
RewriteRule .* http://www.example.com/$0 [R=301] #tohle je spatne!
Server pak přesměruje
z http://www.example.cz/index.php?title=d%C3%ADvka (parametr
title obsahuje slovo dívka)
na http://www.example.com/index.php?title=d%25C3%25ADvka
(parametr title obsahuje řetězec d%C3%ADvka)
Jak vidíte, mod_rewrite ublížil dívce! Je to jeho přirozené chování,
aby to nedělal, musíte mu říct NE:
RewriteEngine On
RewriteRule .* http://www.example.com/$0 [R=301,NE] #tohle uz je spravne
V PHP jsou k dispozici tři knihovny pro regulární výrazy: PCRE, Oniguruma a POSIX Regex. Druhá
jmenovaná nemusí být vždy k dispozici a třetí je zavržená, proto byste
měli používat výhradně šikovnější a rychlejší knihovnu PCRE. Bohužel
implementace trpí docela nepříjemnými nedostatky, a to ve všech
verzích PHP.
Činnost jednotlivých funkcí preg_* lze rozdělit do
dvou kroků:
kompilace regulárního výrazu
exekuce (hledání, záměna, filtrování, …)
Sympatické je, že PHP zkompilovanou podobu regulárních výrazů udržuje
v cache a tudíž se kompilují vždy jen jednou. Proto je vhodné používat
statické regulární výrazy, tj. negenerovat je parametricky.
Teď k těm nepříjemným záležitostem. Pokud se během kompilace odhalí
chyba, PHP na ni upozorní chybou úrovně E_WARNING, avšak
návratová hodnota funkce je nejednotná:
preg_filter, preg_replace_callback,
preg_replace vrací null
preg_grep, preg_match_all,
preg_match, preg_split vrací false
Dobré je vědět, že funkce vracející skrze referenci pole
$matches (tj. preg_match_all a
preg_match) při kompilační chybě argument nevynulují, tudíž
testovat návratovou hodnotu má opodstatnění.
PHP od verze 5.2.0 disponuje funkcí preg_last_error vracející kód
poslední chyby. Avšak pozor, týká se to pouze chyb vzniklých během
exekuce! Pokud dojde k chybě během kompilace, hodnota
preg_last_error se nevynuluje a vrací předchozí hodnotu. Pokud
tedy návratová hodnota preg_* funkce není null
resp. false (viz výše), rozhodně nepřihlížejte
k tomu, co preg_last_error vrací.
K jakým chybám může dojít během exekuce? Nejčastějším případem
je překročení pcre.backtrack_limit nebo nevalidní UTF-8 vstup
při použití modifikátoru u. (Poznámka: neplatné
UTF-8 v samotném regulárním výrazu se odhalí již při kompilaci.)
Nicméně způsob, jak PHP s takovou chybou naloží, je naprosto
neadekvátní:
nevygeneruje žádnou zprávu (silent error)
návratová hodnota funkce může naznačovat, že je vše v pořádku
chybu lze zjistit až následným zavoláním
preg_last_error
Zastavím se u té návratové hodnoty, což je asi největší zrada.
Proces se totiž vykonává do chvíle, než se chyba objeví a poté se vrátí
částečně zpracovaný výsledek. A to v naprosté tichosti. Jenže ani
tohle neplatí vždy, třeba trojice funkcí preg_filter,
preg_replace_callback, preg_replace umí i při
exekutivních chybách vracet null.
Zda došlo během exekuce k chybě lze zjistit jedině voláním
preg_last_error. Ale jak už víte, tato funkce vrací nesmyslný
výsledek v případě, že došlo naopak k chybě kompilace, musíme tedy
obě situace rozlišit přihlédnutím k návratové hodnotě funkce, zda-li je
null resp. false. A jelikož funkce vracející
null při chybě kompilace umí vracet null i při
chybě exekuce, lze konstatovat asi jen tolik, že PHP je nadevší pochybnost
zkurvený jazyk.
Jak by vypadalo bezpečné použití PCRE funkcí? Například takto:
function safeReplaceCallback($pattern, $callback, $subject)
{
// callback musíme ověřit sami
if (!is_callable($callback)) {
throw new Exception('Neplatny callback.');
}
// testujeme výraz nad prázdným řetězcem
if (preg_match($pattern, '') === false) { // chyba kompilace?
$error = error_get_last();
throw new Exception($error['message']);
}
// zavoláme PCRE
$result = preg_replace_callback($pattern, $callback, $subject);
// chyba exekuce?
if ($result === null && preg_last_error()) {
throw new Exception('Chyba zpracovani regularniho vyrazu.', preg_last_error());
}
return $result;
}
Uvedený kód transformuje chyby do výjimek, nesnaží se však potlačit
výpis varování.
Bezpečné zpracování regulárních výrazů je implementováno ve
třídě Nette\Utils\Strings.
Můžu v názvu svého programu nebo služby použít slovo
„Nette“? Otázka, která v poslední době padá docela často, dokonce si
zasloužila oprášit blogísek ;)
Chcete krátkou nebo dlouhou odpověď? Krátká odpověď: bohužel
ne. A teď ta obšírnější.
Velice mě těší, že kolem Nette vznikají nové projekty, ať už jde
o CMS, wiki, fóra nebo dokonce hostingy. Celý ekosystém. Nette inspiruje
programátory, baví je programovat a tvořit. To je prostě paráda!
Když někdo vytvoří open-source fórum a nazve ho NetteForum, je to
v podstatě pocta pro framework. Programátor s ním byl nejspíš velmi
spokojen, jinak by si ho do názvu nedával. Jenže je tu i druhá
strana mince.
Nette Framework je u nás známý a těší se výborné reputaci. Taky se
snažíme s komunitou vše pro to dělat. Když se pak objeví projekt
NetteNěco, spousta lidí se bude domnívat, že jde o oficiální
projekt „od tvůrců Nette Framework“. Ale půjde o omyl a to přece není
příjemné pro nikoho – ani pro uživatele, ani skutečného autora a ani
pro framework. Budou hledat podporu na stránkách frameworku, psát e-maily,
atd. Budou také očekávat stejnou kvalitu, a pokud je zklame, může to házet
špatné světlo i na framework.
Příklad? Nette Framework si buduje image nejbezpečnějšího
frameworku. A teď si představte, že vznikne dejme tomu hosting
pojmenovaný NetteHosting a ten jednoho sychravého dne hacknou. Zmínka
o útoku se objeví na technických serverech a čtenář si řekne: „ejhle,
s tou bezpečností Nette to asi nebude tak žhavé.“ Přitom framework
s tím nemá naprosto nic společného.
Samozřejmě bych byl rád, kdyby hypotetický NetteHosting a
NetteForum byly ty nejlepší služby a držel bych jim palce, ovšem
držení palců je to jediné, co mohu dělat. Když s nimi nemám nic
společného a žádným způsobem nemohu ovlivnit jejich kvalitu, nechci
přijmout ani odpovědnost. Tedy slovo „Nette“ v názvu.
Programátorovi fóra bych stejně spíš poradil: hele, programuješ to pro
lidi, které beztak žádné frameworky nezajímají. Snažit se do názvu
nějak zapasovat „Nette“ je úplně zbytečné, spíš vymysli pěkný
název, co se bude líbit tvým uživatelům, a ať se jim dobře pamatuje. Že
jsi nadšen z Nette Framework raději napiš na své stránky, dej si tam ikonku, obleč si tričko
s logem Nette, ale nepojmenovávej tak své děti 🙂
Mám ve zvyku vyvíjet a spouštět webové aplikace na doménách
s příponou .l, takže třeba vývojová verze
https://nette.org mi běží na http://nette.l. Což
znamená přidat do souboru hosts řádek pro každou
subdoménu, např.:
To je přinejmenším otravné. Kéž by hosts podporoval zápis
pomocí wildcards, stačilo by napsat
*.l 127.0.0.1
a měl bych vystaráno. Jenže tohle nefunguje. Hledal jsem proto jiné
řešení. K velkému překvapení, internet se návody nejen že nehemží,
nenašel jsem vůbec nic.
Bylo zřejmé, že budu potřebovat najít lokální DNS server, který toto
umožní. Narazil jsem na Simple DNS
Plus. Po instalaci je potřeba jej manuálně aktivovat, tj. říci
síťovému připojení, že má používat DNS server na adrese
127.0.0.1. Poté přímo v aplikaci v Tools / Options / Plug-Ins vytvořit
instanci pluginu Regular Expressions a určit, že maska \.l$ se
bude mapovat na adresu 127.0.0.1.
Funguje to výborně, jen cena $79 mi nepřipadá odpovídající,
využívám-li okrajové vlastnosti jinak našlapaného programu.
Další možností je instalace multiplatformního opensource DNS serveru BIND. Stáhl jsem si distribuci pro
Windows a nainstaloval do výchozího adresáře. Ovšem zapomeňte na nějaké
klikací prostředí, BIND zná jen příkazovou řádku a textové
konfigurační soubory. Pro mě španělská vesnice. Naštěstí mě pohled do
dokumentace neodradil rovnou a
podařilo se mi vytvořit konfigurační soubory. Ty se nacházejí
v podadresáři etc, v mém případě je to
c:\Windows\System32\dns\etc\. Na vašem počítači může být
cesta odlišná.
Soubor etc\named.conf
options {
directory "C:\Windows\System32\dns\etc"; // změňte pokud používáte jinou cestu.
};
zone "localhost" {
type master;
file "localhost";
};
zone "l" {
type master;
file "localhost";
};
zone "0.0.127.in-addr.arpa" {
type master;
file "localhost.rev";
};
Drazí provozovatelé hostingů, už je to tady. Před bezmála půl rokem
vyšlo PHP 5.3.0. Přirozený nástupce řady 5.2 bez zpětně
nekompatibilních změn. Víme, že jste s nasazením čekali, než se objeví
následující setinková verze. Dnes se tak stalo, PHP 5.3.1 je venku.
Trojková řada přináší velké
množství vylepšení, na které my, krásní a bohatí programátoři,
toužebně čekáme. Neotálejte proto více a novou verzi PHP nám dopřejte.
Když budete chtít, stihnete ji nasadit ještě dnes v noci! Stejně
v televizi nedávají nic zajímavého. Děkujeme!
(tipy na hostingy, které už novou verzi mají, můžete posílat do
komentářů)
Mam novinu jak cyp! Tak 11.11. v 11.11 hodin (by si to zapamatovaly
i babky z Ludgeřovic) zfarame na 3 hodiny na Bani (Vysoke škole pro štajgery). Esli nas nezavali,
řeknu vam o vyvoji webových aplikací v PHP a Nette Framework. Bude to tu
v baraku RV101.
Fajne logo jak od Najbrtu
Zfarat možu fšeci, kere zajima vyvoj webovych aplikaci, ať už
dělaji jakesik programovaci jazyky nebo maji framework co o něm neslyšela
ani svačinařka z NHKG. Jo! A je to zadara!
Řekneme a ukažem si:
jake je fajne používat frameworky
základy Nette Framework co je to AJAX, DRY, KISS, MVC
ukážu, jak s využitím Nette Frameworku vývoj aplikací zjednodušit,
urychlit a eliminovat bezpečnostní rizika
zkusíme si něco naprogramovat
Jako esli bude aj ohňostroj jak na odpustu v Kravařu to nevim, ale aji tak
to bude fajne, takže dovalte na faračku! (Je to taky na xichtbuku).
Dovolte mi malý experiment. Týká se všech programátorů, které baví
návrh aplikací a OOP. Zadám vám velmi jednoduchý úkol, který má mnoho
možných řešení. A spíš než konkrétní kód mě zajímá způsob
uvažování. Budu rád, když se zapojí programátoři používající různé
jazyky. Proto také zadání zapíši v pseudokódu.
Mějme třídu WebPage, které zadáme URL a ona načte stránku
a vrátí jeji obsah, hlavičky a dokonce i náhled v podobě obrázku.
Příklad použití:
Chápejme url, body, headers a
thumbnail jako vlastnosti (properties, accessors) třídy
WebPage. Je asi zřejmé z logiky věci a principu zapouzdření,
že zatímco url umožňuje zápis i čtení, ostatní vlastnosti
lze pouze číst.
Protože funkčnost třídy WebPage je výkonnostně i časově
náročná, je vhodné jednou získaná data ukládat do databáze. Jak to ale
implementovat? Sice nejsnadnější by bylo rovnou upravit kód třídy
WebPage, jenže takový
postup je špinavý. Proč by třída s jednou srozumitelnou
funkcionalitou měla navíc ještě komunikovat s databází? Pověříme tím
tedy třídu WebPageStorage. Ta nám bude, mimo jiné, schopna
dodat objekt WebPage rovnou z databáze:
Zopakuji, že se pídíme po nejčistším řešení. Jak docílit toho, aby
metoda load mohla vytvořit objekt a nastavit mu read-only
vlastnosti body, headers a thumbnail na
hodnoty načtené z databáze? Vytvořením setterů nebo zpřístupněním
vnitřních proměnných by se porušil princip zapouzdření. Navrhli byste
redesign třídy WebPage? Jaký? Navrhli byste redesign
WebPageStorage? A co když úkol zkomplikujeme tím, že
thumbnail se z databáze načte až při vyžádání?
Věřím tomu, že pro řadu čtenářů je zadání naprosto triviální.
Přesto se zkuste zamyslet nad nejčistším řešením a vysvětlete jej
v komentářích. Klidně obšírně. Díky!
Na WebExpu jsem měl přednášku nazvanou RIA jednoduše –
JavaScript + AJAX + PHP + Nette Framework. Pokusil jsem se ukázat, jak snadno
lze psát AJAXové aplikace za použití Nette
Framework a jQuery. Během přednášky
vznikl jednoduchý příklad, který vám nyní dávám ke stažení.
Protože přednáška byla zaměřená na začátečníky a posluchače,
kteří Nette Framework zatím neznají, začal jsem na zelené louce stažením
frameworku z webu a poté psal celý kód naživo. Doufal jsem, že divák
spíš odpustí různá zaškobrtnutí, která k živému programování
patří, než abych z rukávu vycopy&pastoval předpřipravené kusy kódu
a posluchače připravil o niť. Ale sebekriticky přiznávám, že nervozita
zapracovala a těch zaškobrtnutí mělo být mnohem méně 🙂
Video z přednášky zatím není k dispozici, ale mělo by se objevit
na StreamHostingu.
Pokud máte dojem, že ty 140 znakové kravinky, co píšete na Twitter, je
nutné zálohovat pro příští generace, ať už z důvodu, že Twitter má
občas výpadky doprovázené ztrátou dat, nebo vám někdo může účet
ukrást a smazat, nebo se blížíte k limitu 3200 štěbetnutí, po kterém
se (prý) nejstarší kusy odmazávají, nebo prostě chcete mít vše na disku
kvůli lepšímu vyhledávání, je tento článek pro vás.
Protože jsme na blogu o PHP, nebudu zde popisovat online služby určené
k zálohování, ale rovnou vypustím z klávesnice kus kvalitního
objektového kódu ;)
Nejprve si stáhněte knihovničku Twitter for
PHP (verzi 2.0) od stejnojmenného autora s autorem blogu. A pak si
vytvořte zálohovač twitter-backup.php:
<?php
set_time_limit(0);
require 'twitter.class.php';
// zde dejte své přihlašovací údaje
$twitter = new Twitter($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
// naráz lze načíst maximálně 200 twittů, tož budeme stránkovat
$page = 1;
$retry = 0;
do {
try {
$channel = $twitter->load(Twitter::ME, 200, $page);
if (empty($channel->status)) { // prázdný výstup? narazili jsme na konec
break;
}
file_put_contents("twitter-backup.$page.xml", $channel->asXml());
echo "Ulozena stranka c. $page\n";
$page++;
$retry = 0;
sleep(1);
} catch (TwitterException $e) {
echo "Error: {$e->getMessage()}\n";
if ($retry > 3) break; // chyby s občas stávají, dáme 3 pokusy
$retry++;
}
} while (true);
Po spuštění se vytvoří soubory twitter-backup.1.xml,
twitter-backup.2.xml atd., podle toho, jak jste aktivní
štěbetal. XML obsahuje skutečně vše, včetně informací, na koho zpráva
reaguje, z jakého zařízení byla poslána nebo jaké máte barvičky
v profilu.