PHP triky

Weblog o elegantním programování v PHP pro mírně pokročilé

Common Mistakes in Securing Web Applications

I originally wrote this article for Smashing Magazine about a year ago but it hasn't found its way to publishing. However the ideas in the article are still valid so I am publishing it at least on my blog.

Terms like XSS, SQL Injection or CSRF are well known to more experienced web-application developers. The description of these and other attacks is usually easily understandable and the defense against them is straightforward. Unfortunately, there are still lots of things that can be done wrong. This article tries to point on the usual mistakes done while securing the application. The article is written primarily for PHP developers but most concepts are valid also in other programming languages.

Cross-Site Scripting

The defense against XSS is the easiest one, right? Just use htmlspecialchars somewhere and you are safe. Well, not really.

First of all, you also have to specify the page encoding by the charset parameter of Content-Type HTTP header (you can use default_charset configuration in PHP). Otherwise the attacker can trick the user to view the page in UTF-7 encoding (by displaying it inside a frame on own page in this encoding) where safe strings like +ADw-script+AD4- become dangerous. It is important to use encoding containing all characters (UTF-8 in particular, avoid Latin-1) otherwise browsers will send HTML entities for unknown characters filled in the form.

Next, it is vital to escape data just before outputting it to HTML and not on input for three reasons.

  1. You may want to use data in some other context. Then you do not need to unescape it.
  2. You may want to store data from some other application. For example, you will want to fix some typos in a comment through Adminer and you forget to escape some special character by hand. It may result in non-displaying the page if it is send as XHTML.
  3. The most important reason is simple – the escaped data are longer. Therefore, if you want to store data in varchar(20) and a user will input 15 characters with some special characters then it will not fit. Now you have two options – inform the user that the maximum allowed length is 20 characters (which she adhered) or silently truncate data (it is the default behavior of MySQL) which may result in invalid XHTML again.

Excuses for escaping input like “a new programmer comes in to the project, and forgets to sanitize data before printing it out” are wrong because you cannot escape data twice. Therefore, if you escape data on the input then you cannot escape it on the output, which is an anti-pattern.

The problem with manually applying htmlspecialchars (or its equivalent) is that it can be easily forgotten. Not only echo "?id=$_GET[id]" but also echo $_SERVER["PHP_SELF"] must be escaped which may not be evident (URL can contain special characters for example if Apache directive AcceptPathInfo is enabled which is the default). Thus, the best defense against XSS is to not escape data by hand at all but use a templating system that escapes data for us. Even the classic Smarty supports this feature. Best templating systems provide context-sensitive escaping, which can safely escape data also in JavaScript or style embedded in HTML.

Another common mistake in securing against XSS is using strip_tags on user input. One problem of this function is that it does nothing with character & which may result in invalid XHTML. Another problem is that it is not easy for user to write a character < itself. The usage of a second parameter of this function is even worse because it does not leave only the specified tags but also all their parameters including JavaScript events. Shortly, this function was not meant to escape the user input – it can be used for retrieving the text from XML document. If you want to sanitize the code inputted to WYSIWYG editor, use HTML Purifier.

If you have rock-solid defense against XSS then disable the reflected XSS filter in IE8 by X-XSS-Protection: 0 HTTP header (or parts of your page can be removed by a malicious user).

To sum this up: specify charset, do not escape data for output on input, use auto-escaping feature of your templating system, avoid strip_tags.

SQL Injection

The defense against SQL Injection is even easier than XSS, right? Just use prepared statements with bound variables and you are safe. Well, mostly.

The problem is that variables binding can be used only for data so ORDER BY ? does not work. Binding LIMIT ? or OFFSET ? is also tricky because PDOStatement::execute passes data as strings. You can bind the number by PDOStatement::bindValue with third parameter but easier is usually to simply use intval (or equivalent). If you want to specify a column then use whitelist:

$allowed_orders = array("id" => true, "created" => true);

