Hlavní navigace

Jmenné prostory a další novinky v PHP 5.3

1. 7. 2009
Doba čtení: 12 minut

Sdílet

PHP 5.3 obsahuje nejvíce změn ze všech minoritních verzí, které kdy PHP vydalo. Důvodem je, ž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 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é konstrukcedeclare (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é slovouse 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.

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

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

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

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:

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:

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.

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

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.

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

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

  • Jednak jednoduchou mysqli_fetch_all , která efektivně získá všechny řádky výsledku, pokud je potřebujeme předat další vrstvě (např. šabloně)
  • Dále mysqli_get_connection_stats , která dovoluje získat statistické informace o připojení
  • A takémysqli_poll a mysqli_reap_async_query , pomocí kterých lze pokládat asynchronní dotazy – jejich výhoda spočívá v tom, že nemusíme čekat na odpověď z jednoho serveru a ještě než dorazí, můžeme položit dotaz dalšímu serveru
$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 funkcemysqli_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í funkcemysqli_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.

root_podpora

Nové funkce

Konstanty

  • Pro zastaralé konstrukce byla zřízena konstanta E_DEPRECATED a její uživatelská varianta E_USER_DEPRECATED. (Co by to bylo za novou verzi PHP bez nové chybové úrovně, že?) Chybová úroveň E_STRICT zůstává vyčleněna z E_ALL, i když některé zdroje tvrdí opak.
  • Místo obligátního dirname(__FILE__), které se používá především při vkládání skriptů, lze nově použít konstanta  __DIR__.
  • Aktuální jmenný prostor je k dispozici v konstantě  __NAMESPACE__.
  • Globální konstanty lze kromě tradiční funkcedefine nově definovat také pomocí klíčového slova const stejně jako u konstant tříd.

Změny v php.ini

  • Vznikla nová konfigurační direktiva request_order , která se používá pro určení pořadí hodnot v poli $_REQUEST. Pokud není nastavena, použije se hodnota variables_order .
  • Odesílání e-mailů lze nově logovat díky konfigurační direktivě mail.log .
  • Byla odstraněna konfigurační direktiva zend.ze1_compatibility_mode , která sloužila pro emulaci chování objektů podle PHP 4. Neznám nikoho, kdo by si ji troufl používat…
  • Do php.ini lze přidávat sekce, které dovolují upravit chování v závislosti na doméně nebo cestě k souboru.
  • Pro CGI byla přidaná podpora adresářové konfigurace (obdoba .htaccess). Slouží k tomu konfigurační direktiva user_ini.filename, jejíž výchozí hodnota je  .user.ini.
  • Na extenze se lze odkazovat pomocí absolutní cesty.
  • Dodávané konfigurační soubory byly přejmenovány na php.ini-development a php.ini-production a prodělaly několik změn, z nichž asi nejdůležitější je vypnutímagic_quotes_gpc i ve vývojářské konfiguraci. Výchozí hodnotasession.use_only_cookies byla změněna na zapnuto.

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.

  • Pokud to je možné, tak se hodnota konstant v PHP 5.3 dosazuje už při kompilaci a konstanty se ukládají do paměti určené pouze pro čtení.
  • Mělo by dojít ke zrychlení přístupu k proměnné $this.
  • Místo parseru flex se pro zpracování zdrojových kódů nově používá re2c.

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.

Byl pro vás článek přínosný?

Autor článku

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.