Jmenné prostory a další novinky v PHP 5.3

Školení, která pořádám

Článek vyšel na serveru Root.cz.

PHP 5.3 obsahuje nejvíce změn ze všech minoritních verzí, které kdy PHP vydalo. Důvodem je skutečnost, že vývojáři PHP se rozhodli do této verze přesunout téměř všechny novinky původně plánované pro PHP 6 s výjimkou podpory Unicode. Vývoj trval velmi dlouho (původně bylo vydání naplánováno na podzim 2007) a byl poměrně bolestivý kvůli několika změnám v jeho průběhu.

Obsah článku

Jmenné prostory

Jmenné prostory dovolují oddělit jednotlivé části programu tak, že i když obsahují stejnojmenné identifikátory, nebudou vzájemně kolidovat. Potřeba jmenných prostorů se naléhavě ukázala při vydání PHP 5.1, které obsahovalo třídu date, která kolidovala s PEAR knihovnou Date.

Pro definici jmenného prostoru se používá nové klíčové slovo namespace. To lze použít buď v syntaxi se složenými závorkami nebo jednoduše ukončit středníkem – potom bude ve jmenném prostoru obsažen veškerý následující kód. Syntaxe je tedy stejná jako u málo známé konstrukce declare (tu je možno nově použít i k určení kódování souboru, bez speciální kompilace ale volba slouží zatím jen k získání dopředné kompatibility s PHP 6).

V jednom souboru lze definovat více jmenných prostorů, to se ale využije asi jen zřídka. Naopak stejný jmenný prostor může být nastaven ve více souborech, takže je možno zachovat konvenci jednoho souboru na jednu třídu a přitom všechny mohou sdílet stejný jmenný prostor.

Pokud chceme použít třídu, funkci nebo konstantu z cizího jmenného prostoru, můžeme tak učinit předřazením jeho identifikátoru a zpětného lomítka. Jako oddělovač se v průběhu vývoje používala čtyřtečka, kvůli nejednoznačnosti ale byla nahrazena (znamená A::f() statickou metodu ve třídě A nebo funkci ve jmenném prostoru A?). Uvažovalo se o šestitečce, zaslechl jsem návrh ležaté dvojtečky (..), ale zpětné lomítko nakonec vychází asi nejlépe. Jen jsem zvědav, jak si s ním poradí editory zdrojového kódu…

Jmenné prostory lze vytvářet hierarchicky oddělením jednotlivých komponent pomocí zpětného lomítka. Pro zjednodušení práce s takovýmito jmennými prostory lze použít klíčové slovo use které importuje jmenný prostor pod identifikátorem jeho poslední části. Za use je navíc možné přiřadit klauzuli as, která dovoluje zpřístupnit jmenný prostor pod aliasem.

<?php
// knihovna.inc.php
namespace Knihovna\Komponenta {
	class Trida {
		function __construct() {
			echo "OK\n";
		}
	}
}

// index.php
include "./knihovna.inc.php";
new Knihovna\Komponenta\Trida; // OK
use Knihovna\Komponenta\Trida as T;
new T; // OK
?>

Anonymní funkce

Některé funkce PHP přijímají tzv. callback parametry – funkce nebo metody, které se zavolají v určitém okamžiku. Jsou to např. funkce preg_replace_callback, array_map, ob_start nebo session_set_save_handler. PHP na místě těchto parametrů dovoluje předat globální funkci (předáním řetězce) nebo metodu nějakého objektu (předáním pole array($obj, 'metoda')). Funkci je možné vytvořit i přímo na místě pomocí create_function, s tím je ale spojeno potenciální bezpečnostní riziko (pokud bychom v definici funkce použili neošetřená data od uživatele) a výkonnostní hendikep, kód se navíc zapisuje do řetězce, takže v něm v editorech nefunguje ani zvýrazňování syntaxe.

PHP 5.3 dovoluje funkci definovat přímo na místě podobně, jako to umožňuje např. JavaScript – uvedením klíčového slova function, vynecháním názvu funkce a připojením parametrů a těla funkce.

<?php
$s = 'hello-world';
echo preg_replace_callback('~-([a-z])~', function ($match) {
    return strtoupper($match[1]);
}, $s);
?>

Uplatnění vidím hlavně u jednoduchých jednořádkových operací, kde je definice globální funkce samoúčelná. Užitečné je samozřejmě i to, že jednoúčelová funkce nepřekáží mezi obecně použitelnými funkcemi.

