Co mi vadí na PDO
Školení, která pořádám
Extenze PDO, která se od PHP 5.1 dá použít pro práci s databázemi, se mi v zásadě líbí, přesto mám několik výhrad.
Líbí se mi, že jde o nízkoúrovňové řešení, takže s ním není spojena žádná zásadní režie stejně jako u tradičních extenzí (jako např. MySQL). Líbí se mi vázání proměnných, které zjednodušuje zabezpečení aplikace, a PDO zároveň nabízí jejich efektivní řešení (vázání proměnných na straně serveru je za určitých okolností pomalé, proto ho PDO dokáže emulovat). Zásadní výhrady nemám ani proti API.
Vadí mi několik věcí, které lze vyřešit pomocí dědičnosti:
- Vadí mi, že neexistuje jedna metoda pro položení dotazu s navázanými proměnnými. Pro průchod připraveným dotazem je potřeba vykonat tři příkazy (
query
, execute
a fetch
). To jsem vyřešil už v předchozím článku o PDO.
- Vadí mi, že neexistuje metoda pro ošetření řetězce jako identifikátoru. To znesnadňuje vytváření knihoven vyšší úrovně, které by PDO využívaly. Samozřejmě si člověk může takovou metodu napsat sám, každý ovladač ale používá jiný způsob, který navíc může záviset na konfiguraci databázového serveru.
Přestože lze tyto nedostatky vyřešit vlastní třídou odvozenou ze základní PDO, tak to znesnadňuje psaní ukázek využívajích PDO, které si nemohou dovolit předpokládat existenci tohoto potomka.
Nemožnost vázání polí
Zásadní problém, který mi na PDO vadí, je absence vázání polí. To je potřeba např. u dotazů typu id IN (1, 2, 3)
. Vyzkoušel jsem několik způsobů, jak to obejít, a žádný se mi nelíbí:
$pdo->query("id IN (?)", array($pole))
není podporované.
$pdo->query("id IN (?)", array(implode(",", $pole)))
pochopitelně nefunguje, protože to vytvoří dotaz id IN ('1,2,3')
.
$pdo->query("FIND_IN_SET(id, ?)", array(implode(",", $pole)))
nedokáže využít klíč.
$pdo->query("id IN (" . substr(str_repeat(",?", count($pole)), 1) . ")", $pole)
modifikuje dotaz a ještě víc se zkomplikuje, když se mají navázat i další proměnné.
$pdo->query("id IN (" . implode(",", array_map('intval', $pole)) . ")")
vůbec nevyužívá vázání proměnných.
Zvažoval jsem dokonce, že bych zástupný symbol pro pole v dotazu vyhledal, to ale není tak jednoduché, protože např. uvnitř řetězců se zástupné symboly interpretovat nesmějí. To by znamenalo dotaz ručně parsovat, což by byla duplikace práce PDO.
Závěr
Všechny zmíněné problémy řeší pokročilejší knihovny pro práci s databází, zejména dibi. Jsem také zvědav, co přinese PDO 2, zatím jsem zaregistroval pouze spory ohledně CLA.
Diskuse
Mám za to, že omezení PDO je dané primárně tím, co dovolují databáze samotné - nevím o tom, že by to některá z nich podporovala. PDO by to _muselo_ řešit emulací, ale to by odstraňovalo hlavní výhodu prepared statements - jejich rychlost (odpadá syntaktická analýza).
Zdar
jednoparametricky prepared statement pro vzor neco IN (?) dost dobre nejde. Pokud vim, tak urcita shoda mezi databazemi je pouze ve skalarnich parametrech. MSSQL podporuje typ tabulka, PostgreSQL a ORACLE zase pole. Navic PostgreSQL zapis promenna in (pole) vyhodi jako syntakticky nespravny - protoze se snazi porovnavat pole a nikoliv skalar v poli - viz syntax: skalar IN (skalar, skalar, ...). Musi se pouzit syntaxe skalar = ANY(pole). V PostgreSQL se v teto situaci pouziva parsovani pole a pretypovani na cilovy typ
WHERE a = ANY (string_to_array($1,',')::int[])
tato konstrukce je bezpecna vuci sql injection a i rychla. Pokud by PDO preneslo i pole, pak muzu vynechat parsovani pole - ale to snad jeste nejde).
Univerzální řešení by hodnotu proměnné mohlo dosadit už na straně PHP. Druhou možností by bylo expandovat jeden zástupný symbol na více a poslat jednotlivé prvky pole.
Expandovat jeden placeholder na vicero v podstate nejde. Pocet parametru je zafixovan v provadecim planu. Tady bych se i branil proti vlastni inteligenci PDO. Od takoveto vrstvy ocekavam, ze posle do databaze presne to, co ji urcim. Pokud mozno s minimalnimi, nejlepe zadnymi upravami. V prepade, kdy je nejaka vrstva inteligentni, a upravuje SQL prikazy, tak se hrozne spatne hledaji chyby. Je ale fakt, ze pokud databaze nepodporuje pole, tak podminka WHERE neco IN (vycet) se pomoci Prepared Statements implementuje dost neohrabane. V MySQL mne napada pouzit funkci find_in_set, tj nahradit podminku za
WHERE find_in_set(sloupec, ?) <> 0
Ale planner dostane dost na frak :(
Všechny zmíněné problémy taktéž řeší i Zend_Db, což mi připomíná, že bych mohl napsat článek, co mi vadí na dibi :)
To bych si velice rád přečetl. Až článek bude, tak sem dej prosím odkaz.
Toby77:
Podle této starší rfc stránky byl operátor IN (?) pro pole odmítnut v podstatě z důvodů, které už popsal Pavel Stěhule.
http://wiki.php.net/rfc/pdov1
Megaloman:
"Vadí mi, že neexistuje jedna metoda pro položení dotazu s navázanými proměnnými."
Ale ona je jen jedna :-)
Chyba je spíš v tom, že nerozlišuješ mezi prepared a jednorázovými dotazy. Jak by potom PDO rozlišovalo mezi prepared dotazy, klasickými dotazy s result setem a klasickými dotazy vracejícími počet řádků?
S prepared statementem vždy souvisí nějaká režie na straně serveru navíc, ale může dojít i k úspoře přenosu po síti díky použití binárního protokolu client/server. Proto nemusí být vždy vhodné používat prepared statements pro jednorázově vykonávané dotazy (jednorázové z pohledu života jednoho připojení k DB).
Proto je principiálně špatné i řešení rozšíření v předchozím článku o PDO, kde jsou přepsány metody query a exec tak, aby přijímaly parametry. Tím netvrdím, že v praxi není lepší.
Sám toto rozšíření používám, ale napadlo mě metody query a exec přepsat buď na variantu, která parametry v $statement nahradí a pak výsledek předá parent::query, nebo na variantu, kde bude nepovinný třetí parametr s volbou režimu (Jakubův prepared / klasický PDOvský).
Otázkou ovšem je, zda by nebyla emulace parametrů v query mnou navrženým způsobem delší, než režie s prepared statementem. Ale i tak by se našla situace, kdy by to mělo svůj smysl (víceméně čistě teoretická).
Nějak mě nenapadá, jak bych otestoval časovou náročnost přípravy prepared dotazu.
Celou dobu se tu bavím o MySQL.
"To by znamenalo dotaz ručně parsovat, což by byla duplikace práce PDO."
Používám verzi IN(implode(..., ale čekám na nápad, jak to přepsat. Třeba před spuštěním by se mohl odpovídající '?' mimo řetězec nahradit za str_repeat('?, ', count($pole) - 1) . '?' a array($pole) za $pole, samozřejmě s ošetřením situací, kdy je v poli více parametrů.
I když jde o duplikaci práce PDO, ale když použiju
<?php
$pdo->query("id IN (" . implode(",", array_map('intval', $pole)) . ")");
?>
, tak stejně dotaz ručně parsuju.
Kit:
<?php
$pole=array(1,2,3);
$stmt=$pdo->prepare("id IN (?".str_repeat(",?",sizeof($pole)-1).");";
$stmt->execute($pole);
?>
Diskuse je zrušena z důvodu spamu.