PDO
Asi nejvýznamnější novinkou v PHP 5.1 je rozšíření PDO umožňující jednotným způsobem pracovat s rozličnými databázemi. Na rozdíl od knihoven ADOdb nebo PEAR DB se nesnaží vytvořit kompletní abstrakci skrývající rozdíly mezi jednotlivými databázemi, ale naopak k nim poskytuje nízkoúrovňový přístup se všemi jejich specifickými vlastnostmi pomocí jednotného rozhraní. Používání PDO je samozřejmě doporučováno a přestože klasická rozšíření pro práci s databázemi zatím nejsou označena jako zastaralá (v některých případech obsahují širší funkčnost), jednou k tomu pravděpodobně dojde. Pokud chcete pracovat např. s databází SQLite 3, jiná možnost už ani neexistuje, pro práci s MySQL jsou naopak možnosti hned tři – klasické MySQL, MySQLi pokrývající funkčnost verze 4.1 a právě PDO.
Prvním příkazem při práci s databází je inicializace – u databázových serverů to je připojení k nim, u SQLite otevření souboru. V PDO k tomu slouží konstruktor, kterému se předává DSN a případně uživatelské jméno a heslo. Překvapuje mě, že některým databázím se jméno a heslo předává přímo v DSN a jiným jako další parametry, ale asi to je dáno způsobem, jakým se s databází komunikuje.
<?php
$pdo = new PDO("mysql:dbname=test");
?>
Trochu mě také zaskočilo množství funkcí pro pokládání dotazů. Zatímco rozšíření MySQL nabízí jedinou funkci mysql_query, v PDO jsou k dispozici hned tři:
<?php
// vrátí počet ovlivněných řádek
echo $pdo->exec("INSERT INTO test (jmeno) VALUES ('Franta')");
// vrátí PDOStatement, který lze přímo procházet
foreach ($pdo->query("SELECT * FROM test") as $row) {
echo "$row[jmeno]\n";
}
// vázání proměnných
$result = $pdo->prepare("SELECT * FROM test WHERE jmeno LIKE ?");
$result->execute(array('Franta'));
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
echo "$row[jmeno]\n";
}
?>
Za pozornost stojí, že výsledky lze procházet jak tradičním while ($row = $result->fetch())
, tak přímo konstrukcí foreach. Myšlenka vázání proměnných se mi líbí, ale protože často kladu dotaz s jedinou sadou parametrů, postrádám možnost v jedné funkci určit jak SQL dotaz, tak předané parametry. Za účelem vyřešení těchto drobných nedostatků jsem vytvořil skromné rozšíření:
<?php
/// rozšíření třídy PDO, které umožňuje jednotné předávání uživatelského jména a hesla a předávání spouštěcích parametrů metodám query() a exec()
class MyPDO extends PDO {
public function __construct($dsn, $username = "", $password = "", $driver_options = array())
{
switch (preg_replace('~:.*~', '', $dsn)) {
case 'firebird': return parent::__construct("$dsn;User=$username;Password=$password", "", "", $driver_options);
case 'odbc': return parent::__construct("$dsn;UID=$username;PWD=$password", "", "", $driver_options);
case 'pgsql': return parent::__construct("$dsn user=$username password=$password", "", "", $driver_options);
default: return parent::__construct($dsn, $username, $password, $driver_options);
}
}
public function query($statement, $input_parameters = array())
{
$result = parent::prepare($statement);
return ($result && $result->execute($input_parameters) ? $result : false);
}
public function exec($statement, $input_parameters = array())
{
$result = parent::prepare($statement);
return ($result && $result->execute($input_parameters) ? $result->rowCount() : false);
}
}
?>
Při použití vázání proměnných je záhodno vypnout direktivu magic_quotes_gpc, jinak se do databáze uloží nadbytečná zpětná lomítka.
Vázání proměnných je velice užitečný koncept zvyšující bezpečnost skriptů, ale doporučuji se obloukem vyhnout jeho implementaci funkcemi PDOStatement::bindParam a PDOStatement::bindColumn. Funkce by měly mít jasně definovaný vstup v podobě parametrů a výstup v podobě návratové hodnoty. V odůvodněných případech lze výstup předávat i skrze parametry, ale rozhodně je lepší se uvnitř funkce vyhnout používání globálních proměnných a obzvláště jejich modifikaci. Zmíněné dvě funkce způsobí, že různé PDO funkce budou měnit hodnotu nastavených globálních proměnných, což může u trochu složitějších skriptů vést k nepříjemným a těžko odhalitelným chybám.
Do PHP 5.1.0 se bohužel přes všech 6 RC dostala chyba (v CVS už opravená) způsobující, že exec v MySQL vrací vždy pouze 0 nebo –1. Představené rozšíření používá metodu rowCount, která touto chybou netrpí.
Pro vytváření složitějších rozšíření se může hodit atribut PDO::ATTR_STATEMENT_CLASS, který dovoluje nastavit potomka třídy PDOStatement, který následně bude vytvářen místo PDOStatement. Hodnota parametru je pole array("Název třídy", array("parametry konstruktoru"))
. Použití bez přidání jakékoliv funkčnosti je tedy následující:
<?php
class MyPDOStatement extends PDOStatement {
private function __construct() { }
}
$pdo = new PDO("mysql:dbname=test");
$pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array("MyPDOStatement", array()));
?>
Práce s datem
V PHP 5.1 byla z velké části přepracována práce s datem, díky čemuž např. začal fungovat negativní timestamp i pod Windows, které ho nativně nepodporují. Funkce by se navenek měly chovat stejně, ale nově respektují časové pásmo nastavené direktivou date.timezone nebo novou funkcí date_default_timezone_set. Doposud bylo možné časové pásmo ovlivnit pouze proměnnou prostředí TZ a k práci s časem se používaly systémové knihovny, což způsobovalo nekompatibilitu mezi různými platformami. Nová je i funkce strptime převádějící řetězec na datum. Na rozdíl od funkce strtotime je u ní možné nastavit formát data.
Nově je také definováno několik konstant pro pohodlnější generování standardních formátů dat. Tyto konstanty jsou umístěny do třídy date, jejíž název bohužel koliduje s knihovnou PEAR Date, která existuje už tři a půl roku a která je poměrně hojně používaná. Je to o to smutnější, že třída date zatím nedefinuje žádné metody (byť je to v plánu). Pokud tuto nebo jinou stejnojmennou třídu používáte, můžete do konfigurace přidat disable_classes = date
.
Nové schopnosti regulárních výrazů
Knihovna PCRE, která se v PHP používá pro kontrolu Perl-kompatibilních regulárních výrazů, je průběžně vylepšována. V dokumentaci například chyběla zmínka o tom, že konstrukci \x
je možné v UTF-8 režimu (při použití přepínače u
) používat ve tvaru \x{...}
, kde uvnitř složených závorek je hexadecimální číslo Unicodového znaku.
Do PHP 4.4.0 a 5.1.0 byla zakompilována verze s rozšířenou podporou Unicode, takže je možné kontrolovat rozličné vlastnosti Unicodových znaků. Například \pL
strefí jakékoliv písmeno (s diakritikou i bez).
__halt_compiler
Poměrně nenápadnou a asi zřídka používanou novinkou bude nová řídící struktura __halt_compiler, která zastaví zpracování PHP instrukcí. Hodí se především pro vytváření skriptů přímo obsahujících data, např. instalačních programů. Na rozdíl od konstrukce exit, která ukončí zpracování skriptu, se za konstrukcí __halt_compiler nepokračuje ani v parsování, takže následující data nemusí vyhovovat PHP syntaxi. Na začátek dat odkazuje konstanta __COMPILER_HALT_OFFSET__.
<?php
// jednoduchý instalátor
$fp = fopen(__FILE__, "r");
fseek($fp, __COMPILER_HALT_OFFSET__);
while (($filename = trim(fgets($fp)))) {
$length = trim(fgets($fp));
file_put_contents($filename, fread($fp, $length));
}
fclose($fp);
__halt_compiler();a.php
18
<?php
echo "a";
?>
Další novinky
- PHP 5.1 také pokračuje v opravách práce s referencemi. Změny jsou poměrně rozsáhlé a bohužel i poněkud chaotické. Tak třeba příklad z dokumentace proběhne v PHP 4 včetně 4.4 bez jakéhokoliv varování, v PHP 5.0.5 způsobí fatální chybu, ale v PHP 5.1.0 pouze striktní varování. Ne nadarmo je těmto změnám věnována téměř polovina oficiálních poznámek k aktualizaci.
- Konstrukce instanceof, is_subclass_of a catch už také nevolají magickou funkci __autoload, takže jsou použitelné i bez volání funkce class_exists.
- Interní funkce, které používají standardní parsování parametrů, nyní proběhnou i v případě, že je na místě číselného parametru uveden řetězec, který sice je převoditelný na číslo, ale obsahuje i nečíselné znaky. V tomto případě je vyvolána chyba úrovně E_NOTICE. Dřívější verze funkci nevykonaly a vypsaly E_WARNING.
- Některé funkce dostaly nové parametry rozšiřující jejich schopnosti, z těch nejpoužívanějších zmíním alespoň file_get_contents (parametry
offset
amaxlen
), preg_replace a preg_replace_callback (parametrcount
), explode (negativní hodnota parametrulimit
) a substr_count (parametryoffset
alength
). - Z PHP 5.0.5 byla odstraněna funkce php_check_syntax, která kromě kontroly syntaxe modifikovala také tabulku funkcí. Místo této funkce lze použít tradiční
`php -l`
nebo novou funkci runkit_lint, pro tu je ale potřeba povolit samostatné rozšíření. - V INI souborech se lze odkazovat na již definované direktivy pomocí
${direktiva}
. - PHP 5.1 nově podporuje kromě Basic HTTP autentizace také bezpečnější variantu Digest, takže již není třeba ošetřovat tuto variantu uživatelskými skripty. Verze PHP 5.1.0 se bohužel chová jinak v Apache 1 a 2, takže je o něco hůř použitelná. Tato chyba už je v CVS opravena.
- K magickým metodám __get a __set přibyly logické doplňky __isset a __unset sloužící k testování existence, resp. ke zrušení virtuálních vlastností třídy.
- Funkce var_export nově umožňuje exportovat i třídy. Využívá k tomu novou magickou metodu __set_state.
Závěr
I přes velkou snahu QA týmu vydat verzi, která by byla bez chyb, se to bohužel nepodařilo. Kromě zmíněných chyb v PDO::exec a Digest autentizaci bude v PHP 5.1.1 možná odstraněn nebo přejmenován konfliktní objekt date. Kontroverzní upozornění na zastaralost {}
pro přístup ke znakům řetězce naštěstí mezi RC 6 a ostrou verzí zmizelo.
Přestože tedy PHP 5.1 přináší poměrně užitečné novinky, vyplatí se s přechodem myslím počkat až na PHP 5.1.1. Vzhledem k tomu, že současná verze obsahuje i bezpečnostní záplaty, se vydání opravy doufejme příliš neprotáhne.