$query = "SELECT ...";
if (isset($_GET["order"], $allowed_orders[$_GET["order"]])) {
	$query .= " ORDER BY $_GET[order]";
}
if (isset($_GET["limit"])) {
	$query .= " LIMIT " . intval($_GET["limit"]);
}

If you are stuck with the mysql_query and cannot use prepared statements, then use mysql_real_escape_string always inside apostrophes (use intval for numbers). You also need to specify encoding by mysql_set_charset function because different encodings escape different characters. Avoid addslashes because it does not respect server configuration.

Do not forget to disable magic_quotes_gpc or you will encounter backslashes in stored data.

To sum this up: disable magic_quotes_gpc, use variables binding wherever possible, use intval for numbers and whitelists for columns, specify encoding.

Security by Obscurity

To avoid Security by Obscurity, it is enough to hash password, right? Well, not only.

First of all, you also need to add a random salt to password before hashing. Generating a good random string is not easy – the best is to use /dev/random, pretty good is also md5(uniqid(mt_rand(), true)). However, there is bunch of other things you have to do which are thoroughly analyzed in the Month of PHP Security article.

The password is not the only secret thing that must be protected. All tokens are secret too and even the great MOPS article recommends storing password-regeneration token in plain text, which is Security by Obscurity. Always store hash of the token in the database or to the session variable to avoid Security by Obscurity.

If you need to store some sensitive information that you will need to retrieve in original form later (like credit card number) then use asymmetric cryptography. Credit card number will be encrypted by a public key and decrypted by a private key secured with a password. The point is that the password does not need to be stored anywhere (but your head) so even if the attacker would gain access to the database and source codes then he would not get the sensitive data. Delete the encrypted data as soon as it is not required.

To sum this up: use external library for saving passwords, store hash of tokens, use asymmetric cryptography for sensitive data that you need to restore.

Cross-Site Request Forgery

Defense against CSRF is simple, right? Just send some token in all forms and you are safe. Well, not really.

First of all, you do not want to send the token with the GET forms because it can leak the token through the Referer HTTP header. Send the token only in forms performing some operations, which should be always sent by POST. The token does not need to be sent in forms that can be submitted by anyone (for example the registration form). On the other hand, some forms require the token even for anonymous users – for example polls. Send the token if the form is specific for current user.

CSRF is not only the forms attack. If you pass private data in JavaScript executable files (var contacts = [ '...' ]) then the attacker can gain access to this data. Use AJAX for transferring the data instead or include it in HTML.

Never base the CSRF defense on the Referer HTTP header as it may be filtered by firewalls. The session.referer_check PHP configuration directive is useless.

A common mistake in CSRF defense is to generate only one valid token for each operation. It will make it impossible to use the application in more browser tabs or windows:

$operation = $_SERVER["REQUEST_URI"];

// avoid this or the application will not work in more browser tabs
if (!$_POST) {
	$token = generate_token();
	$_SESSION["token"][$operation] = md5($token);
} elseif (md5($_POST["token"]) == $_SESSION["token"][$operation]) {
	// here will be performing of the operation
	unset($_SESSION["token"][$operation]);
}

// use this instead
if (!$_POST) {
	$token = generate_token();
	$_SESSION["token"][$operation][md5($token)] = true;
} elseif (isset($_SESSION["token"][$operation][md5($_POST["token"])])) {
	// here will be performing of the operation
	unset($_SESSION["token"][$operation][md5($_POST["token"])]);
}

The token should be random. Anything else would allow attacker to reproduce it.

To sum this up: generate and verify random token for all POST forms specific for a user, do not send data in JavaScript files, do not unset the token if it can still be in use in another browser tab.

ClickJacking

The defense against ClickJacking is simple, right? Use some frame-busting JavaScript and you are safe. Well, absolutely no.

