PHP/Jak to się robiło kiedyś?

< PHP
Poprzedni rozdział: ORM i biblioteka Doctrine
Następny rozdział: phpMyAdmin

Jak to się robiło kiedyś? edytuj

PHP Data Objects jest bardzo młodą biblioteką i mimo swoich zalet, wciąż tysiące skryptów napisanych wcześniej korzystają ze starych oraz niewygodnych funkcji komunikacji z bazami danych. Dlatego podręcznik ten zawiera także im poświęcony rozdział.

Pobieranie wyników edytuj

Ten zestaw funkcji w ogóle nie korzysta z dobrodziejstw programowania obiektowego - kiedy powstawał, w PHP po prostu jeszcze takowego nie było! Ponieważ znamy już się nieco na pracy z bazami danych, zaczniemy od razu od pobrania listy naszych produktów:

<?php

	mysql_connect('localhost:3305', 'root', 'root'); // 1
	mysql_select_db('produkty'); // 2
	
	$r = mysql_query('SELECT `id`, `nazwa`, `ilosc` FROM `produkty` ORDER BY `ilosc`'); // 3
	
	echo '<ul>';
	while($row = mysql_fetch_assoc($r)) // 4
	{
		echo '<li>'.$row['id'].' - '.$row['nazwa'].' - '.$row['ilosc'].'</li>';	
	}
	echo '</ul>';
	
	mysql_close(); // 5

?>

Opis:

  1. Funkcja mysql_connect(), która przyjmuje parametry: serwer, nazwa użytkownika, hasło, powoduje nawiązanie połączenia z bazą danych.
  2. Funkcja mysql_select_db() wybiera bazę danych, na której będziemy pracować.
  3. Funkcja mysql_query() wysyła zapytanie do bazy. W zależności od jego rodzaju generuje:
    • Zbiór wyników - dla zapytań SELECT.
    • true - dla zapytań typu INSERT jeśli wykonanie zapytania się powiodło
    • false - w przypadku jakiegokolwiek błędu w zapytaniu.
  4. mysql_fetch_assoc() pobiera kolejny rekord ze zbioru wyników $r jako tablicę asocjacyjną. Istnieją jeszcze mysql_fetch_num() (numeryczne indeksy tablicy) oraz mysql_fetch_array() (połączenie obu tych sposobów).
  5. mysql_close() zamyka połączenie z bazą.

Zauważ, że nie ma tutaj w ogóle czegoś takiego, jak zamykanie kursora. Jeśli korzystasz z tych funkcji, jest ono niepotrzebne, ponieważ to rozszerzenie tak naprawdę oszukuje. Wszystkie rekordy w są pobierane automatycznie przez mysql_query() i zapisywane do specjalnego bufora, skąd odczytuje je mysql_fetch_assoc(). Analogiczna metoda PDOStatement::fetch() pobierała dane bezpośrednio z serwera DB, umożliwiając ich natychmiastowe przetwarzanie. Obie techniki mają swoje plusy i minusy. PDO dzięki temu jest znacznie wydajniejsze, szczególnie przy większej liczbie rekordów, lecz nie można w nim sprawdzić, ile wyników ostatecznie dało nam zapytanie, dopóki ich wszystkich nie pobierzemy.

Obsługa błędów edytuj

Spróbujmy dodać do naszej listy produktów sortowanie:

<?php

	mysql_connect('localhost:3305', 'root', 'root');
	mysql_select_db('produkty');
	
	$r = mysql_query('SELECT `id`, `nazwa`, `ilosc` FROM `produkty` ORDER BY `ilosc` DECS');
	
	echo '<ul>';
	while($row = mysql_fetch_assoc($r))
	{
		echo '<li>'.$row['id'].' - '.$row['nazwa'].' - '.$row['ilosc'].'</li>';	
	}
	echo '</ul>';
	
	mysql_close();

?>

Po uruchomieniu skryptu dostajemy dziwny komunikat:

Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in D:\Serwer\www\mysql\skrypt.php on line 9

Zobaczmy: mamy literówkę w zapytaniu; napisaliśmy DECS zamiast DESC, jednak mysql_query() w ogóle nie zgłosił żadnego komunikatu! Jedynym sygnałem, że coś jest nie tak, było zwrócenie wartości false zamiast zbioru wyników, co po wstawieniu do funkcji mysql_fetch_assoc() zaowocowało komunikatem o podaniu niewłaściwego parametru. Czy więc jest tu w ogóle jakaś obsługa błędów? Oczywiście, tyle że zakłada ona, że programista lubi monotonię i pisanie w kółko:

