Proč nefungují funkce mysql_*?

Na internetu se povaluje spousta kódů využívajících PHP rozšíření ext/mysql a těch se chytne mnoho začátečníků, což je motiv pro napsání tohoto článku. Pokusím se ve dvou kapitolách popsat, proč rozšíření ext/mysql nepoužívat a jaké jsou ekvivalentní funkce z rozšíření ext/mysqli.

Nejprve je třeba si ujasnit, které rozšíření je které. ext/mysql označuje funkce začínající mysql_, nikoliv samotnou databázi MySQL (což je odlišný a stále vyvíjený produkt). ext/mysqli (případně pouze MySQLi – MySQL improved) naproti tomu označuje novější rozšíření pro práci s MySQL databází, které je aktivně podporované a vyvíjené (jeho funkce začínají mysqli_, ale má i objektové rozhraní). Další podporované rozšíření pro práci s MySQL databází je PDO_MySQL, o kterém si můžete přečíst u Str4wberryho.

Nevýhody rozšíření ext/mysql

  • nepodporuje veškerou dostupnou funkčnost
  • již se nevyvíjí (pouze v režimu údržby)
  • v PHP 5 je označeno jako zastaralé (tudíž každé jeho použití vyhazuje chybu) a v PHP 7 je odstraněno (tudíž skripty na něm založené nebudou fungovat)

Pokud jste programátor–laik, pravděpodobně pro vás bude pádný pouze poslední důvod, protože do situace, kdy narazíte na nepodporu „pokročilé“ funkčnosti se asi nedostanete. Zatímco na PHP 7, ve kterém toto rozšíření již neexistuje, budete narážet čím dál častěji. (Pro pořádek dodám, že ext/mysql je stále možné ručně doinstalovat.)

Určit, jak urgentní je situace pro vás, vám pomohou chybové hlášky, které vám PHP vypisuje:

  • Deprecated: The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead
    • používáte PHP 5 a skripty (prozatím) budou normálně fungovat
  • Call to undefined function mysql_*
    • používáte PHP 7, rozšíření ext/mysql již není dostupné a skripty musíte přepsat

I pokud vaše skripty stále fungují, doporučuji co nejdříve začít s přechodem na ext/mysqli nebo PDO_MySQL, dokud na to máte klid a čas.

Rozšíření ext/mysql taktéž neobsahuje některé funkce, které zjednodušují práci s pokročilejšími vlastnostmi MySQL databáze. Tady jsou ty nejdůležitější rozdíly ext/mysql vs. ext/mysqli a PDO_MySQL:

ext/mysql ext/mysqli PDO_MySQL
od verze PHP 2.0 5.0 5.1
do verze PHP 5.6 stále aktuální stále aktuální
stav vývoje režim údržby aktivní aktivní
životní cyklus v PHP 5 zastaralé, odstraněné v PHP 7 aktivní aktivní
objektové rozhraní ne ano ano
procedurální rozhraní ano ano ne
asynchronní dotazy ne ano ne
API pro prepared statements ne ano (pouze server-side) ano
API pro stored procedures ne ano ano
API pro multiple statements ne ano většina
API pro transakce ne ano ano
podpora veškeré funkcionality MySQL 5.1+ ne ano většina

(Tabulka je převzata z php.net a upravena.)

Rychlý začátek s MySQLi

MySQLi rozšíření podporuje jak objektové, tak procedurální rozhraní. Zaměřím se na to objektové, je přehlednější (a při vývoji je objektové programování lepší, aplikace se pak lépe rozšiřuje). Pravděpodobně byste raději pokračovali v procedurálním přístupu, ale když už své skripty musíte přepisovat, prozkoumejte (a nejlépe i využijte) ten objektový.

Základy objektově orientovaného programování (OOP) si můžete nastudovat na ITnetworku.

Připojení k databázi se u MySQLi neděje přes samostatnou funkci, nýbrž rovnou při volání konstruktoru (v případě procedurálního přístupu přes funkci mysqli_connect()):

$mysqli = new mysqli("localhost", "uživatel", "heslo", "databáze");

Tímto jsme v proměnné $mysqli vytvořili instanci objektu MySQLi. Rozdíl oproti mysql_* funkcím je ten, že tady je potřeba vždy definovat o kterou instanci se jedná (u mysql_* to bylo volitelné), takže můžeme pohodlně pracovat s vícero databázemi.