The problem with JavaScript is that it can be programmatically disabled in IE by <iframe security="restricted">. If you use the opposite approach and allow an action by JavaScript only if a user is not inside a frame then it will not work for the most paranoid users who disable JavaScript. The correct defense is to send the X-Frame-Options HTTP header, which will forbid using a page inside a frame in modern browsers. You can still use the JavaScript for older browsers but do not force users with the newest browsers to enable JavaScript just for securing your site.

To sum this up: send X-Frame-Options on pages specific for the current user.

Conclusion

Use the proper procedures to defend even against the simplest attacks. Try to use the simplest techniques to not forget defense anywhere – automatic escaping in HTML templates or variables binding in SQL are good examples.

There are many other things to take care of in securing web applications – proper initialization of variables, risks of remote execution, session attacks and so one. Defense against them can be also screwed up easily. It can be covered in a next article.

Resources

Jakub Vrána, Dobře míněné rady, 9.11.2011, diskuse: 3 (nové: 3)

Představuji nové školitele

školení se za těch pět let, co je pořádám, stala velmi oblíbená a prošlo jimi několik set spokojených účastníků. Přišlo mi škoda je kvůli mému přesunu bez náhrady zrušit, proto jsem se domluvil s několika zkušenými vývojáři, kteří zároveň umí i přednášet, jestli by školení nevedli po mně. Pozval jsem je na svá školení, předal jsem jim své materiály a zároveň jsem jim dal volnou ruku v tom, kam školení dále posunou. Školení by tak mohla být možná ještě zajímavější než přímo ode mě, protože noví školitelé do nich přidají i své bohaté zkušenosti. Spolu s vypsáním prosincových termínů školení bych vám proto školitele rád představil:

Michal Špaček

foto Michal Špaček se PHP profesionálně věnuje od roku 2000, v současnosti působí v pražské vývojové kanceláři Skype jako vedoucí vývojář webových aplikací. Má zkušenosti s platebními systémy i aplikacemi s vysokou návštěvností. Michala jste mohli několikrát vidět přednášet na WebExpu, případně se s ním setkat jako s pořadatelem IPO48 v Praze.

Michal povede školení Úvod do PHP, Programování v PHP 5, Výkonnost webových aplikací a Bezpečnost PHP aplikací.

Antonín Faltýnek

foto Antonín Faltýnek se již řadu let pohybuje v prostředí aplikací pro bankovní domy, kde je kladen velký důraz na vysokou dostupnost a výkon. Profesionálně se věnuje databázím Oracle, PostgreSQL a MySQL a vyvíjí serverové aplikace v Javě.

Antonín povede školení Návrh a používání MySQL databáze a Konfigurace a výkonnost MySQL.

Kristýna Knapová

foto Kristýna Knapová absolvovala MFF UK a pracuje jako analytička a programátorka webových informačních systémů (např. GOSys). Kromě toho také působí jako lektorka kurzů a autorka vzdělávacích materiálů.

Kristýna povede školení JavaScript a AJAX.

Jakub Vrána, Školení, 3.10.2011, diskuse: 7 (nové: 7)

WebExpo 2011

Letošní WebExpo se podle mě mimořádně vyvedlo. Spokojený jsem byl i loni, protože jsem drobné organizační problémy považoval za detaily, ale letos se jich konference zbavila a musím říct, že byla opravdu profesionálně připravená a zcela srovnatelná se zahraničními konferencemi. Na rozdíl od loňska nebudu popisovat všechny přednášky, na kterých jsem byl, ale jenom ty, které mě něčím zaujaly.

Doporučuji také komunitní poznámky z DevHall a DevRoom.

Jak napsat a otestovat tisíce řádků kvalitního objektového kódu

