phpFashion

Rubrika PHP

foreach pikoška

Na stránce www.phpbench.com, která je bohužel plná zavádějících měření, jsem narazil na zajímavou konstrukci:

foreach ($arr as $key[] => $val[]);

Funguje to od PHP 4 a jde vlastně o obdobu

$key = array_keys($arr);
$val = array_values($arr);

Vůbec jsem netušil, že za as může následovat podobný výraz. Podíval jsem se do souboru zend_language_parser.output (najdete ve zdrojácích PHP) a ono je možné dokonce vytvářet konstrukce jako foreach ($arr as $key[fce($obj->x)] => $val[$y]). Praktické využití mě nenapadá, dívky tím neohromíte, ale blognout jsem to musel 🙂

Ať je alespoň něčím tento spot užitečný: víte, že funkce array_keys má tři parametry?


Ještě ke stránce The PHP Benchmark: snaha autora i vizuální provedení je rozhodně chválihodné, bohužel některé výsledky bez patřičného komentáře vyvolávají dojem, že ta či ona konstrukce je zbytečně pomalá. Pro pochopení je ale potřeba důkladná znalost vnitřností PHP (viz Půvab optimalizace rychlosti, Černá magie optimalizace, Derick Rethans: Understanding the PHP Engine).


Smějeme se s Delphi for PHP

Že je Delphi for PHP absolutní propadák, se už nějakou dobu ví. Prohra je to naprosto zasloužená. Borland (respektive jeho pobočka CodeGear) totiž vypustil produkt, kde prakticky nic nefungovalo. Zatímco Arthur už dal Delphi sbohem u první verze, já neodolal svodům CodeGearu a zkusil si nainstalovat docela čerstvou verzi 1.5. Mohu tak potvrdit, že ani v ní si aplikaci nespustíte! (to si nedělám srandu)

Je mi upřímně smutno z toho, kam to společnost, ke které jsem vzhlížel už jako dítě, kam to dotáhla. Byl to Borland, kdo dal světu IDE, kdo přišel s revolučním Turbo Vision a ustál i smrtící přechod na Windows. Sbohem a šáteček.

Ještě než jsem Delphi for PHP odinstaloval, zlákal mou pozornost zdrojový kód frameworku VCL for PHP (pro PHP 5), který má webové aplikace pohánět. Kdyby mě někdo v tu chvíli natáčel, zaznamenal by desetiminutový plynulý morphing od němého úžasu k hlasitému smíchu.