Další fází je obvykle ověření, zda bylo připojení úspěšné. U mysql_* se to zjišťuje testováním návratové hodnoty funkce mysql_connect(). U MySQLi je na to vyhrazená vlastnost connect_errno, která obsahuje kód chyby připojení (v případě úspěchu to bude nula). K dispozici je také textová reprezentace připojovací chyby, umístěná v datové položce connect_error.

if($mysqli->connect_errno){
	echo "Chyba při připojování k databázi: ".$mysqli->connect_error;
}

Pokud jsme připojeni, provádíme nějaké dotazy na databázi. Stejně jako u mysql_* přiřazujeme výsledky do určité proměnné. S tou pak dále pracujeme a už neplatí, že bychom se museli odkazovat na objekt MySQLi, nyní se odkazujeme rovnou na proměnnou s výsledkem, který totiž také tvoří objekt.

$result = $mysqli->query("SELECT 'Používám MySQLi' AS msg");

Důležitou součástí zabezpečení je escapování, prováděné funkcí mysql_real_escape_string(). U MySQLi je to téměř to samé:

$escaped = $mysqli->real_escape_string($var);

Když už máme pomocí nějakého dotazu vybraná data, obvykle je zpracováváme funkcí mysql_fetch_assoc(). U MySQLi je to opět podobné, nyní se však neodvoláváme na instanci objektu MySQLi (tedy proměnnou $mysqli), ale přímo na objekt výsledku (tedy proměnnou $result):

while($row = $result->fetch_assoc()){
	echo($row["msg"]);
}

Prepared statements

Prepared statements se využívají při vícero opakování stejného dotazu (byť s jinými hodnotami) a při jejich použití se dosahuje větší efektivity (nicméně je lze používat i při jediném volání dotazu, je to elegantní forma escapování a samotný dotaz neobsahuje žádná data, takže je přehlednější). Skládají se ze dvou fází: prepare (připravit) a execute (vykonat).

Při fázi prepare je SQL dotaz odeslán na server, ten provede kontrolu syntaxe a vyhradí zdroje pro jeho pozdější vykonání. Rovnou tedy můžeme provést kontrolu, zda je syntaxe správná a vypsat případnou chybovou hlášku:

$statement = $mysqli->prepare("INSERT INTO `table` VALUES (?)");
if(!$statement){
	echo("Příprava SQL dotazu selhala: ".$mysqli->error);
}

Dále se již nebudeme odkazovat na objekt MySQLi, ale na prepared statement (proměnná $statement), který také tvoří objekt. MySQL podporuje použití výplňového znaku ?, který se při fázi execute nahradí příslušnými daty:

$statement->bind_param("s", $var);

Výhoda je, že data se přenášejí nezávisle na dotazu a nemůžou jej ovlivnit (čímž se zamezí riziko SQL injection). Při použití funkce bind_param se data neescapují. První argument funkce bind_param označuje datový typ vkládané proměnné:

Znak Datový typ
i číslo
d desetinné číslo
s řetězec
b binární data (budou odeslána po paketech)

(Tabulka je převzatá z php.net.) Druhý parametr označuje vkládanou proměnnou (nebo rovnou nějakou hodnotu). Pokud by bylo třeba vložit proměnných více, prostě se přidají další argumenty a do prvního se dopíše odpovídající identifikátor datového typu (pozor, argumenty a definice jejich datových typů musejí být ve stejném pořadí):

$statement->bind_param("si", $string, $integer);

Vrací vám bind_param chybu typu Fatal error: Cannot pass parameter x by reference in…? Pravděpodobně proto, že jste neuvedli proměnnou, ale funkci nebo přímo hodnotu (např. $statement->bind_param("s", trim($text)); nebo $statement->bind_param("i", 1);). Toto není dovoleno a chybu lze odstranit využitím pomocné proměnné, do které uložíte výsledek funkce a onu proměnnou pak použijete v metodě bind_param.

Nyní už zbývá prepared statement jen vykonat (a rovnou ověříme, zda se vykonal správně):

if(!$statement->execute()){
	echo("Chyba při vykonávání SQL dotazu: ".$statement->error);
}

Pro další vykonání dotazu stačí pouze přepisovat proměnné uvedené ve funkci bind_param, samotnou funkci bind_param již není třeba spouštět.