Konference pro mě začala už ve čtvrtek, protože jsem přišel na workshop o objektovém pojetí programování kluků z Medio (i když oficiální název byl trochu jiný). Bylo velmi zajímavé se pobavit o tom, jestli může být Square potomkem Rectangle (nebo snad naopak), jestli má být Cache interface nebo abstraktní třída nebo jak řešit ošetření chyb u nesouvisejících operací. Zajímavá byla debata o zodpovědnosti konstruktoru – na přednášce zaznělo dogma „Nedělat v konstruktoru žádnou práci“, což je třeba u třídy reprezentující připojení do databáze podle mě nesmysl. Na obědě jsme se bavili také o tom, jestli třída může znát své výchozí hodnoty – ať už formou statické vlastnosti nebo předáním hodnoty null místo objektu (to podle mě šikovně využívá NotORM). Obecně mi přišlo, že se kluci zbytečně bojí dědičnosti (která vyjadřuje vztah „je“) a místo ní se omezují jen na skládání objektů (které vyjadřuje vztah „má“) a implementaci rozhraní („umí“). Zajímavá byla i diskuze o Dependency Injection, při které jsem dospěl k tomu, že se mi nelíbí volání $userSevice->login($user) ve srovnání s $user->login(). Další věc je ta, že když přidám parametr do konstruktoru, tak to musím deklarovat ještě v konfiguraci DI (pokud nepoužívám magii).

Nakonec došlo i na testování se zaměřením na PHPUnit. Ten sice nepoužívám, ale docela mě zaujala možnost mockování (které osobně dělám až na co nejnižší možné úrovni, protože tím se mi zadarmo otestují nižší vrstvy na operacích, které aplikace skutečně používá). Ale i obrat whitebox testování může být někdy užitečný. Např. při blackbox testu operace uložení musíme data následně načíst, abychom ověřili, že se to podařilo. Při whitebox stačí ověřit, jestli se zavolala metoda pro uložení se správnými parametry.

Novinky z konference BUILD

Na přednášce Davida Grudla bylo znát, že výjimečně nepřednáší o něčem, co sám vytvořil. Jeho obvykle velmi vysoké úrovně přednášení se mu proto podle mě tentokrát nepovedlo dosáhnout.

Deployment PHP aplikací

Přednáška Jana Mittnera se mi líbila velmi. Bylo vidět, že nasazování PHP aplikací mají opravdu dobře zvládnuté. Bylo mi trochu líto, že v některých oblastech nešel trochu víc do hloubky, např. principu fungování DBDeploy mohlo být podle mě věnováno alespoň pár minut.

Test Driven Development v CoffeeScriptu

Přednášku Jirky Knesla jsem si bohužel moc neužil. Začala totiž popisem CoffeeScriptu, který znám, a tak jsem se pustil do připravování vlastní přednášky. Pak ale najednou koukám, že na obrazovce se volají jakési funkce test a asyncTest, které přesně nevím, kde se vzaly. Část o tom, jak zprovoznit testovací prostředí, mi tedy asi bohužel unikla.

Cassandra

Přednáška o NoSQL databázi Cassandra pro mě byla zajímavá především proto, že ji vytvořili ve Facebooku (i když ji už teď nepoužívají). Přednáška byla dobrá, ale přesto mi bylo líto, že jsem kvůli tomu přišel o přednášku o AngularJS, která prý byla výborná.

On-line mapy u nás a ve světě

Na přednášku o Mapy.cz jsem se těšil, protože tuto aplikaci považuji za velmi zdařilou a mapové aplikace mě zajímají obecně. Přednášejícímu se ale podle mě bohužel nepodařilo kvalitu aplikace a práci, která s tím byla spojená, ukázat příliš přesvědčivě. Rozhodně bych víc ocenil přednášku od současného vývojáře, než od produktového manažera.

Za hranicemi jQuery

