Napisany przez: SlimShady 18.08.2013, 00:37:34
Tradycyjny i popularny sposób łączenia z bazą danych przy pomocy mysql_* dawno stał się przestarzały. Teraźniejszość z PHP daje nam do wyboru 2 rozszerzenia, które są dużo lepszą alternatywą niż dotychczasowa metoda. Mowa o MySQLi oraz PDO.
Przedstawmy zalety i wady tego rozszerzenia w porównaniu z MySQLi:
- PDO obsługuje wiele interfejsów baz danych. MySQLi (jak sama nazwa wskazuje) skupia się jedynie na systemie MySQL, natomiast w PDO wystarczą niewielkie zmiany, aby pasował on pod takie systemy baz danych jak np. PostgreSQL, SQLite oraz oczywiście MySQL. Oznacza to, że kod w PDO jest wysoce przenośny i doskonale nadaje się do projektów na sprzedaż (np. silniki gier).
- Mamy do dyspozycji podpinanie danych, dzięki któremu programista zaoszczędzi sobie pracy z pisaniem dodatkowego kodu. PDO pomoże nam zabezpieczyć zapytania w zaledwie 2 linijkach.
- Łatwy i przejrzysty zapis instrukcji.
- Duża wygoda korzystania.
- Multum poradników.
- W niektórych przypadkach MySQLi okazuję się być szybsze, jednak to nie przysłania wszystkich zalet PDO.
Bez względu na to co wybierzesz i tak będzie to lepszy sposób niż stare mysql_*
1. Pierwszą czynnością będzie ustanowienie nowego połączenia. Najprościej zrobimy to w ten sposób:
try {
$baza = new PDO('mysql:host=localhost;dbname=testowa_baza;charset=utf8', 'użytkownik', 'hasło');
// kod php
} catch(PDOException $err) {
http://www.php.net/exit('Blad polaczenia z baza danych: '.$err->getMessage();
}
mysql: definiuję system bazy danych, na którym będziemy opierać swoje działania. Dalej podajemy
host,
nazwę bazy, ustalamy na szybko kodowanie (
utf8) oraz wpisujemy login
użytkownika + jego
hasło.
Blok
try { ... } catch { ... } po prostu obsługuję zaistniałe błędy. Pomiędzy klamrami od try zamieszczamy cały skrypt naszej strony, a w catch ustalamy sposób raportowania błędów. Robimy to poprzez wyjątek
PDOException, który zwraca nam kilka możliwych metod. Skorzystaliśmy z
getMessage() przechowującą pełną informację o błędzie. Co należy wiedzieć o PDOException? Jest na tyle fajne, że nie musimy umieszczać dotychczasowego
or die mysql_error.. a jedną linijką dostaniemy wszystkie informację.
Choć kod działa i dodatkowo prosto, to nie oznacza, że w pełni swoich możliwości. Wprowadźmy parę zmian:
// pobraliśmy skądś dane do połączenia (np. pliku)
$host = 'localhost';
$user = 'root';
$pass = '';
$base = 'testowa';
try {
$baza = new PDO('mysql:host='.$host.';dbname='.$base.';charset=utf8', $user, $pass,
http://www.php.net/array(
// wyłączenie zbędnego emulate prepares
PDO::ATTR_EMULATE_PREPARES => false,
// ustalenie sposobu raportowania błędów
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
// kod php
} catch(PDOException $err) {
http://www.php.net/exit('Blad polaczenia z baza danych: '.$err->getMessage();
}
Dodaliśmy kilka parametrów i przy okazji przedstawiłem sposób zamieszczania zmiennych w połączeniu.
2. Będąc już połączonym z bazą danych, przyszedł czas na parę zapytań. Odpowiednikiem
mysql_query() jest
query():
// pierwszy przykład
$zapytanie = $baza->query("SELECT login FROM gracze WHERE id = 2");
$zapytanie->closeCursos(); // zamknięcie pierwszego zapytania
// drugi przykład
$nazwa_nadawcy = 'shady';
$minimum_id = 5;
$zapytanie = $baza->query("SELECT * FROM poczta WHERE nadawca = '".$nazwa_nadawcy."' AND id >= ".$minimum_id);
Jak widać nie ma tu praktycznie nic nowego.
Omówić należy
closeCursor(). Używamy go wtedy, gdy wykonamy zapytanie typu SELECT, a zaraz po nim chcemy wykonać inne zapytanie POBIERAJĄCE dane. Wtedy zamykamy tzw. kursor zapytania. Jest wiele teorii, dla których ten zapis powinien być stosowany. W sieci spotkamy się z tym, że pozwala to nadać większej kompatybilności między różnymi interfejsami, a nawet, iż przyśpiesza to działanie, choć osobiście nie sprawdzałem różnic..
3. Należy zwrócić uwagę, że polecenia query() użyjemy tylko do zapytań SELECT, a wszelkie inne jak UPDATE, DELETE, INSERT itp. zamieszczamy w
exec():
// przykład pierwszy
$baza->exec("UPDATE gracze SET zloto = zloto + 5 WHERE id = ".$gracz['id']);
// przykład drugi
$baza->exec("INSERT INTO przedmioty(nazwa, gracz_id, sila, obrona) VALUES('".$item['nazwa']."', ".$gracz['id'].", ".$item['sila'].", ".$item['obrona'].")");
W przeciwieństwie do query() metoda exec() zwraca liczbę, a dokładniej liczbę zoperowanych rekordów. Przykład:
$ile = $baza->exec("UPDATE gracze SET energia = 100 WHERE energia <= 20");
http://www.php.net/echo 'Zmienionych rekordów: '.$ile;
4. Nie pokazaliśmy jeszcze jak pobrane dane przy użyciu query() przypisać do elementu dokumentu:
$zapytanie = $baza->query("SELECT login, level FROM gracze WHERE id = ".$_SESSION['id']);
$gracz = $zapytanie->fetch();
http://www.php.net/echo 'Witaj '.$gracz['login'].'! Masz obecnie '.$gracz['leve'].' level.';
Proste? Proste
fetch() może przyjmować jeszcze parametr ustalający sposób zwracanych danych. Dostępne są m.in:
- $dane = $zap->fetch(PDO::FETCH_BOTH) - ustawiane domyślnie, możemy stosować zarówno $dane['kolumna'] jak i $dane[1]
- $dane = $zap->fetch(PDO::FETCH_ASSOC) - pozwala na stosowanie wyłącznie indeksów asocjacyjnych ($dane['kolumna'])
- $dane = $zap->fetch(PDO::FETCH_OBJ) - zwraca tablice z indeksami wyłącznie numerycznymi ($dane[1])
- $dane = $zap->fetch(PDO::FETCH_OBJ) - zwraca dane jako anonimowy obiekt ($dane->kolumna)
oraz 4 inne (patrz http://php.net/manual/en/pdostatement.fetch.php)
A tak wygląda fetchowanie z wykorzystaniem pętli (przykład na liście graczy z levelem większym lub równym 50):
$gracze = $baza->query("SELECT login, level FROM gracze WHERE level >= 50");
while ($rekord = $gracze->fetch()) {
http://www.php.net/echo $rekord['login'].' ma lvl: '.$rekord['level'].'<br/>';
}
Warto napisać o sposobie wyświetlania liczby pobranych rekordów:
$gracze = $baza->query("SELECT login, level FROM gracze WHERE level >= 50");
http://www.php.net/echo 'Graczy na levelu 50+: '.$gracze->rowCount();
5. Bindowanie danych, a mówiąc polskim odpowiednikiem: podpinanie danych. To jedna z największych zalet PDO, a zarazem wnosząca największą ze zmian w zapisie kodu. Na czym ono polega? Nie wykonujemy od razu zapytania, lecz najpierw je pod to przygotowujemy. Ustalamy dane, które mają ulec filtracji i dopiero teraz możemy je wykonać.
Przyjmijmy, że mamy formularz logowania i chcemy sprawdzić istnienie danego konta. W tym celu wykonujemy do bazy danych zapytanie, mające na celu odnalezienie konta o wprowadzonym nicku.
$zapytanie = $baza->query("SELECT id, haslo FROM gracze WHERE login = '".$_POST['login']."'");
$konto = $zapytanie->fetch();
Powyższy zapis naraża bezpieczeństwo naszej aplikacji na katastrofalne straty. Potencjalny hacker wykorzystujący takie nie zabezpieczone dane może wykonać dosłownie KAŻDE zapytanie! W tym usunąć użytkowników, a także całą bazę..
Jak sobie z tym poradzić? Nie jest to trudne, a dzięki PDO wręcz banalne. Każde dane otrzymywane od użytkowników (zmienne super globalne GET, POST, ale także ciastka i sesje) odkazić z potencjalnego niebezpieczeństwa. Po prostu sprawdzić, czy nie zawierają one cudzysłowów, kodu html/js i innego świństwa, które należałoby zamienić.
Nowy zapis ostatniego kodu powinien mieć taką formę:
// przygotowanie zapytania
$zapytanie = $baza->prepare("SELECT id, haslo FROM gracze WHERE login = :login");
// filtracja wskazanych danych
$zapytanie->bindValue(':login', $_POST['login'], PDO::PARAM_STR);
// właściwe wykonanie
$zapytanie->execute();
$konto = $zapytanie->fetch();
bindValue przyjmuję 3 parametry:
- nazwa używana w zapytaniu, poprzedzona dwukropkiem (:)
- dane, które mają ulec filtracji
- pod jakim kątem przefiltrować te dane
Jak widać w 3 parametr (kąt filtracji) podaliśmy akurat
PDO::PARAM_STR, co oznacza, że PDO ma filtrować odebrane dane jako ciąg znaków. Drugą wartością dla tego parametru może być
PDO::PARAM_INT, czyli filtracja dla liczb. Inne znajdziemy http://php.net/manual/en/pdo.constants.php. Bindować możemy nie tylko SELECT, ale i całą resztę
W celach lepszego zrozumienia dam kolejny przykład. Panel administratora wyświetlający adresy email graczy o podanym levelu i klasie postaci.
// przygotowanie zapytania
$zapytanie = $baza->prepare("SELECT login, email FROM gracze WHERE level = :poziom AND klasa= :klasa");
// filtracja wskazanych danych
$zapytanie->bindValue(':poziom', $_POST['poziom'], PDO::PARAM_INT);
$zapytanie->bindValue(':klasa', http://www.php.net/trim($_GET['klasa']), PDO::PARAM_STR);
// właściwe wykonanie
$zapytanie->execute();
if ($gracze->rowCount() > 0) {
while ($gracz = $zapytanie->fetch()) {
http://www.php.net/echo 'Gracz: '.$gracz['login'].' ma adres emaill: '.$gracz['email'].'<br/>';
}
} else http://www.php.net/echo 'Brak graczy o podanych parametrach..';
Bindowanie nie ma w sobie
trimowania (usuwania z początku i końca linii białych znaków) dlatego przy klasie postaci (ogólna tendencja głównie do stringów) użyliśmy jeszcze funkcji trim().
Nie wspomniałem o przydatnej funkcji, która zwróci numer id ostatnio dodanego wiersza przez polecenie INSERT.
$baza->exec("INSERT INTO kurs_pdo(uzytkownik, stan) VALUES('Ty', 'zaliczony')");
http://www.php.net/echo 'Ten rekord otrzymał ID: '.$baza->lastInsertId();
Po więcej wiedzy warto zajrzeć oczywiście do http://www.php.net/manual/en/book.pdo.php.
Przepraszam, jeśli temat wykracza poza tematykę działu, ale lubię się czymś dzielić, a poza tym nie znalazłem tu innego działu do zamieszczenia tego
Zapraszam do dyskusji i krytyki tego co wybazgrałem, sam chętnie dowiem się nowych rzeczy.