mysql_query('zapytanie') or die('Blad MySQL: '.mysql_error().'<br/>');

W praktyce bardziej opłacało się tu napisać własny wariant mysql_query(), który automatyzuje tę czynność i samodzielnie zgłasza nam błędy, jak trzeba.

Wstawianie danych edytuj

Do wykonywania zapytań INSERT czy UPDATE także używana jest funkcja mysql_query(), lecz tym razem będzie ona za każdym razem zwracała wartość true. Aby sprawdzić, ile rekordów zostało zmodyfikowanych, musimy wywołać dodatkowo mysql_affected_rows(). Oto przepisany przykład z poprzedniego rozdziału dodający nowe produkty do bazy:

<?php

	if($_SERVER['REQUEST_METHOD'] == 'POST')
	{
		mysql_connect('localhost:3305', 'root', 'root');
		mysql_select_db('produkty');

		mysql_query('INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`)	VALUES(
			\''.mysql_real_escape_string($_POST['nazwa']).'\',
			\''.mysql_real_escape_string($_POST['opis']).'\',
			\''.mysql_real_escape_string($_POST['ilosc']).'\',
			\''.mysql_real_escape_string($_POST['cena']).'\',
			\''.mysql_real_escape_string($_POST['jakosc']).'\')');
		$ilosc = mysql_affected_rows();

		if($ilosc > 0)
		{
			echo 'Dodano: '.$ilosc.' rekordow';
		}
		else
		{
			echo 'Wystąpił błąd podczas dodawania rekordów!';
		}
		
		mysql_close();
	}
	else
	{
		?>
		<form method="post" action="mysql_4.php">
		<p>Nazwa: <input type="text" name="nazwa"/></p>
		<p>Opis: <input type="text" name="opis"/></p>
		<p>Ilosc: <input type="text" name="ilosc"/></p>
		<p>Cena: <input type="text" name="cena"/></p>
		<p>Jakosc: <select name="jakosc">
		<option value="1">1</option>
		<option value="2">2</option>
		<option value="3">3</option>
		<option value="4">4</option>
		<option value="5">5</option>
		<option value="6">6</option>
		</select></p>
		<p><input type="submit" value="Dodaj"/></p>
		</form>
		<?php	
	}

?>

Zauważ, jak musimy tutaj umieszczać dane w zapytaniu. Nie tylko wymaga to zabawy operatorem łączenia ciągów, ale też konieczność wywoływania funkcji mysql_real_escape_string() do escape'owania danych i zapobiegania atakom SQL Injection. Oczywiście, gdy magic quotes było włączone, funkcji tej nie powinno się używać.

Informacje dodatkowe edytuj

Ze względu na charakter pobierania rekordów przez to rozszerzenie, umożliwia ono policzenie ilości zwróconych wyników jeszcze przed rozpoczęciem ich pobierania. Aby to wykonać, należy skorzystać z funkcji mysql_num_rows() z podanym jako parametr zbiorem wyników.

<?php

	mysql_connect('localhost:3305', 'root', 'root');
	mysql_select_db('produkty');
	
	$r = mysql_query('SELECT `id`, `nazwa`, `ilosc` FROM `produkty` ORDER BY `ilosc`');
	
	echo '<p>Pobrano '.mysql_num_rows($r).' wyników</p>';
	
	echo '<ul>';
	while($row = mysql_fetch_assoc($r))
	{
		echo '<li>'.$row['id'].' - '.$row['nazwa'].' - '.$row['ilosc'].'</li>';	
	}
	echo '</ul>';
	
	mysql_close();

?>

Jeśli dodaliśmy nowy rekord, możemy pobrać jego ID funkcją mysql_insert_id() wykonaną zaraz po funkcji mysql_query() z zapytaniem INSERT.

Zakończenie edytuj

Jak wspomnieliśmy, rozszerzenie to ma charakter historyczny. Twórcy PHP stopniowo ograniczają wsparcie dla niego; nie ma w nim np. żadnej implementacji mechanizmu podpinania, choć biblioteki klienckie MySQL jak najbardziej na to zezwalają. Jest ono także niewygodne w użyciu oraz na dłuższą metę mało efektywne. W codziennej praktyce chyba żaden szanujący się programista nie stosował żadnej z tych funkcji bezpośrednio, lecz korzystał z dodatkowej, napisanej w PHP nakładki automatyzującej wszystkie nużące czynności i zapewniającej wsparcie programowania obiektowego. Teraz, gdy do dyspozycji jest biblioteka PDO, sens korzystania z tych funkcji jest bardzo wątpliwy.