Danovi Steigerwaldovi se na přednášce podařilo ukázat, že se v JavaScriptovém vývoji opravdu dobře orientuje a část svých znalostí dokázal předat i publiku. Na přednášce se podle mě zbytečně zdržoval některými obecnými tématy (např. Dependency Injection), takže nezbyl čas na detailnější popis toho, jaké přednosti frameworky za hranicemi jQuery vlastně mají. Jeho rozdělení na frameworky první až třetí generace se nicméně okamžitě ujalo a odkazovali na něj i další přednášející. Zajímavá byla i zmínka o novém jazyku Dart, který má ambice JavaScript nahradit. Danovo školení vývoje aplikací v JavaScriptu bych si rozhodně nenechal ujít.

Serversideness

Douglas Crockford, jedna z největších osobností na konferenci, začal popisem programování v JavaScriptu na straně serveru. Pak bohužel neukázal, jaké problémy jsou s tím spojené, a rovnou přešel k řešení těchto problémů. Kvůli tomu na mě přednáška působila poněkud roztříštěným dojmem bez sjednocující nitě. Jako opakování principu closures, který má v JavaScriptu široké uplatnění, to ovšem bylo dobré. Člověk skoro nabyl dojmu, že nejpřirozenější funkce je taková, která přijímá callback a vrací zase callback. Vrtalo mi hlavou, jak se dá napsat funkce, která paralelně spustí více funkcí a zavolá callback, až skončí. Základní myšlenka je tahle (jak mi poradil Dan):

/** Paralelní spuštění více funkcí s dokončovací operací
* @param function[] pole funkcí, které se mají spustit
* @param function funkce spuštěná po jejich dokončení
* @return undefined
*/
function parallel(callbacks, done) {
	var count = callbacks.length;
	for (var i=0; i < callbacks.length; i++) {
		setTimeout((function (callback) {
			return function () {
				callback();
				count--;
				if (!count) {
					done();
				}
			}
		})(callbacks[i]), 10 * Math.random());
	}
}

PHP in 2011

Přednáška Rasmuse Lerdorfa, původního tvůrce PHP, byla vynikající. V prezentaci se mi líbily živé ukázky, které bez opuštění stránky komunikovaly se serverem. Dobrý byl i popis vlastností PHP 5.3 a 5.4, který šel nad rámec běžně uváděných informací. Např. mi došlo, že místo break 2 by bylo lepší použít goto nebo jsem se dozvěděl o zlepšení chybových hlášek v PHP 5.4 (to je k nezaplacení při školeních). V pasáži o HipHopu jsem se dozvěděl také o zajímavém chování PHP: funkce může dostat dva stejnojmenné parametry (HipHop na to upozorní, PHP ne):

<?php
function two_as($a, $a) {
    echo "$a\n";
}
two_as(1, 3);
?>

Při následné diskuzi jsme se bavili o tom, proč se některé věci do PHP 5.4 nejspíš nedostanou (např. new foo()->bar() má nekvalitní patch, který někdy způsobuje i pád PHP) a jaký bude další vývoj PHP (Rasmusovi osobně teď žádná zásadní vlastnost nechybí).

Scala - jazyk budoucnosti

Přednáška o Scale byla asi nejprofesionálnější na celé konferenci – práce s publikem, krásné slajdy, motivující přednes. Od jiných přednášejících jsem se přitom dozvěděl, že na generální zkoušce patřila přednáška naopak k tomu nejhoršímu – klobouk dolů za takový pokrok. Nejvíce mě zaujal současný trend jazyků, které se kompilují do něčeho jiného – CoffeScript do JavaScriptu, Scala do bytekódu Javy. Spatřuji na tom jisté výhody (možnost používat stávající knihovny a nástroje), ale i nevýhody (pomalejší běh, horší možnosti ladění). Jsem zvědav na přístup jazyka Dart, který by měl v kompatibilních prohlížečích běžet nativně a v nekompatibilních pomocí JavaScriptu.

ElasticSearch: Za hranice běžného fulltextového vyhledávání

Karmi dostál pověsti vynikajícího přednášejícího a ElasticSearch dokázal publiku „prodat“. Škoda, že se přednáška soustředila hlavně na možnosti rozkládání zátěže a schopnosti zotavit se z výpadku serveru a ne na samotné vlastnosti vyhledávání.

