Prawie każda aplikacja pisana w PHP’ie korzysta z bazy danych. Od wczesnych wersji PHP’a zmuszeni byliśmy do pisania „czystego” kodu SQL. Z czasem jednak pojawiały się z tym problemy takie jak kompatybilność zapytań SQL z różnymi typami baz danych.
Na przeciw temu powstawały biblioteki „abstrakcyjnego” dostępu do baz danych – DAO (Data Access Object) – nazywane także warstwą pośrednią dostępu do danych.
Niewątpliwie takie podejście pozwoliło, przynajmniej w teorii, odetchnąć z ulgą w przypadku gdy musieliśmy zmienić bazę na inną niż dotychczas używaną.
Świat na szczęście nie stoi w miejscu i doczekaliśmy się rozwiązań dla leniuchów
Mówię tu o tzw. ORM’ach – Object-Relational Mapping. Jest to w skrócie technika umożliwiająca mapowanie danych do postaci obiektów, które w prosty i łatwy sposób możemy wykorzystywać w aplikacji. Dzięki niej, nie musimy także pisać SQLa przez co raz, że skraca się czas tworzenia aplikacji, a dwa zmniejsza ilość błędów – w tym zapobiega atakom SQL Injection.
I właśnie o jednej z implementacji ORM’ów chciał bym tutaj napisać.
Doctrine, bo o niego mi chodzi, to implementacja ORM’a rodem z Javy i jej Hibernate’a. Tak jak i on posiada swóją własną abstrakcyjną warstwę dostępu do danych i wewnętrzny język SQLowy zwany DQL – „obiektowy” SQL.
Oczywiście jego głównym zadaniem jest mapowanie danych do postaci obiektów. Dzięki temu w prosty sposób mam dostęp do naszych danych.
Nie będę opisywał szczegółów działania czy procedury instalacyjnej i konfiguracyjnej tego środiwska, ale chciał bym przedstawić Wam tylko kilka przykładów w jaki sposób wygląda dostęp do bazy przy użyciu Doctrine’a. Niestety w polskiej sieci mało jest informacji o tej bibliotece, a jest to w mojej ocenie jeden z lepszych produktów dla PHP, którym warto się zainteresować.
Zobaczmy więc przykładowe wywołanie zapytania INSERT:
$user = new User(); $user->name = 'Jan'; $user->surname = 'Kowalski'; $user->save();
…i tyle
Oczywiście klasę User zdefiniowaliśmy wcześniej zgodnie z wytycznymi jakie zawarte są w dokumentacji Doctrine’a.
Powyższy kod w bardzo prosty sposób wstawia rekord do tabeli z użytkownikami. Jak widać w ogóle nie użyliśmy SQL’a.
Kolejnym przykładem jest dostęp do powiązanej z tabeli profile, powiązanej z tabelą user.
echo $user->Profile->hobby;
Kod ten ma za zadanie pobranie z tabeli profile, kolumny hobby. Tabela profile, jest powiązana z tabelą user, co zdefiniowane zostało w konfiguracji tej tabeli.
Taki zapis jest tym samym co wykonanie zapytania:
SELECT hobby FROM profile WHERE id_user = xxx
Jednak doctrine sam zajmie się skonstruowaniem zapytania tak abyśmy nie musieli się tym zajmować.
Przyjrzyjmy się teraz nieco bardziej skomplikowanemu kodowi:
$users = new Doctrine_Query()->from('User u') ->innerJoin('u.Profile p') ->innerJoin('u.Group g') ->where('u.birthday = ?', 'now()') ->limit(5) ->execute(); foreach ($users as $user) { echo $user->name . ''; echo $user->Group->name . ''; echo $user->Profile->hobby . ' '; }
Na początku wykonujemy zapytanie które pobierze nam 5 użytkowników, którzy urodziny wypadają dzisiaj (zwróćcie uwagę na zapytanie WHERE, które zawiera znak ‘?’ – w jego miejsce wstawiona zostanie wartość ‘now()’. Gdyby była to inna wartość została by ona automatycznie oczyszczona ze znaków, które powodowały by błędy w zapytaniu SQL). Dodatkowo pobierzemy dane z dwóch powiązanych tabel profile oraz group. A na koniec wylistujemy wyniki.
Jak widać kod jest bardzo prosty i przejrzysty. Nie musieliśmy pisać żadnego SQL’a.
Przykład ten mogli byśmy także nieco okroić, przez usunięcie wywołań innerJoin(). W pętli foreach nie musielibyśmy nic zmieniać, gdyż Doctrine sam będzie wiedział co oznacza wywołanie $user->Group->name i zamieni je na pojedyńcze zapytanie SQL (metoda ta nazywa się lazy loading).
Przy tym przykładzie, wewnątrz pętli foreach moglibyśmy także zastosować metodę delete() na obiekcie $user, w celu usunięcia rekordu z bazy.
Doctrine oferuje oczywiście znacznie więcej jak np. wspomniany DQL, obsługę transakcji, własne triggery i sekwencje, obsługe drzewek SQLowych i wiele innych zaawansowanych rozwiązań. Dodatkowo warto wspomnieć iż Doctrine używa PDO, przez co jest to wydajne rozwiązanie. No i jak sami widzicie używanie go jest bardzo intuicyjne i nawet początkującemu nie powinno sprawić problemów.
Oczywiście są i inne tego typu rozwiązania jak chociażby Propel, Zend_Db, POG ale sądzę, że Doctrine już na tyle rozwiniętą biblioteką (i stale rozwijaną), że warto się nim zainteresować.
Więcej informacji o Doctrine dowiecie się z oficjalnej strony znajdującej się pod tym adresem:
W końcu ktoś się podjął krótkiego opisu tego ORM’a.
Od paru dni próbuje poznać tą technologię.
Skorzystałem z generowania modeli z bazy danych (generateModelsFromDB), w efekcie dostaje w dwóch folderach tabele zamienione na klasy.
I teraz w pliku ładuje te wszystkie modele metodą loadModels w celu automatycznego ładowania tych klas. Jednak wykonując jakies Doctrine_Query na którejś klasie strasznie dlugo to trwa.
metoda LoadModels laduje te wszystkie potrzebne pliki w pare sekund.
Istnieje jakas inna, szybsza droga? (tabel/klas mam około 35 w jednym folderze i tyle samo w folderze ‘generated’)
Pozdrawiam
Spróbuj użyć tej metody:
spl_autoload_register(array(‘Doctrine’, ‘autoload’));
zamiast loadModels();
Klasy będą wtedy ładowane automatycznie w momencie kiedy będziesz chciał sie do nich odwołać.
Już sobie poradziłem.
Tą metode która podałes już miałem wcześniej użytą ponieważ ona ładuje automatycznie klasy niezbędne dla działania ORM’a.
Ja na swoje potrzeby musiałem stworzyć drugą taką metodę która po prostu ładuje klasy z tych moich katalogów gdzie znajdują się modele.
spl_autoload_register(array(‘General’,'autoload’));
Stworzyłem osobna klasę General w której występuje jedna statyczna metoda autoload która ładuje klasy z dwóch katalogów (models/ oraz models/generated/)
I rzeczywiście działa dużo szybciej!
Pozdrawiam.
Nie wiem czy korzystałeś z generowania modeli z istniejącej już bazy danych?
Wszystko ładnie generuje tylko… nie uwzględnia np kluczy unikalnych i relacji między tabelami.
Mam to wszystko sam uzupełniać ??
Raczej tak.
Z generowania tabel skorzystałem tylko raz i też nie miałem powiązań. Co prawda było to w połączeniu z symfony (korzystając z plugina) ale nie udało mi się zmusić Doctrinea aby wygenerował klucze obce :-/
Dowiedziałem się że opcje te mają być zaimplementowane w którejś z następnych wersji Doctrine.
Ja mam jednak pytanie odnośnie definiowania relacji.
W dokumentacji Doctrine’a napisane jest, że przy definiowaniu relacji jeden-do-jeden trzeba używać metody setUp() w obu klasach(tabelach),
w jednej hasOne() i w drugiej hasOne().
W przypadku relacji jeden-do-wielu metoda setUp() używana była tylko w klasie po stronie jeden. Jednak na końcu rozdzialu „relation” zastosowano to w dwóch klasach. I w końcu nie wiem czy trzeba w obu czy nie…
Po stronie jeden używana była hasMany() a po stronie wiele metoda hasOne().
Ja używam tej drugiej metody: hasMany() i hasOne().
Jako, że działa to nie zagłębiałem się w tą pierwszą opcję
Źle mnie zrozumiałeś,
Ja tez używam tych metod, są one wywoływane w metodzie setUp określonej klasy.
Chodzi mi tylko o to czy w klasie po stronie jeden używasz metody hasMany() a w klasie po stronie wiele używasz metody hasOne() ??
Ja do tej pory w tego typu relacjach jeden-do-wielu używałem tylko metody hasMany() po stronie jeden.
Natomiast w dokumentacji Doctrine’a raz stosowano to rozwiązanie a raz uzupełniano to metodą hasOne() po stronie wiele.
I nie wiem w końcu jak jest poprawnie ??
Pozdrawiam.
Tak jak pisałem
hasMany() po jednej stronie, a po drugiej hasOne() inna sprawa, ze spookojnie mozna uzywac nawet hasOne() po obu stronach.
[...] Znalazłem trochę czasu i poniżej przedstawiam Wam mały wyciąg z podstawowych operacji na danych przy wykorzystaniu biblioteki Doctrine opisywanej przeze mnie w poprzednim wpisie. [...]