Anonymní funkce mohou používat i proměnné nadřazeného objektu. Na rozdíl od JavaScriptu ale nejsou vidět všechny proměnné, nýbrž jen ty, které explicitně uvedeme v klauzuli use:

<?php
$separator = "-";
$s = 'helloWorld';
echo preg_replace_callback('~[A-Z]~', function ($match) use ($separator) {
    return $separator . strtolower($match[0]);
}, $s);
?>

Anonymní funkce jsou interně reprezentovány jako objekty třídy Closure, která obsahuje metodu __invoke. Tuto metodu můžeme nadefinovat i u vlastních tříd, takže pak objekty odvozené z těchto tříd můžeme používat v kontextu funkce:

<?php
class Ahoj {
    function __invoke($s) {
        echo "Ahoj $s!\n";
    }
}
$ahoj = new Ahoj;
$ahoj("světe");
?>

Late static binding

PHP vyhodnocuje klíčové slovo self při kompilaci, takže následující kód vypíše Trida a nikoliv Potomek, jak by se možná na první pohled zdálo:

<?php
class Trida {
    static $a = 'Trida';
    static function f() {
        return self::$a;
    }
}
class Potomek extends Trida {
    static $a = 'Potomek';
}
echo Potomek::f(); // Trida
?>

K vyřešení tohoto problému dovoluje použít PHP 5.3 u statických metod místo self klíčové slovo static, které použitou třídu vyhodnotí až při běhu:

<?php
class Trida {
    static $a = 'Trida';
    static function f() {
        return static::$a;
    }
}
class Potomek extends Trida {
    static $a = 'Potomek';
}
echo Potomek::f(); // Potomek
?>

__callStatic

Pro přetížení volání statických metod přibyla magická metoda __callStatic. Ta se zavolá vždy, když se pokusíme staticky zavolat nedefinovanou metodu nějaké třídy. Stejně jako metoda __call dostane název metody a předané argumenty.

<?php
class Trida {
    static function __callStatic($nazev, $argumenty) {
        echo "$nazev\n";
    }
}
Trida::f();
?>

Zkrácený podmíněný výraz

Operátoru pro podmíněný výraz se obvykle říká ternární operátor, tento pojem ale vyjadřuje jen počet operandů (podobně jako u binárních a unárních operátorů) a je jen náhoda, že je v PHP jen jeden. Výraz ($if ? $true : $false) vrátí $true, pokud je podmínka $if splněna, jinak vrátí $false.

PHP 5.3 dovoluje prostřední část vynechat, takže výraz ($if ?: $false) vrátí $if, pokud je podmínka $if splněna, jinak vrátí $false. V JavaScriptu se stejně chová obyčejný operátor ||, který vrátí první pravdivou složku (v PHP tento operátor vrací true nebo false).

Zkrácený podmíněný výraz bohužel není obdobou funkce IFNULL a když je podmínka nedefinovaná, tak vyvolá chybu úrovně E_NOTICE stejně jako jakákoliv jiná práce s neinicializovanou proměnnou. Obdobu této funkce si můžeme sami vytvořit pro pole, kde je tato vlastnost nejčastěji potřeba:

<?php
function vychozi($pole, $klic, $vychozi) {
    return (isset($pole[$klic]) ? $pole[$klic] : $vychozi);
}
echo htmlspecialchars(vychozi($_GET, "search", ""));
?>

Příkaz goto

Po vášnivých debatách přibyl do PHP 5.3 příkaz goto. Slouží k přesunu na jiné místo kódu a uplatnění má především pro ošetření chybových stavů, osobně ho ale používat nebudu. Příkaz neumožňuje přeskočit dovnitř cyklu nebo příkazu switch.

<?php
function f($filename) {
    $fp = fopen($filename, 'r');
    $error1 = true;
    if ($error1) {
        goto clean;
    }
    return $fp;
    
    clean:
    fclose($fp);
    return false;
}
?>

Nowdoc

PHP dovoluje řetězec zapsat třemi způsoby:

  1. Do apostrofů, kde se nedosazují proměnné a zpětné lomítko má zvláštní význam jen před apostrofem a zpětným lomítkem.
  2. Do uvozovek, kde se dosazují proměnné a zpětné lomítko má zvláštní význam před celou řadou znaků.
  3. V tzv. heredoc syntaxi, kde lze navíc psát uvozovky bez ošetřování.