Druhý přednášející působil zmateně a i když k vyhledávání vytvořil zajímavé rozhraní, tak bych si přednášku víc užil, když by prezentaci nechal na Karmim.

Testování prakticky

Honza Král nabídl přehledný úvod do testování webových aplikací, i když z kódu jsem dvakrát nadšený nebyl. Komentář „tady vypočítáme nějaká data“ v definici pohledu (logika by přitom měla být v modelu) nebo vytváření objektů přímo v kódu testování příliš nepomáhá. Ani poznámka o tom, že některé metody není třeba testovat, mě příliš nenadchla – takové metody je někdy lepší z kódu úplně eliminovat. Ale jako testování 101 to myslím pro řadu lidí mohlo být užitečné.

Ajaxizace

Já jsem na WebExpo přihlásit přednášku o tom, jak urychlit webovou aplikaci tím, že se většina operací (návštěva odkazů a odeslání formulářů) bude dělat AJAXem. S tím mám velké zkušenosti z Admineru a chtěl jsem se o ně podělit. Původně jsem o tom chtěl napsat článek, ale téma se mi nedařilo příliš uchopit, tak jsem zvolil formu přednášky. A musím říct, že nelituji. Přednáška byla ryze technická a soustředila se především na popis problémů, se kterými by aplikace měla počítat. Nebylo to takové to konferenční show, ale mým cílem bylo především předat informace, což se mi doufám povedlo. Materiály jsou ke stažení.

CZ Podcast #54

V pátek jsem se dozvěděl, že bych měl být spolu s Honzou Králem hostem rozhovoru o práci v zahraničí. Já jsem v zahraničí ještě nepracoval a Honza momentálně pracuje pro českou pobočku, ale nakonec jsme se o čem bavit přece jen měli. V sále bylo jen pár posluchačů, protože většina dala přednost nadšenému projevu Johna Vanhary, kam jsem chtěl původně jít i já. Nakonec jsem stihl alespoň závěr přednášky (dotazy z publika) a i to stálo za to.

Závěr

Většina přednášek mě něčím zaujala a rozhodně nelituji, že jsem na WebExpu byl. Organizace i prostory konference se zlepšily, takže i na to už si málokdo může stěžovat. Nejsem si jist, že se WebExpu někdy podaří vyrůst mimo české prostředí, ale podle mě to není na škodu. Pokud se organizátorům bude dařit shánět vynikající české i zahraniční přednášející tak jako letos a české publikum to dokáže uživit, tak podle mě ani nevadí, že platící cizinci na konferenci prakticky nejsou.

Jakub Vrána, Osobní, 26.9.2011, diskuse: 13 (nové: 13)

Transakce v reálném světě

U některých operací v reálném světě bych docela uvítal, když by probíhaly v transakci:

A tak dále, další příklady vás jistě napadnou samotné. Co by transakce vlastně měla splňovat?

Atomicita
Provede se buď všechno, nebo nic. Pokud třeba nanejdeme nový sáček na smetí, tak v koši raději zůstane ten plný.
Konzistence
Po dokončení systém zůstane neporušen. Tedy nový pytlík v koši nebude děravý.
Izolovanost
Není vidět mezistav. To znamená, že se nikdo nesetká se situací, kdy by v koši nebyl žádný pytlík. Dá se to vyřešit čekáním.
Trvalost
Změny se neztratí. Tedy když nám systém (např. spolubydlící) řekne, že operace je dokončena, tak už o ni nepřijdeme.

V reálném světě ale bohužel většina lidí pracuje v autocommit režimu…

Příště si povíme něco o reportování chyb a zotavování z nich, které bych v reálném světě také trochu poupravil.

Přijďte si o tomto tématu popovídat na školení Návrh a používání MySQL databáze.

Jakub Vrána, Seznámení s oblastí, 21.9.2011, diskuse: 5 (nové: 5)