Pro obsáhlejší příklady vás odkáži na anglicky psanou dokumentaci. (V dalším článku popisuji možnost získání vrácených dat z dotazu.)

Funkce mysql_result

V některých kódech se můžete setkat s funkcí mysql_result(), která už ale v rozšíření MySQLi neexistuje. Její funkci může snadno suplovat metoda mysqli_result::fetch_assoc() a pomocné pole.

Metodou fetch_assoc() si uložíme všechny sloupce do pomocné proměnné a poté místo funkce mysql_result() používáme pole vytvoření v pomocné proměnné.

mysql_*:

$query = mysql_query('SELECT * FROM users');
echo(mysql_result($query, 0, 'name').': '.mysql_result($query, 0, 'email'));

MySQLi:

$query = $mysqli->query('SELECT * FROM users');
$data = $query->fetch_assoc();
echo($data['name'].': '.$data['email']);

Druhý parametr funkce mysql_result() označuje číslo řádku, který chcete vrátit (začíná se od nuly). Metodě mysqli_result::fetch_assoc() žádný takový parametr vnutit nejde, začíná od prvního řádku a po každém jejím zavolání se posune na následující řádek.

Pokud ale potřebujeme přeskočit na konkrétní řádek (a nemáme možnost omezit výběr už v SQL dotazu), lze chování funkce mysql_result() nasimulovat metodami mysqli_result::data_seek() a mysqli_result::fetch_row(). Nevýhodou je, že pro výběr příslušného sloupce nemůžeme použít jeho název, ale musíme použít jeho pořadové číslo (začíná se od nuly).

mysql_*:

$query = mysql_query('SELECT * FROM users');
echo(mysql_result($query, 38, 'name').': '.mysql_result($query, 38, 'email'));

MySQLi:

$query = $mysqli->query('SELECT name, email FROM users');
$query->data_seek(38);
$data = $query->fetch_row();
echo($data[0].': '.$data[1]);

Procedurální rozhraní

Procedurální funkce MySQLi se většinou nijak neliší od mysql_*, pouze s tím rozdílem, že je u nich povinně uvedena jako první parametr instance buď připojení, nebo výsledku dotazu a mají prefix mysqli.

Procedurálně:

$mysqli = mysqli_connect("localhost", "uživatel", "heslo", "databáze");
$result = mysqli_query($mysqli, "SELECT 'Používám MySQLi' AS msg");
while($row = mysqli_fetch_assoc($result)){
	echo($row["msg"]);
}

Objektově:

$mysqli = new mysqli("localhost", "uživatel", "heslo", "databáze");
$result = $mysqli->query("SELECT 'Používám MySQLi' AS msg");
while($row = $result->fetch_assoc()){
	echo($row["msg"]);
}

Objektový a procedurální přístup je možné mixovat, ale v kódu vám to bude dělat zbytečný nepořádek.

Vydáno před 35 měsíci, autor: Fisir, kategorie: IT blog

Komentáře

libor

Středa 30. března 2016, 21:31

Prosím o radu.Zkouším ten váš kód,ale mě to nefunguje.

Mám tohle

$sys['ad_types'] = array("email", "ban", "ptc","ptp","side");
mysql_connect($host,$username,$password) or die(mysql_error());
mysql_select_db($database) or die(mysql_error());

a po úprav na a aktualizaci stránky naběhle býlá stránka..Co dělám špatně?

msqli_connect($host,$username,$password) or die(mysqli_error());

Fisir

Středa 30. března 2016, 21:43

[#1]: Máš tam překlep a u mysqli_error() ti chybí parametr připojení:

$db = mysqli_connect($host,$username,$password);
if(mysqli_connect_errno()){
    echo mysqli_connect_error();
}

Podobné chyby ti pro příště pomůže odhalit pohled do chybového logu webového serveru ;-)

libor

Čtvrtek 31. března 2016, 20:54

Původně podle toho původního to bylo dobře.Vše fungovalo normálně.Až nedávno my začala endora zobrazovat hlášku o změně z msql na msqli.
pokud se ti opravdu chce pomoc uvítám.Posílám ti e-mailem mou adresu webu.Pak my napiš co budeš potřebovat z php scritu

Nodo

Neděle 11. prosince 2016, 13:52

stručné a pekné. gratulujem

Přidat komentář