phpFashion

Programátoři chyby neignorují

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

Bastlení zdar!


Víte, komu ublížil mod_rewrite?

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:

  1. ti, kteří mod_rewrite nerozumí
  2. 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

Zrádné regulární výrazy v PHP

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

  1. kompilace regulárního výrazu
  2. 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.


StárNette a TloustNette?

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 🙂


Heuréka: example.l na localhost

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ř.:

nette.l   127.0.0.1
www.nette.l   127.0.0.1
forum.nette.l   127.0.0.1

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";
};

Sourbor etc\localhost (využívající wildcard DNS record)

$TTL	86400
@	   IN SOA   @ root (
						2005022501	  ; serial
						3H			  ; refresh
						15M			 ; retry
						1W			  ; expiry
						1D )			; minimum

		IN NS		@
		IN A		 127.0.0.1
*.l.	IN A		 127.0.0.1

a nakonec soubor etc\localhost.rev

$TTL	86400
@	   IN	  SOA	 localhost. root.localhost.  (
							2005022501	  ; serial
							3H			  ; refresh
							15M			 ; retry
							1W			  ; expiry
							1D )			; minimum

		IN	  NS	  localhost.
1	   IN	  PTR	 localhost.

Poté stačí na síťovém připojení aktivovat DNS server na adrese 127.0.0.1 (viz postup výše) a spustit službu ISC BIND. Světe div se, ono to funguje!

C:\>ping nette.l

Příkaz PING na nette.l [127.0.0.1] - 32 bajtů dat:
Odpověď od 127.0.0.1: bajty=32 čas < 1ms TTL=128

Apel na hostéry: chceme PHP 5.3!

PHP

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ářů)


NETTE!!! OSTRAVA!!!

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, KISSMVC
  • 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).


Čistý Programátorský Experiment

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

page = new WebPage
page.url = 'https://phpfashion.com'
echo page.url
echo page.body
echo page.headers
echo page.thumbnail

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:

page = WebPageStorage.load('https://phpfashion.com')
echo page.url
echo page.body
echo page.headers
echo page.thumbnail

Otázka zní: jak to naprogramovat?

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!


Ukázkový příklad z WebExpa 2009

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.


Jak zazálohovat všechny své twíty

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.


phpFashion © 2004, 2025 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.