Záměna proměnných v řetězci

Pro vygenerování HTML kódu ve webové aplikaci obvykle používám šablony, konkrétně Latte. Někdy je to ale zbytečně těžký kalibr a úplně by mi stačilo v textu nahradit pár proměnných. Něco jako sprintf, ale s tím, že by proměnné byly pojmenované.

K tomu se dá použít funkce preg_replace_callback, pomocí které vyhledáme proměnné v určitém formátu a nahradíme je na základě pole s definovanými hodnotami.

Řešení v PHP 5.3

PHP 5.3 nám díky anonymním funkcím nabízí elegantní řešení. Funkci pro záměnu můžeme definovat přímo při volání a můžeme jí určit, že může používat pole s hodnotami.

<?php
/** Náhrada proměnných v řetězci
* @param string řetězec $abc se nahradí za proměnnou, $$ za dolar
* @param array ('abc' => 'hodnota')
* @return string
* @copyright Jakub Vrána, http://php.vrana.cz/
*/
function replaceVariables($template, $variables) {
    return preg_replace_callback(
        '~\$([a-z_][a-z0-9_]*|\$)~i',
        function ($match) use ($variables) {
            if ($match[1] == '$') {
                return '$';
            }
            return $variables[$match[1]];
        },
        $template
    );
}

echo replaceVariables('Hello $name!', array('name' => 'World'));
?>

Řešení v PHP 5

V PHP < 5.3 si s funkcí nevystačíme a musíme definovat třídu, která si hodnoty uloží do pomocné vlastnosti:

<?php
class VariablesReplacer {
    protected $variables;
    private $string;
    
    function __construct($template, $variables) {
        $this->variables = $variables;
        $this->string = preg_replace_callback(
            '~\$([a-z_][a-z0-9_]*|\$)~i',
            array($this, 'replace'),
            $template
        );
    }
    
    function __toString() {
        return $this->string;
    }
    
    protected function replace($match) {
        if ($match[1] == '$') {
            return '$';
        }
        return $this->variables[$match[1]];
    }
    
}

echo new VariablesReplacer('Hello $name!', array('name' => 'World'));
?>

PHP 4

V PHP 4 neexistovala metoda __toString, proměnné navíc mohly být jen veřejné. Ke zpracování výsledku bylo tedy nutné použít dočasnou proměnnou:

<?php
class VariablesReplacer4 {
    var $string;
    var $variables;
    
    function VariablesReplacer4($template, $variables) {
        $this->variables = $variables;
        $this->string = preg_replace_callback(
            '~\$([a-z_][a-z0-9_]*|\$)~i',
            array($this, '_replace'),
            $template
        );
    }
    
    function _replace($match) {
        if ($match[1] == '$') {
            return '$';
        }
        return $this->variables[$match[1]];
    }
    
}

$variablesReplacer = new VariablesReplacer4('Hello $name!', array('name' => 'World'));
echo "$variablesReplacer->string\n";
?>

Hack

Samozřejmě by se dala použít i funkce eval (lišící se ve zpracování zpětného lomítka):

<?php
function replaceVariablesEval($template, $variables) {
    extract($variables);
    return eval('return "' . addcslashes($template, '\\"') . '";');
}

echo replaceVariablesEval('Hello $name!', array('name' => 'World'));
?>

Tento přístup je ale pomalý (protože eval nedokáží zpracovat akcelerátory) a potenciálně nebezpečný (protože se dá přistoupit i k superglobálním proměnným).

Závěr

Na jednoduché ukázce je vidět, jak se novější verze PHP dají využít k psaní kratšího a elegantnějšího kódu.

Přijďte si o tomto tématu popovídat na školení Programování v PHP 5.

Jakub Vrána, Řešení problému, 26.8.2011, diskuse: 22 (nové: 22)

Starší články naleznete v archivu.

© 2005-2012 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.