Drukowana wersja tematu

Kliknij tu, aby zobaczyć temat w orginalnym formacie

Forum PHP.pl _ Propozycje artykułów _ Obsługa baz danych z PDO

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:


Bez względu na to co wybierzesz i tak będzie to lepszy sposób niż stare mysql_* wink.gif

1. Pierwszą czynnością będzie ustanowienie nowego połączenia. Najprościej zrobimy to w ten sposób:
  1. try {
  2. $baza = new PDO('mysql:host=localhost;dbname=testowa_baza;charset=utf8', 'użytkownik', 'hasło');
  3.  
  4. // kod php
  5.  
  6. } catch(PDOException $err) {
  7. http://www.php.net/exit('Blad polaczenia z baza danych: '.$err->getMessage();
  8. }

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:
  1. // pobraliśmy skądś dane do połączenia (np. pliku)
  2. $host = 'localhost';
  3. $user = 'root';
  4. $pass = '';
  5. $base = 'testowa';
  6.  
  7. try {
  8.  
  9. $baza = new PDO('mysql:host='.$host.';dbname='.$base.';charset=utf8', $user, $pass,
  10. http://www.php.net/array(
  11. // wyłączenie zbędnego emulate prepares
  12. PDO::ATTR_EMULATE_PREPARES => false,
  13. // ustalenie sposobu raportowania błędów
  14. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
  15. ));
  16.  
  17. // kod php
  18.  
  19. } catch(PDOException $err) {
  20. http://www.php.net/exit('Blad polaczenia z baza danych: '.$err->getMessage();
  21. }


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():
  1. // pierwszy przykład
  2. $zapytanie = $baza->query("SELECT login FROM gracze WHERE id = 2");
  3.  
  4. $zapytanie->closeCursos(); // zamknięcie pierwszego zapytania
  5.  
  6. // drugi przykład
  7. $nazwa_nadawcy = 'shady';
  8. $minimum_id = 5;
  9.  
  10. $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():
  1. // przykład pierwszy
  2. $baza->exec("UPDATE gracze SET zloto = zloto + 5 WHERE id = ".$gracz['id']);
  3.  
  4. // przykład drugi
  5. $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:
  1. $ile = $baza->exec("UPDATE gracze SET energia = 100 WHERE energia <= 20");
  2.  
  3. 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:
  1. $zapytanie = $baza->query("SELECT login, level FROM gracze WHERE id = ".$_SESSION['id']);
  2. $gracz = $zapytanie->fetch();
  3.  
  4. http://www.php.net/echo 'Witaj '.$gracz['login'].'! Masz obecnie '.$gracz['leve'].' level.';


Proste? Proste wink.gif fetch() może przyjmować jeszcze parametr ustalający sposób zwracanych danych. Dostępne są m.in:


A tak wygląda fetchowanie z wykorzystaniem pętli (przykład na liście graczy z levelem większym lub równym 50):
  1. $gracze = $baza->query("SELECT login, level FROM gracze WHERE level >= 50");
  2.  
  3. while ($rekord = $gracze->fetch()) {
  4. http://www.php.net/echo $rekord['login'].' ma lvl: '.$rekord['level'].'<br/>';
  5. }


Warto napisać o sposobie wyświetlania liczby pobranych rekordów:
  1. $gracze = $baza->query("SELECT login, level FROM gracze WHERE level >= 50");
  2.  
  3. 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.
  1. $zapytanie = $baza->query("SELECT id, haslo FROM gracze WHERE login = '".$_POST['login']."'");
  2.  
  3. $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ę:
  1. // przygotowanie zapytania
  2. $zapytanie = $baza->prepare("SELECT id, haslo FROM gracze WHERE login = :login");
  3. // filtracja wskazanych danych
  4. $zapytanie->bindValue(':login', $_POST['login'], PDO::PARAM_STR);
  5. // właściwe wykonanie
  6. $zapytanie->execute();
  7.  
  8. $konto = $zapytanie->fetch();


bindValue przyjmuję 3 parametry:
  1. nazwa używana w zapytaniu, poprzedzona dwukropkiem (:)
  2. dane, które mają ulec filtracji
  3. 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.
  1. // przygotowanie zapytania
  2. $zapytanie = $baza->prepare("SELECT login, email FROM gracze WHERE level = :poziom AND klasa= :klasa");
  3. // filtracja wskazanych danych
  4. $zapytanie->bindValue(':poziom', $_POST['poziom'], PDO::PARAM_INT);
  5. $zapytanie->bindValue(':klasa', http://www.php.net/trim($_GET['klasa']), PDO::PARAM_STR);
  6. // właściwe wykonanie
  7. $zapytanie->execute();
  8.  
  9. if ($gracze->rowCount() > 0) {
  10. while ($gracz = $zapytanie->fetch()) {
  11. http://www.php.net/echo 'Gracz: '.$gracz['login'].' ma adres emaill: '.$gracz['email'].'<br/>';
  12. }
  13. } 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.
  1. $baza->exec("INSERT INTO kurs_pdo(uzytkownik, stan) VALUES('Ty', 'zaliczony')");
  2.  
  3. 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 sad.gif Zapraszam do dyskusji i krytyki tego co wybazgrałem, sam chętnie dowiem się nowych rzeczy.

Napisany przez: !*! 18.08.2013, 10:59:50

A czym to się różni od http://pl.wikibooks.org/wiki/PHP/Biblioteka_PDO ?

Napisany przez: TursoN 19.08.2013, 19:29:41

Dobrze opisane biggrin.gif

Literówka w 3. kodzie CloseCursos

Napisany przez: gitbejbe 26.08.2013, 13:19:08

ja, który zawsze znajdzie wymówkę przed pisaniem w oop, zostałem w końcu łopatologicznie uświadomiony jak korzystać z tego całego PDO ! wielki + ! Robiłem juz pare podeść ale bez skutku. Po obiedzie lece orać kod specool.gif

edit:
Z mojej strony prośba do administracji, żeby wyróżnić ten poradnik, na bank nie raz zajrze

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)