PDO – czyli zabawa z PHP i bazą danych obiektowo!
Killavus dnia Sierpień 27, 2009
Jako, że mnóstwo programistów (czy to garażowych, czy „profesjonalnych”) wciąż siedzi w zamierzchłych przedpotopowych czasach dziadka PHP 4.x jeżeli chodzi o połączenie z bazą danych, chciałbym przedstawić ciekawą alternatywę dla ich prehistorycznych narzędzi. Mowa tutaj o PDO – PHP Database Objects. Czymże to ustrojstwo jest? Niczym innym jak obiektową implementacją interakcji PHP z bazą danych – nowszą, oferującą ciekawe możliwości i o wiele bardziej przejrzystą od starego, proceduralnego podejścia.
Oto, co w wielkim skrócie oferuje PDO:
- automatyczną ochronę przed atakami typu SQL Injection,
- możliwość korzystania z każdego silnika bazodanowego transparentnie, przy użyciu tego samego API – to kwestia zmiany jednej linijki w konstruktorze!
- mechanizmy transakcji (o ile obsługuje je nasza baza danych – MySQL, PostgreSQL i SQLite3 – obsługują) – możliwość cofnięcia dowolnych zmian w bazie danych do momentu przed manipulacją za pomocą jednej funkcji! Naprawianie błędów w bazie pozostawionych przez nieudane zapytania jeszcze nigdy nie była tak prosta!
- przygotowane zapytania (ang. prepared statements) – zoptymalizowany sposób na wysyłanie dowolnej ilości identycznie skonstruowanych zapytań z różnymi argumentami,
- pełny dostęp do informacji o aktualnym stanie bazy danych (błędy itd.) w każdej chwili,
- możliwość denerwowania głównego webmajstra Natodii,
- rozbudowany system wyjątków,
- możliwość pozwolenia dziadkowi PHP4 spokojnie umrzeć;
Pozwolę sobie skoncentrować się na kawałkach kodu wraz z objaśnieniem ‘o co chodzi’ – opis całości biblioteki znajduje się tutaj.
1. Instalacja:
W przypadku Debiana, którego używam instalacja odbywa się poprzez zainstalowanie odpowiednych paczek do bazy danych:
# aptitude install php-mysql php-sqlite php-pgsql # i tak dalej
Dla Windows polecam użycie WampServera – ma on już wszystko, czego potrzeba do korzystania z PDO. Wymaga to jednak aktywacji. Aby aktywować PDO, należy kliknąć na ikonkę serwera w trayu i w menu PHP -> PHP Extensions zaznaczyć potrzebne nam składniki i bazy danych (np. php_pdo.dll, php_pdo_mysql.dll) po czym zrestartować serwer.
2. Nawiązywanie połączenia z bazą danych:
Wszystko gotowe? Środowisko skonfigurowane? Czas więc zrobić pierwszy krok w manipulacji bazą danych – połączyć się z nią! Przykład będzie dla bazy MySQL:
try { $db = new PDO( 'mysql:host=localhost;dbname=nazwabazy', $user, $password ); } catch( PDOException $e ) { // ewentualna obsługa wyjątku... }
Jak wszyscy widzą, na argumenty konstruktora składają się (w tym wypadku): łańcuch znaków deklarujący typ bazy z którą się łączymy i parametry, użytkownika i hasło. Uwaga! W przypadku baz takich jak np. SQLite3, gdzie logowania nie ma, po prostu pomijamy te parametry dot. użytkownika i hasła – są one opcjonalne. Sercem tego systemu jest właśnie ten string formatujący – za jego pomocą definiujemy bazę, z jaką mamy się połączyć. Reszta API jest identyczna – czy to MySQL, czy SQLite, czy PostgreSQL, czy MSSQL, czy cokolwiek sobie wymyślisz, a ma sterownik w PDO.
Jako czwarty argument konstruktor przyjmuje tzw. atrybuty Są to flagi, których postawienie pozwala uruchomić zaawansowane (i niekoniecznie działające wszędzie) funkcjonalności samej bazy danych, lub PDO. Wszystkie (oprócz PDO::ATTR_PERSISTENT, która jest chyba najprzydatniejszą) można potem ustawić za pomocą funkcji setAttribute.
$db = new PDO( 'sqlite:file=file.sqlite', NULL, NULL, PDO::ATTR_PERSISTENT | PDO::ATTR_FETCH_TABLE_NAMES ); $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); // PDO nie sypie wyjątkami - wyświetla jedynie warningi // po więcej atrybutów odsyłam na: http://pl.php.net/manual/en/pdo.constants.php i strony konkretnych driverów w dokumentacji
Jako, że PDO ma swoje destruktory, aby zerwać połączenie wystarczy usunąć referencję za pomocą funkcji unset:
unset( $db ); // zrywa połączenie z bazą danych
3. Wykonywanie zapytań:
Przechodzimy do najważniejszej części – wykonywania zapytań do naszej (połączonej już) bazy danych. Są dwa sposoby, dlatego rozdzielę ten podpunkt.
3a. Surowe zapytania używając $db->query( … ):
Jest to zdecydowanie najgorszy sposób z racji tego, że nie ma wbudowanej ochrony przeciwko SQL Injection. Niemniej jednak, czasem takie „surowe” zapytania są przydatne. Pamiętaj tylko, aby pod ŻADNYM pozorem nie korzystać z tego sposobu w połączeniu z surowymi danymi z formularzy od użytkowników – powinny one zostać wcześniej przetworzone (może w tym pomóc rodzina funkcji ctype_, *val oraz PDO::quote)!!!
try { $sql = $db->query( 'SELECT * FROM foo WHERE bar = 2' ); while( $row = $sql->fetch( PDO::FETCH_ASSOC ) ) { // PDO::FETCH_ASSOC sprawia, że kolejne wiersze tabeli są wrzucane do tablicy asocjacyjnej z nazwami kolumn. // Domyślnie wrzucane są do tablicy numerowanej od 0 do x gdzie x-1 to liczba kolumn (w kolejnosci) + dorzucane sa odwolania jako nazwy kolumn. Aby zostawic same numerki - skorzystaj z PDO::FETCH_NUM echo $row[ 'id' ]; } } catch( PDOException $e ) { // ... }
3b. Zapytania przygotowane:
Zdecydowanie lepszy sposób. Dlaczego? Chroni nas przed SQL Injection, pozwala się nam nie powtarzać i rozdziela budowanie zapytania na fazę budowy i wykonywania.
$sql_one = $db->prepare( 'SELECT * FROM foo WHERE bar = ? AND x = ?' ); // to *nie* wykonuje jeszcze tego zapytania $sql_one->execute( Array( 12, 13 ) ); // wykonuje zapytanie, pod ? wstawiając kolejno: 12, 13 $sql_two = $db->prepare( 'SELECT * FROM foo WHERE :col = :val AND :coltwo = :valtwo' ); $sql_two->bindColumn( ':col', 'bar' ); // podstawia pod :col kolumnę bar $sql_two->bindColumn( ':coltwo', 'x' ); // podstawia pod :coltwo kolumnę x $sql_two->bindParam( ':val', 12, PDO::PARAM_INT ); // podstawia pod :val wartość 12. PDO::PARAM_INT jest <strong>opcjonalne</strong> $sql_two->execute( Array( ':valtwo' => 13 ) ); // wykonuje to samo polecenie co $sql_one $res = $sql_one->fetchAll( PDO::FETCH_ASSOC ); // jeden ze sposobów pobierania wyników - wszystkie na raz są wrzucane do tablicy // można też skorzystać ze 'starego' sposobu: while( $row = $sql_two->fetch( PDO::FETCH_ASSOC ) ) { // ... } $sql_one->execute( Array( ':val' => 14, ':valtwo' => 18 ) ); // wyślijmy jeszcze jedno takie samo zapytanie, z innymi argumentami
Jak zauważyliśmy, możemy tutaj dowolnie manipulować wartościami i kolumnami wrzucanymi do zapytania jeszcze PRZED wysłaniem ich do bazy danych – to wielka wygoda. Ponadto możemy jedno przygotowane zapytanie wykonać n razy, zmieniając jedynie parametry. I wszystkie wprowadzane dane są zabezpieczone przed SQL Injection, więc można tam wstawiać nawet surowe dane podane z formularzy. Czyż nie jest to raj programisty? :)
Mam nadzieję, że przekonałem Was do tego sposobu. Może jest odrobinę bardziej rozwlekły od pierwszego, ale nauczenie się korzystania z niego pozwoli Wam na pisanie bardzo dobrego i bezpiecznego kodu do interakcji z bazą danych. A o to chyba tutaj chodzi, prawda?
I, jeszcze aby do końca osłodzić Wam życie…
4. Transakcje:
Wyobraźmy sobie następującą sytuację. Mamy sobie ogromniastą bazę danych w formacie-koszmarze, przygotowaną przez programistę-troglodytę – powiedzmy, że w XMLu (sry, Javowcy, musiałem :(). Jako wspaniali, mądrzy i światli programiści korzystający z dobrodziejstw XXI wieku postanawiacie wrzucić to całe cholerstwo do jakiejś cywilizowanej bazy danych typu MySQL czy Postgre. Wyposażeni w nowo nabytą wiedzę dot. PDO postanawiacie skorzystać z tej wspaniałej technologii. Przygotowujecie zapytanie, przekazujecie argumenty – wszystko jest fajnie. Lecz nagle, BANG! Skrypt podawał złe dane! Dwa wątki się skrzyżowały i nastąpiła sytuacja wyścigu! Zrozpaczony tym, że aktualnie w Twojej bazie danych masz połowę niekoniecznie poparawnych rekordów spędzasz sporo czasu na znalezienie uszkodzonych, usunięcie bezsensownych itd. Wybacz, time limit exceeded, Ziemia została zjedzona przez Obcych. Zawiodłeś, stary.
Ale programiści baz danych przewidzieli coś specjalnie na takie okazje – mechanizm transakcji! Na czym to polega? Za pomocą metody przekazujesz do bazy danych, że zaczynasz transakcje – w tym momencie baza danych ustanawia punkt, do którego wróci, gdy coś pójdzie nie tak. Potem wykonujesz polecenia – nie są one jednak wykonywane w bazie danych. Dopiero, gdy zwolnisz transakcje za pomocą drugiej metody, wtedy zacznie się manipulacja danych. Co to daje?
- Masz pewność, że zostaną wykonane wszystkie zapytania, jakie znajdowały się w obrębie transakcji – w przeciwnym wypadku zostanie zgłoszony wyjątek,
- W przypadku błędu lub z innego powodu będziesz w stanie za pomocą jednej metody powrócić do stanu bazy sprzed transakcji,
- Baza danych optymalizuje zapytania na bieżąco – przez co wszystko jest szybsze.
Co najlepsze, wykorzystanie dobrodziejstw transakcji jest w PDO bardzo proste. Zilustruje to poniższy przykład:
try { $db->beginTransaction(); // rozpoczynamy transakcje $sql = $db->prepare( 'INSERT INTO foo VALUES( NULL, ?, ? )' ); for( $i = 0; $i < 1000000000; ++$i ) $sql->execute( Array( rand( 0, 1000000000 ), rand( 0, 1000000000 ) ) ); // wykonujemy miliard zapytań z losowymi danymi $db->commit(); // wykonujemy transakcje - w tym momencie zapytania zostaną wykonane } catch( PDOException $e ) { // coś poszło nie tak! $db->rollBack(); // wracamy do stanu sprzed transakcji ... // ... }
Jak widzimy, funkcja rollBack odpowiada za wrócenie do ‘stanu początkowego’. Oczywiście, w transakcjach możemy wykonywać zapytania typu SELECT – nie ma to jednak większego sensu. Mechanizm ten został wymyślony raczej do zapytań w stylu CALL, INSERT, UPDATE czy DELETE FROM – i przy takich okazjach należy go używać. Niestety, wadą tego rozwiązania jest fakt, że nie działa na każdej bazie danych – mogę potwierdzić z doświadczenia, że działa na nowszych MySQL, SQLite3 i PostgreSQL – w innych nie mam pojęcia. PDO jest jednak tak sprytnie skonstruowane, że w przypadku braku obsługi tego mechanizmu funkcje nie będą nic robić. Zapytacie się więc – jak sprawdzić, czy baza na pewno wróciła do stanu początkowego? Prosto – rollBack zwraca TRUE lub FALSE, jeżeli się nie udało.
Podsumowanie:
Uważam, że PDO jest jedną z lepszych technologii, które przyszły wraz z obsługą obiektowości w PHP. Dopiero od PHP 5.x rozwinęła ona skrzydła i jest teraz o wiele lepszą alternatywą dla proceduralnego podejścia. Zwalnia programistę od sporej ilości rzeczy, które miał do uczynienia w przypadku starszych technologii, a które były żmudne i niestety niezbędne, gdy chodziło o bezpieczeństwo strony – chodzi tutaj o zabezpieczenie przed atakami SQL Injection. W przypadku zapomnienia lub pomyłki taki atak mógłby mieć opłakane skutki – chociażby wykradnięcie całej bazy danych maili użytkowników to ogromna gratka dla wszelkiego rodzaju spambotów. Dzięki prepared statements ten problem znika – wewnętrzne mechanizmy PDO radzą sobie z bezpieczeństwem znakomicie.
Mam nadzieję, że tym artykułem zachęcę Was do używania PDO w swoich małych lub większych projektach!
Pozdrawiam
Killavus
12 komentarzy
Zwracam uwagę na jedną rzecz: nikt nie wie jak połączyć się bazą danych zawierającą średnik w nazwie. Podrzucam temat: http://forum.php.pl/index.php?showtopic=132488
autor: mad/ data: 29 listopada 2009, 5:31. #
nie wyobrażam sobie innej pracy z bazami danych, PDO rządzi :)
autor: php thumbnailer data: 21 maja 2010, 18:47. #
[...] aplikacji sieciowej. W dzisiejszych czasach, gdy posiadamy tak potężne narzędzia jak PDO (artykuł) często walidacja danych przychodzących od użytkownika uchodzi nam płazem. Niestety, nie [...]
autor: Standardowe narzędzia PHP do filtrowania danych | Natodia blog data: 19 lutego 2011, 0:37. #
accutane for rhinophyma
buy accutane
skin is red while taking accutane
autor: Tarpcildida data: 29 marca 2011, 16:42. #
Buy Accutane OweftOvetle
autor: Stememicy data: 1 czerwca 2011, 10:15. #
Temesauccerne cialis 10m Fahagenia
autor: cialis online data: 6 listopada 2011, 11:10. #
Duhimputh cialis suisse Noinywhellhon
autor: cialis online data: 6 listopada 2011, 22:57. #
Oceadadip cialis 10m Coinnilloluse
autor: cialis europe data: 8 listopada 2011, 23:13. #
zoloDarklergo Intakeleark
autor: cialis generique data: 13 listopada 2011, 9:59. #
MahMordakcorb buy lasix amamebousnedo
autor: buy lasix data: 17 listopada 2011, 22:06. #
ExexcypeRaRia buy nolvadex TubKayall
autor: buy nolvafex data: 20 listopada 2011, 2:00. #
abomasum…
My congratulations are flying to you, Thomas! Very well done)Jeanie recently posted..Free TV Channel Video Facebook Template…
autor: Scrapeboard data: 22 lutego 2012, 4:33. #