Heredoc jsem nikdy neměl v oblibě, protože jako jediná syntaktická konstrukce v PHP je platformově závislá (používá nativní ukončovač řádku) a oproti uvozovkám přináší jen nepatrnou výhodu (konec řádku lze totiž na rozdíl od mnoha jiných jazyků v PHP použít i uvnitř apostrofů a uvozovek). Heredoc naopak nelze použít na všech místech (např. při deklaraci vlastností třídy).

V PHP 5.3 přibyl čtvrtý způsob definice řetězce, tzv. nowdoc syntaxe. Ta je podobná jako heredoc, ale neinterpretují se proměnné a zpětné lomítko nemá žádný význam. Heredoc bez proměnných lze navíc nově použít i u statických proměnných a třídních vlastností a konstant. Pro nowdoc se používá syntaxe <<<'EOT', pro heredoc přibyla vedle stávající <<<EOT také alternativní syntaxe <<<"EOT".

<?php
echo <<<'EOT'
'$x:\'
EOT;
?>

Nový garbage collector

PHP používá pro uvolňování paměti jednoduchý algoritmus počítání referencí. Pokud se na sebe vzájemně odkazují dvě proměnné, vzniká cyklus, který tento algoritmus nedokáže nalézt, takže proměnné zůstanou v paměti až do konce běhu skriptu. To je problém hlavně u dlouhoběžících skriptů.

V rámci Google Summer of Code byl implementován algoritmus, který tyto cykly dokáže detekovat. Tento algoritmus je součástí PHP 5.3 a protože skripty mírně zpomaluje, dá se vypnout funkcí gc_disable nebo konfigurační direktivou zend.enable_gc.

MySQLnd

PHP ve všech extenzích pro práci s MySQL (MySQL, MySQLi, PDO) používá univerzální knihovnu libmysql. Tato knihovna má několik nevýhod, proto PHP 5.3 přichází s novou knihovnou MySQLnd, kterou lze použít místo libmysql. Knihovna navenek nijak nemění API (kromě několika přidaných funkcí) a je tedy zpětně kompatibilní se staršími verzemi.

Hlavním přínosem knihovny MySQLnd je to, že data získaná od MySQL serveru ukládá přímo do struktur, které používá PHP. Knihovna libmysql naproti tomu data ukládá do vlastních struktur, které je před použitím v PHP potřeba převést, což stojí jednak čas a jednak paměť (data jsou uložena dvakrát). MySQLnd navíc data stejně jako PHP kopíruje až když to je potřeba, takže přiřazení řádku s výsledkem do proměnné nestojí téměř žádnou paměť. Podle dostupných informací by se navíc paměť zabraná MySQLnd měla počítat do memory_limit, to se mi ale nepodařilo potvrdit a i zdrojový kód vypadá, že místo emalloc používá malloc, které se do paměťového limitu nezapočítává. Nakonec to je možná dobře, protože řada hostingů má nastavené nesmyslně nízké paměťové limity a náhlé započítání paměti zabrané knihovnou (která se zabírá stejně, jen to není tak snadno vidět) by řadu aplikací vyřadilo z provozu.

Knihovna přidává i několik funkcí:

<?php
$link1 = mysqli_connect();
$link2 = mysqli_connect();
$link1->query("SELECT 'test1'", MYSQLI_ASYNC);
$link2->query("SELECT 'test2'", MYSQLI_ASYNC);
$all_links = array($link1, $link2);
$processed = 0;
do {
    $links = $errors = $reject = array();
    foreach ($all_links as $link) {
        $links[] = $errors[] = $reject[] = $link;
    }
    if (!mysqli_poll($links, $errors, $reject, 1)) {
        continue;
    }
    foreach ($links as $link) {
        if ($result = $link->reap_async_query()) {
            print_r($result->fetch_row());
            mysqli_free_result($result);
            $processed++;
        }
    }
} while ($processed < count($all_links));
?>

Extenze MySQLi se přidává ke zbylým dvěma extenzím a zavádí možnost persistentního připojení. Nevznikla sice funkce mysqli_pconnect, ale názvu serveru u funkce mysqli_connect lze předřadit p:, což vytvoření persistentního připojení zajistí. Většina nevýhod persistentního připojení zůstala, ale alespoň ta hlavní díky automatickému zavolání funkce mysqli_change_user zmizela (při připojení se ukončí transakce, odemknou tabulky a odstraní dočasné tabulky).

Nové extenze

