phpMinAdmin – abstrakce databázové extenze
phpMinAdmin umí používat tři extenze pro práci s databází: MySQLi, MySQL a PDO. Nad těmito extenzemi je vytvořena jednoduchá abstrakce, která dovoluje se všemi extenzemi pracovat jednotným způsobem.
MySQLi
Za základ je zvoleno MySQLi, protože disponuje nejvíce funkcemi:
<?php class Min_MySQLi extends MySQLi { function Min_MySQLi() { $this->init(); } function connect($server, $username, $password) { return @$this->real_connect( (strlen($server) ? $server : ini_get("mysqli.default_host")), (strlen("$server$username") ? $username : ini_get("mysqli.default_user")), (strlen("$server$username$password") ? $password : ini_get("mysqli.default_pw")) ); } function result($result, $field = 0) { $row = $result->fetch_array(); return $row[$field]; } } ?>
V potomku třídy mysqli je přepsán konstruktor, kde je provedena pouze inicializace a je přidána metoda connect
, která se připojí k serveru s předanými nebo výchozími parametry. Metoda result
, která vrátí dané pole prvního řádku výsledku, by měla být spíše součástí třídy mysqli_result, extenze MySQLi tuto třídu ale bohužel nedovoluje snadno rozšiřovat, proto je přímo součástí hlavní třídy.
MySQL
<?php class Min_MySQL { var $_link, $_result, $server_info, $affected_rows, $error; function connect($server, $username, $password) { $this->_link = @mysql_pconnect( (strlen($server) ? $server : ini_get("mysql.default_host")), (strlen("$server$username") ? $username : ini_get("mysql.default_user")), (strlen("$server$username$password") ? $password : ini_get("mysql.default_password")), 131072 // CLIENT_MULTI_RESULTS for CALL ); if ($this->_link) { $this->server_info = mysql_get_server_info($this->_link); } return (bool) $this->_link; } function select_db($database) { return mysql_select_db($database, $this->_link); } function query($query) { $result = mysql_query($query, $this->_link); if (!$result) { $this->error = mysql_error($this->_link); return false; } elseif ($result === true) { $this->affected_rows = mysql_affected_rows($this->_link); return true; } return new Min_MySQLResult($result); } function multi_query($query) { return $this->_result = $this->query($query); } function store_result() { return $this->_result; } function next_result() { return false; } function result($result, $field = 0) { return mysql_result($result->_result, 0, $field); } function escape_string($string) { return mysql_real_escape_string($string, $this->_link); } } class Min_MySQLResult { var $_result, $_offset = 0, $num_rows; function Min_MySQLResult($result) { $this->_result = $result; $this->num_rows = mysql_num_rows($result); } function fetch_assoc() { return mysql_fetch_assoc($this->_result); } function fetch_row() { return mysql_fetch_row($this->_result); } function fetch_field() { $row = mysql_fetch_field($this->_result, $this->_offset++); $row->orgtable = $row->table; $row->orgname = $row->name; $row->charsetnr = ($row->blob ? 63 : 0); return $row; } function free() { return mysql_free_result($this->_result); } } ?>
V abstrakci nad extenzí MySQL stojí za pozornost několik věcí:
- Abstrakce není kompletní, implementovány jsou pouze metody, které používá phpMinAdmin.
- Aby se daly volat uložené procedury, tak se jako čtvrtý parametr funkci mysql_pconnect předává hodnota
CLIENT_MULTI_RESULTS
, pro kterou ale v PHP není definovaná konstanta, proto se místo ní používá přímo číselná hodnota. - Extenze MySQL nepodporuje vícenásobné výsledky, metoda
store_result
proto vrátí pouze první výsledek a metodanext_result
vrátí vždy false. - Funkce mysql_fetch_field bohužel nevrací informaci o původu vráceného sloupce, proto při získání sloupce nebo tabulky přes alias nefungují v obecném SQL příkazu s touto extenzí odkazy na editaci záznamu. Stejně tak funkce nevrací informaci o sloupcích typu binary nebo varbinary, proto se tyto sloupce zobrazují jako prosté řetězce.
PDO
<?php class Min_PDO_MySQL extends PDO { var $_result, $server_info, $affected_rows, $error; function __construct() { } function connect($server, $username, $password) { set_exception_handler('auth_error'); // try/catch is not compatible with PHP 4 parent::__construct("mysql:host=$server", $username, $password); restore_exception_handler(); $this->setAttribute(13, array('Min_PDOStatement')); // PDO::ATTR_STATEMENT_CLASS $this->server_info = $this->result($this->query("SELECT VERSION()")); return true; } function select_db($database) { return $this->query("USE " . idf_escape($database)); } function query($query) { $result = parent::query($query); if (!$result) { $errorInfo = $this->errorInfo(); $this->error = $errorInfo[2]; return false; } $this->_result = $result; if (!$result->columnCount()) { $this->affected_rows = $result->rowCount(); return true; } $result->num_rows = $result->rowCount(); return $result; } function multi_query($query) { return $this->query($query); } function store_result() { return ($this->_result->columnCount() ? $this->_result : true); } function next_result() { return $this->_result->nextRowset(); } function result($result, $field = 0) { $row = $result->fetch(); return $row[$field]; } function escape_string($string) { return substr($this->quote($string), 1, -1); } } class Min_PDOStatement extends PDOStatement { var $_offset = 0, $num_rows; function fetch_assoc() { return $this->fetch(2); // PDO::FETCH_ASSOC } function fetch_row() { return $this->fetch(3); // PDO::FETCH_NUM } function fetch_field() { $row = (object) $this->getColumnMeta($this->_offset++); // table and charset is not available return $row; } function free() { // $this->__destruct() is not callable } } ?>
Podpora přes extenzi PDO je nejslabší. Opět několik poznámek:
- Extenze PDO o chybách informuje pomocí výjimek. Pokud by se definice třídy vkládala jen v PHP 5, tak by nebyl problém použít blok try/catch, kvůli koncepci phpMinAdmina je ale vše v jednom souboru a musí být funkční i v PHP 4. Dalo by se to vyřešit jedním zavoláním funkce eval, tu však nemám v oblibě, proto jsem raději zvolil nastavení funkce pro ošetření výjimek zavoláním funkce set_exception_handler.
- PDO dovoluje nastavit třídu reprezentující výsledek, k čemuž slouží hodnota
PDO::ATTR_STATEMENT_CLASS
předaná metodě PDO::setAttribute. Tento způsob zápisu konstanty je ale v PHP 4 opět zakázaný, proto je místo konstanty použita přímo číselná hodnota. - PDO nemá zvláštní metodu pro výběr databáze. Ta se dá předat konstruktoru, to se mi ale nehodilo, proto jsem zvolil prosté zavolání MySQL příkazu USE. Funkce
idf_escape
ošetří řetězec tak, aby se dal použít na místě identifikátoru (obalí ho zpětnými apostrofy a ošetří speciální znaky). - Metoda PDOStatement::getColumnMeta je bohužel mimořádně chudá – neposkytuje informace ani o tabulce ani o kódování sloupce.
- O uvolnění paměti se stará destruktor třídy PDOStatement, ten však bohužel zevnitř třídy nejde zavolat.
Diskuse
Jakub:
Mám na vás dva dotazy:Proč používáte strlen("$server$username") a ne strlen($username) ?
V manuálu jsem našel že funkce set_exception_handler() je v PHP dostupná až od verze 5
Jinak články o phpMinAdmin jsou výborné. Jen tak dál.
Jakub Vrána
:
Nechceme, aby se na cizím serveru použilo defaultní jméno a heslo.
set_exception_handler() je pochopitelně dostupná až v PHP 5, protože v PHP 4 výjimky nejsou. Jde o to, že to je volání normální funkce, které je syntakticky v pořádku (na rozdíl od try/catch) i v PHP 4. Kód se na to místo nikdy nedostane, protože PDO je k dispozici až v PHP 5, takže k volání neexistující funkce nedojde.


Diskuse je zrušena z důvodu spamu.