Ať se pobavíte taky:

  • autoři neví, že PHP má konstrukci foreach a zásadně iterují přes while(list($key, $val)=each($arr) (používalo se v PHP 3)
  • neví nic o statických metodách
  • slabě tuší o privátních metodách
  • naopak klíčové slovo global používají s úspěchem (více než 70 výskytů)
  • autoři neznají konstanty tříd, vše deklarují na globální úrovni
  • kód tříd je důkladně prošpikován HTML fragmenty, dosolen JavaScriptem a špetkou CSS
  • tyto framenty mají občas syntax HTML, občas XHTML
  • názvy CSS vlastností jsou psány někde velkými písmeny, jinde malými
  • zdrojáky se jen hemží zakomentovaným kódem a různými poznámkami, z nichž některé až zarážení svou triviálností:
    //calls inherited constructor
    parent::__construct($aowner);
    

    Programátor nejspíš dlouho googlil, co ten tajemný kód znamená, že si to pak samou radostí poznamenal 🙂

  • v kódu je použito $HTTP_SERVER_VARS (zavrženo v PHP 4.1)

Fakt by mě zajímalo, který zoufalec by na tom vybudoval aplikaci 🙂

p.s. CodeGear neusnul na vavřínech a už ohlásil IDE pro Ruby on Rails nazvané 3rdRail. Připouštím, dělal jsem si z Rubystů občas srandu, ale tohle bych jim nikdy nepřál!


PHP triky: generování JavaScriptu

Výstupem PHP skriptu bývá nejčastěji HTML stránka. S jejím generováním na úrovni značek může pomoci třeba knihovna NHtml. Ale ať už výstup generujete jakkoliv, je třeba mít na paměti, že každý řetězec se musí ošetřit funkcí htmlSpecialChars. To ji staví do pozice téměř nejdůležitější funkce, škoda proto, že nemá kratší název. Ačkoliv i to se dá napravit:

function h($s) {
	return htmlSpecialChars($s, ENT_QUOTES);
}

Nojo, ale co když potřebujeme ve stránce vygenerovat JavaScriptový kód? Jakou funkcí v něm ošetříme řetězce?

Je to s podivem, ale až do verze PHP 5.2.0 s touto eventualitou tvůrci nepočítali. Žádnou funkci pro „escapování“ řetězců jazyk nenabízel. Jako náhrada se občas používá addSlashes, jenže ta nevkládá lomítka na všechna potřebná místa a navíc je závislá na nastavení direktivy magic_quotes_sybase. Teprve s příchodem verze 5.2 přišla spása nazvaná json_encode.

$foo = ...;  // libovolný řetězec v UTF-8
echo '<script type="text/javascript">';
echo 'var foo = ', json_encode((string) $foo), ';';
echo 'alert(foo);';
echo '</script>';

Poznámka: pokud píšete v XHTML a obsah skriptu uzavíráte mezi značky <![CDATA[ a ]]>, je třeba ještě zajistit, aby se v textu skriptu neobjevila koncová značka. Ale lepší spíš bude se těmto Xvěcem vyhnout.

Sice za několik dní už žádného správného vývojáře verze nižší než 5.2.0 zajímat nebudou 😉, než k tomu však dojde, podělím se s vámi o alternativní řešení. To umí nejen sanitizovat řetězce, ale je plnohodnotnou náhradu funkce json_encode (potřebujete-li ošetřit pouze řetězce, stačí vám část za if (is_string($val))):

if (!function_exists('json_encode')) { // since PHP 5.2.0

	/**
	 * JSON encode
	 *
	 * @author	 David Grudl
	 * @copyright  Copyright (c) 2007 David Grudl
	 */
	function json_encode($val)
	{
		// indexed array
		if (is_array($val) && (!$val
			|| array_keys($val) === range(0, count($val) - 1))) {
			return '[' . implode(',', array_map('json_encode', $val)) . ']';
		}

		// associative array
		if (is_array($val) || is_object($val)) {
			$tmp = array();
			foreach ($val as $k => $v) {
				$tmp[] = json_encode((string) $k) . ':' . json_encode($v);
			}
			return '{' . implode(',', $tmp) . '}';
		}

		if (is_string($val)) {
			$val = addslashes($val); // ' " \ NUL - due to bug #40915
			return '"' . addcslashes($val, "\x8..\xA\xC\xD/") . '"';
		}

		if (is_int($val) || is_float($val)) {
			return (string) $val;
		}

		if (is_bool($val)) {
			return $val ? 'true' : 'false';
		}

		return 'null';
	}
}

Tato funkce se oproti předloze liší v tom, že neodhalí rekurzivně zanořené pole nebo objekty. V případě polí by se jednalo o docela netriviální úkol a tudíž nejlepším řešením je ho neřešit vůbec.


PHP triky: standardní výjimky

Kromě základní třídy Exception disponuje PHP počínaje verzí 5.1.0 celou škálou dalších předpřipravených výjimek. Než si tedy vytvoříte novou třídu, zkuste se podívat, jestli by vám nevyhovovala některá z těchto:

  • LogicException – předvídatelné již při návrhu programu
    • BadFunctionCallException – chyba při volání funkce; funkce nenalezena; volání nepovoleno
    • BadMethodCallException – totéž pro metody
    • InvalidArgumentException – špatný argument předaný funkci
    • OutOfRangeException – index mimo rozsah pole či kolekce
    • LengthException – hodnota překračuje povolenou délku
    • DomainException – hodnota nespadá do požadované domény, rozsahu
  • RuntimeException – zjistitelné pouze za běhu programu
    • OverflowException – přetečení bufferu či aritmetické operace; více dat než očekáváno
    • UnderflowException – podtečení bufferu či aritmetické operace; méně dat než očekáváno
    • OutOfBoundsException – index mimo rozsah pole či kolekce
    • RangeException – hodnota nespadá do požadovaného rozsahu
    • UnexpectedValueException – neočekávaná hodnota (např. návratová hodnota funkce)

…pokračování


PHP triky: zjišťování ini_get

Interpretovat nastavení konfiguračních direktiv nemusí být vůbec snadné. V případě logických hodnot sice dokumentace tvrdí, že se vždy vrací 1, 0 nebo prázdný řetězec, ale není tomu tak v případě, že hodnotu nastavíme souborem .htaccess či httpd.conf a direktivou php_value:

php_value magic_quotes_gpc On

V takovém případě ini_get vrátí přímo řetězec On. Konfiguraci je proto lepší zjišťovat účelovou funkcí, jako je třeba get_magic_guotes_gpc(). V případě, že žádná funkce k dispozici není, můžete použít tuto:

/**
 * Gets the boolean value of a configuration option
 * @param string  configuration option name
 * @return bool
 */
function getDirective($directive)
{
	$val = strtolower(ini_get($directive));
	return $val === 'on' || $val === 'true'
		|| $val === 'yes' || $val % 256;
}

if (getDirective('zend.ze1_compatibility_mode')) {
	// zend.ze1_compatibility_mode is ON
}

PHP triky: include, require a cesty

Vkládání skriptů přes include nebo require není vždy transparentní, protože programátoři často neví, z jakého adresáře se skript načte. Pravidla jsou následující:

  1. pokud je cesta absolutní, nic se nedohledává
  2. pokud cesta začíná na ./ nebo ../, hledá se vůči aktuálnímu adresáři (neplést s adresářem s právě prováděným skriptem!)
  3. v ostatních případech:
    1. prohledají se všechny cesty z include_path relativně vztažené k aktuálnímu adresáři
    2. hledá se od adresáře s právě prováděným skriptem

„Na jistotu“ lze obecně vkládat pouze přes absolutní cesty. K tomu se užívá tato formule:

require_once dirname(__FILE__) . '/libs/TexyBase.php';

Následující trik ukazuje, jak se lze volání funkce dirname zbavit (nedoporučuju používat)

require_once __FILE__ . '/../libs/TexyBase.php';

PHP 5.3 má novou magickou konstantu __DIR__, která nahradí volání dirname(__FILE__):

require_once __DIR__ . '/libs/TexyBase.php';

PHP & DOM; interní poznámky

Pár postřehů k DOM API v PHP 5. Jde o funkce pro parsování, generování a manipulaci s XML a HTML soubory. Chování se může v různých verzích lišit.

$dom = new DOMDocument();

// konfigurace pro načtení
$dom->preserveWhiteSpace = false;
$dom->load('input.xml');

// konfigurace pro uložení
$dom->formatOutput = true;
$dom->encoding = 'utf-8';
$dom->save('output.xml');

Načtení XML souboru

  • metody $dom->load($fileName), $dom->loadXML($string)
  • konfigurační vlastnosti:
    • $dom->resolveExternals – načíst externí entity z DTD deklarace? Výchozí: false
    • $dom->strictErrorChecking – throws DOMException on errors? Výchozí: true
    • $dom->validateOnParse – načíst a validovat oproti DTD? Vychozí: false
    • $dom->preserveWhiteSpace – zachovat redundantní white space? Výchozí: true
    • $dom->substituteEntities – zaměnit jmenné entity? Výchozí: false
  • validování nebo resolveExternals stahuje DTD – extrémně pomalé!
  • aby se nestahovalo DTD:
    • ponechat nastavené resolveExternals = validateOnParse = false
    • nebo použít XML Catalogs (nepovedlo se mi rozchodit)
    • nebo modifikovat URL v <!DOCTYPE> na lokální kopii DTD
  • zpracování DTD je pomalé (tj. i bez stahování)
    • načtení XHTML dokumentu trvá cca 50× déle
    • validování už pak zpomalí jen mírně
  • bez zpracování DTD:
  • $dom->encoding určuje XML deklarace

…pokračování


Jak ověřit platné IČ a rodné číslo?

Víte, že rodné číslo nemusí být dělitelné 11? Že algoritmus ministerstva vnitra pro ověřování IČ je špatný? A že jej používá celá řada aplikací? A co na to Jan Tleskač?

Je užitečné, pokud aplikace (nejen webové) umí ověřit platnost různých identifikátorů, jako je třeba e-mail, rodné číslo, IČ a podobně. Naopak průseroidní je, když ověřovací algoritmus obsahuje chybu a digitální nepřítel pak tvrdošíjně odmítá vaše nacionále. Ukecat se nenechá, třísknout ho nemůžete.

Zrovna ohledně ověřování rodných čísel a IČ se obávám, že chybných bude většina implementací. Problém je v nedostatečném zdokumentování algoritmů, takže programátoři často spoléhají na neověřené informace. Jako skutečně kvalitní studnici vědění bych doporučil:

Hned si jej stáhněte!

…pokračování


PHP Surprise

Článek upozorňuje na smutný fakt, že i v nových verzích PHP se umí objevit chyba, díky kterým přestanou fungovat základní jazykové konstrukce a funkce.

PHP je jazyk plný překvapení. Takové Kinder Surprise pro dospělé programátory. Psaní aplikací v PHP je adrenalinovým sportem – i pojišťovny k tomu tak přistupují.

Naivní prosťáček nebo PHP laik by při pohledu na tento kód…

$arr = array();
$arr['XB@'] = 'La';
$arr[2089710047] = 'Trine';

unset($arr['XB@']);

// hádejte - co se vypíše?
print_r($arr);

…mohl mít pocit, že ví, co se vypíše. Ale ostřílený profesionál by nejprve kontroval otázkou: „V jaké verzi PHP?“ A rozhodně by se nespokojil s odpovědí „V pětce.“ Když se ptá na verzi, zajímá ho každá setinka. Protože třeba PHP 5.1.2 odstraní (unset) z pole jiný prvek, než verze 5.1.3. Stejně tak 4.4.2 versus 4.4.3.

To je péhápé. To se ví!

V aplikacích psaných pod PHP nestačí neuvádět minimální požadovanou verzi, vhodnější je zmínit výčet verzí, pod kterými dost možná i funguje. Třeba pokud používáte overloading…

class OverloadTest
{
	private $arr;

	public function __set($name, $value)
	{
		$this->arr[$name] = $value;
	}

	public function &__get($name)
	{
		return $this->arr[$name];
	}
}

$test = new OverloadTest();
$test->a['x'] = 'item';

…tak vězte, že nemusí korektně fungovat v PHP 5.2.0. Nicméně v předchozích i následujících verzích ano. Obzvláště pikantní je, že overloading miluje Zend Framework, takže kupříkladu jejich Zend_Session je pětdvanulkou taktéž ochromen. Doporučují upgradovat na 5.2.1, ovšem ani to nemusí být gór dobrý nápad. Proč?

Procházíte ve své aplikaci pole pomocí foreach a používáte funkci key? Pak vám nemusí fungovat v PHP 5.2.1 a novějších. Přitom v minulosti z podobných důvodů nefungovalo třeba Texy v PHP 4.4.1. Historie se opakuje.

Simply PHP

Programovat v Javě umí kdekdo. O ASP.NET ani nemluvě. Jen v PHP píši skuteční hrdinové. Renesanční bytosti. Fascinující a neskutečně trpěliví lidé. Trpěliví od slova trpět.

Třeba já.


DOMDocument::registerNodeClass je boží

A teď jedna dobrá zpráva ze světa zpracování XML dokumentů v PHP. S příchodem verze 5.2.0 se řady funkcí DOM rozšířily o nováčka jménem registerNodeClass(). K čemu však slouží? Dokumentace to úzkostlivě tají, dokonce i strýček Google cudně mlčí. Tak to se asi nedozvíme. Nejspíš půjde o nějakou hloupost…

The end

Ještě tu jste?

Tak to, ukecali jste mě, prozradím vám význam registerNodeClass. Tato funkce bezpochyby přináší svěží vítr do celé problematiky XML a nastavuje zrcadlo dosavadnímu systému zpracování. Možná více než slova napoví malý příklad.

Jednotlivé třídy DOM je možné v PHP dále rozšiřovat. Například takto si rozšíříme DOMElement o tři šikovné metody:

class dgxElement extends DOMElement
{

	/**
	 * Converts to a SimpleXMLElement
	 */
	public function toSimple()
	{
		return simplexml_import_dom($this);
	}


	/**
	 * Removes this element from the document
	 */
	public function remove()
	{
		$this->parentNode->removeChild($this);
	}

	/**
	 * Kills all the children. But kindly!
	 */
	public function childless($node)
	{
		$this->nodeValue = '';
	}

}

První z nich přetaví uzel na objekt SimpleXML, druhá jej zase odstraní ze stromu dokumentu. A třetí funkce je tuze šikovná, protože zbaví element všech potomků a to vskutku elegantně, bez složitých iterací.

Tohle všechno je velmi krásné, skoro až báječné, nebýt jednoho drobného detailu. Výhod dgxElement využijeme pouze tehdy, pokud element vytvoříme skriptem. V případě, že XML dokument naparsujeme ze souboru, tak bude tvořen pouze standardními objekty DOMElement, DOMAttr, atd.

Této vady na kráse si všimli i američtí inženýři. Po krátké poradě se rozhodli jednat a brzy přišli s řešením. Dali mu politicky korektní název registerNodeClass. A nyní opět pohovořme v jazyce PHP:

$doc = new DOMDocument();

// and now use dgxElement instead of DOMElement!
$doc->registerNodeClass('DOMElement', 'dgxElement');

// parse a XML file
$doc->loadXML('<root><blog name="La Trine"/></root>');

// returns true
echo ($doc->documentElement instanceof dgxElement);

// method toSimple() test:
$simple = $doc->documentElement->toSimple();

foreach ($simple as $blog)
	echo $blog['name'];


// bye bye element <blog>
$doc->documentElement->firstChild->remove();

echo $doc->saveXML(); //  <root/>

Paráda, ne?


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