Phar
Asi nejpřínosnější novou extenzí v PHP 5.3 je Phar. Jedná se o knihovnu, která dokáže do jednoho souboru zabalit více skriptů a pracovat s nimi, jedná se tedy o podobný koncept jako JAR. Soubor začíná výkonným PHP kódem (takže takto vytvořené archivy jsou spustitelné i bez extenze), kterému následuje volání funkce __halt_compiler a obsah dalších souborů (které mohou být třeba i komprimované). Předchůdcem této extenze byla PEAR knihovna PHP Archive a o zařazení extenze do základní distribuce PHP se dlouho debatovalo, nakonec se tak ale stalo. Phar registruje i vlastní protokol, který dovoluje přímo přistupovat k souborům uvnitř archivu. Formát dovoluje do archivu zabalit i celou webovou aplikaci a namapovat ji na URL.
SQLite3
Extenze SQLite podporuje pouze druhou verzi této databáze, třetí verze byla podporovaná jen přes PDO. Pro uživatele, kteří si PDO neoblíbili, je nově k dispozici extenze SQLite3. SQLite se tedy řadí k databázi MySQL, se kterou lze v PHP pracovat také pomocí tří různých extenzí…
Fileinfo
Knihovna Fileinfo nahrazuje starší Mimetype a slouží k získávání informací o typu souboru na základě jejich typu.
Intl
Extenze Intl je nadstavbou knihovny ICU, kterou bude používat PHP 6 pro podporu Unicode. Nabízí porovnávací funkce respektující odlišnosti jednotlivých jazyků, formátování čísel a jazykových hlášek a další obraty nutné pro internacionalizaci aplikace.

Několik extenzí bylo také odstraněno.

Nové funkce

Konstanty

Změny v php.ini

Interní změny

V PHP 5.3 došlo také k několika interním změnám, které by měly vést ke zvýšení výkonnosti. Podle prvních testů se zdá, že se to podařilo.

Závěr

PHP 5.3 přináší neobvykle vysoké množství změn a dá se považovat téměř za plánované PHP 6 bez podpory Unicode a bez odstranění zastaralých obratů. Ještě více než kdy jindy se tedy nejspíš vyplatí s nasazením chvilku počkat, protože se jako již tradičně s nulovou verzí dají očekávat nějaké problémy (i když testovací fáze byla neobvykle dlouhá). Všechny změny se také postupně dostávají do oficiálního manuálu a jsou popsané v oficiální dokumentaci.

Jakub Vrána, Seznámení s oblastí, 1.7.2009, diskuse: 10 (nové: 0)

Diskuse

ikona david@grudl.com:

Q1 2008 není podzim 2007 ;)

ikona Jakub Vrána OpenID:

Jako že vydání bety :-).

mark56:

prosim at uz je z php plne objektovy jazyk ... kez by jednou, nejlepsi by bylo kdyby php vzniklo ve zcela nove verzi vytvorene tak rikajic z gruntu. neco jako php reborn :-) mohla by to byt ta sestka podle vas ?

ikona Karel Dytrych:

Rozhodne to bude krok kupredu ale neco ve stylu C# necekejte... Ale ta predstava se mi taky libi..

ikona Jakub Vrána OpenID:

Ne, šestka to nebude. Od šestky se dá očekávat zatím jen přidání podpory Unicode a odstranění zastaralých věcí.

A osobně jsem rád, že PHP zůstane takto univerzální.

Nishkam:

Ja taky :)

optik:

Obávám se, že se tak se nikdy nestane, to si PHP vývojáři prostě nemohou dovolit.

Osobně mi vadí to, že čím dál více kódu v core PHP se dělá přepisem PHP prototypu do C. To je podle mě strašně neefektivní z hlediska vývoje a udržování do budoucna, viz Phar. Spíš bych byl proto, aby se předělal virtuální stroj na něco s JITem + nějaké rychlé malé jádro a pak základní knihovna přímo v PHP. Tak, jak to dělají v nějaké té alternativní Ruby variantě. To by pak vývoj mohl jít mnohem rychleji. Wrappery pro Cčkové knihovny mít v core je taky strašná brzda, měli by to všechno vyndat do PECLu.

JimX:

nechci slovickarit ... ale jste si jisty, ze vite, jak vypada plne objektovy jazyk? Ne, Java ani C# to neni :)

Matej:

Pokud chces pouzivat robusni, objektovy jazyk, nepouzivej PHP. Nechtej neco od neceho, co k tomu neni urceno. Easy...

Visitor:

Souhlas ale nyní mi vadí, že je PHP někde mezi. Každý se tlačí do objektů a zároveň v PHP pořádně nefungují a v každé verzi ještě jinak.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 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.