NNPlaya.pl

Ostatnie prace

Wyzwól potęgę ORM

PHP Kohana

Twórcy Kohany dali nam do ręki potężne narzędzie - bibliotekę Object Relational Mapping (ORM). Programowanie z jej użyciem jest dziecinnie proste, wygodne i bardzo intuicyjne. ORM to tak naprawdę zestaw kilku rozszerzalnych klas, które pozwalają na traktowanie rekordów bazy danych jako obiektów powiązanych ze sobą relacjami. Co ważne, z ORM można pracować zupełnie nie wykorzystując zapytań SQL. Zapraszam do przeczytania artykułu.

W czym tkwi tak naprawdę moc ORM?

W prostocie udostępnionego API. Przykładowo, jeśli mamy w bazie danych tabele newsów i komentarzy, a między nimi zachodzi relacja wiele do jednego (jeden news może mieć nieskończenie wiele komentarzy), to po utworzeniu obiektu reprezentującego news'a

$news = ORM::factory('news', $news_id);

możemy się odwołać do jego komentarzy w ten sposób:

foreach($news -> comments as $comment) {
   echo $comment -> content;
};

Prawda, że proste? Należy pamiętać, że właściwość comments w obiekcie news'a to tak naprawdę obiekt typu kolekcja, (o kolekcji też będzie artykuł ;)) ORM_Iterator, który zwraca przy każdej iteracji kolejny obiekt komentarza.

Nie zawsze można używać tego bajeru

Jak wszystko na tym świecie, również ORM ma pewne wymagania, które należy spełnić, aby można było z niego skorzystać. Dotyczą one struktury tabeli w bazie danych.

  • Należy stosować anglojęzyczne nazewnictwo w nazwach tabeli i klas
  • Nazwy tabeli muszą być rzeczownikami liczby mnogiej, na przykład comments, songs itd.
  • Nazwy klas modeli rozszerzających ORM muszą być rzeczownikami liczby pojedynczej, na przykład comment, song
  • Każda tablica powinna mieć kolumnę o nazwie id ustawioną jako klucz główny z właściwością auto_increment
  • Kolumnom zawierającym klucze obce należy nadawać nazwy według schematu:nazwaklasymodelu_id, na przykład song_id
  • Tabele zawierające relacje wiele do wielu powinny być nazywane według schematu: nazwatabeli1_nazwatabeli2, gdzie nazwatabeli1 i nazwatabeli2 ułożone są alfabetycznie, na przykład comments_news, czy authors_songs
Należy pamiętać, że do tworzenia liczby mnogiej z nazwy modelu Kohana wykorzystuje klasę Inflector. Jeśli zatem nazwa Twojego modelu jest nieregularna, powinieneś skopiować z katalogu system/config plik inflector.php do application/config i dodać w nim poprawną odmianę.

Definiowanie klasy korzystającej z ORM

Na początek zdefiniujmy klasy song i artist. Klasa song:

class Song_Model extends ORM {}

Klasa artist:

class Artist_Model extends ORM {}

Zapisujemy oba pliki w folderze application/models (song.php i artist.php).

Określanie wzajemnych relacji

Aby zdefiniować zależności w ORM, należy posłużyć się jedną lub więcej z 4 specjalnych właściwości obiektu. Są to:

  • has_one - relacja jeden do jednego
  • has_many - relacja jeden do wielu (dla korzenia w drzewie zależności)
  • belongs_to - relacja jeden do wielu (dla liścia w drzewie zależności)
  • has_and_belongs_to_many - relacja wiele do wielu
Dla uproszczenia przykładu przyjmę, że każda piosenka może mieć tylko jednego autora, zaś każdy autor nieskończenie wiele piosenek. Jak łatwo się domyśleć, w tabeli songs wystarczy wstawić kolumnę artist_id i zmodyfikować definicje obu klas:
class Song_Model extends ORM {
   protected $belongs_to = array('artist');
}
class Artist_Model extends ORM {
   protected $has_many = array('songs');
}
Należy zwrócić uwagę, że belongs_to zawiera nazwy modeli (liczba pojedyncza), zaś pozostałe trzy właściwości określające relacje zawierają nazwy tabel (liczba mnoga). Tutaj znajduje się zrzut bazy danych MySQL z przykładowymi danymi.

Tworzenie instancji klasy rozszerzającej ORM

Można tego dokonać dwojako - albo korzystając ze statycznej metody factory klasy ORM

$artist = ORM::factory('artist', 1);

Metoda ta przyjmuje dwa argumenty. Pierwszym jest nazwa modelu, a drugim (opcjonalnym) ID rekordu w bazie danych. Jeśli go pominiemy, wtedy zostanie utworzony nowy, pusty obiekt. Drugim sposobem jest skorzystanie z tradycyjnej składni instancjonowania

$artist = new Artist_Model(1);

W tym przypadku konstruktor przyjmuje jeden opcjonalny argument i ponownie jest to ID rekordu.

Dodawanie nowego wykonawcy do bazy danych

Algorytm postępowania wygląda następująco:

  • 1. Tworzymy nowy obiekt Artist_Model bez podawania ID
  • 2. Przypisujemy mu wartości
  • 3. Wywołujemy metodę save i cieszymy się nowo zapisanym rekordem. :)
Akurat tabela artists ma tylko 2 pola:
`id` MEDIUMINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name_and_surname` VARCHAR(255) NOT NULL
Zatem interesuje nas ustawienie tylko imienia i nazwiska

$new_artist = ORM::factory('artist');
$new_artist -> name_and_surname = 'Jay-Z';
$new_artist -> save();

I to wszystko.

Przypisywanie piosenek do wykonawcy

...to też kaszka z mleczkiem.

  • 1. Tworzymy nowy obiekt(y) Song_Model bez podawania ID
  • 2. Przypisujemy mu(im) wartości
  • 3. Przypisujemy obiektom Song_Model wartość id obiektu Artist_Model dla własności artist_id
  • 4. Wywołujemy metodę save w obiektach modeli.
$song1 = ORM::factory('song');
$song1 -> name = 'Dirt off your shoulder';
$song1 -> artist_id = $new_artist -> id;
$song1 -> save();

Bardziej interesujący przykład

Znacznie ciekawsze jest budowanie relacji typu wiele do wielu. Zmodyfikujmy klasę Artist_Model i dodajmy jeszcze jedną, Album_Model. Przyjmijmy, że jeden wykonawca może występować na nieskończenie wielkiej liczbie albumów i zarazem na albumie może gościć nieskończenie wielu artystów, zatem jest to relacja wiele do wielu. Najpierw modyfikacje w kodzie:

class Artist_Model extends ORM {
   protected $has_many = array('songs');
   protected $has_and_belongs_to_many('albums');
}
class Album_Model extends ORM {
   protected $has_and_belongs_to_many('artists');
}

W bazie danych będziemy potrzebowali dwóch nowych tabel:

CREATE TABLE `albums` (
`id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

CREATE TABLE `albums_artists` (
`album_id` MEDIUMINT UNSIGNED NOT NULL,
`artist_id` MEDIUMINT UNSIGNED NOT NULL,
 PRIMARY KEY  (album_id, artist_id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

ALTER TABLE `albums_artists`
  ADD CONSTRAINT albums_artists_idfk_1 FOREIGN KEY (album_id) REFERENCES albums (id) ON DELETE CASCADE,
  ADD CONSTRAINT albums_artists_idfk_2 FOREIGN KEY (artist_id) REFERENCES artists (id) ON DELETE CASCADE;

Tym razem nie będziemy przez ORM przypisywać id albumów i wykonawców do tabeli albums_artists łącząc je w pary, gdyż nie o to chodzi. Natomiast dysponując zapisanymi w bazie obiektami Artist_Model i Album_Model...

$artist = ORM::factory('artist');
$artist -> name_and_surname = 'Szymon Wydra';
$artist -> save();

$album = ORM::factory('album');
$album -> title = 'Teraz wiem';
$album -> save();

Wywołujemy w dowolnym z nich metodę add i jako argument przekazujemy drugi obiekt. Następnie wywołujemy metodę save.

$album -> add($artist);
$album -> save();

Nic trudnego. ;)

Na koniec

...pozostała jeszcze jedna ważna kwestia - jak pobierać z bazy rekordy spełniające określone warunki? W tym celu wystarczy się posłużyć query_builder'em z biblioteki Database. Brzmi groźnie? Spokojnie, to nic strasznego. Prześledźmy kilka przykładów:

$song = ORM::factory('song') -> where('name', 'Feel') -> find();

$artist = ORM::factory('artist') -> where('name_and_surname', 'Serj Tankian') -> find();
$songs = ORM::factory('song') -> where('artist_id', $artist -> id) -> find_all();

W pierwszym przykładzie wybieramy z bazy rekord z tabeli, którą reprezentuje obiekt Song_Model (notabene chodzi o songs) i którego pole 'name' ma wartość 'Feel'. Innymi słowy, pobieramy piosenkę o tytule Feel. :P Jak widać, pierwsza linijka kodu kończy się na metodzie find(). To istotne, bez wywoływania jej lub find_all (o której za chwilę) baza nie zwróci nam żadnych wyników i pozostaniemy z niczym. ;) Różnica między find, a find_all polega na tym, że find zwraca jeden rekord (jeden obiekt ORM), a find_all wszystkie, które spełniają warunki (obiekt ORM Iterator, o którym była już mowa wcześniej). Jak widać w drugim przykładzie, próbuję wybrać z bazy danych informacje o piosenkach Serj'ego Tankiana. Nieważne, czy jest tam jedna czy 10000, baza zwróci wszystkie. Odnośnie query_builder'a powstanie również artykuł(y?). Wspomnę tylko, że można go również wykorzystywać w ten sposób:

foreach($artist -> where('rank', 1) -> songs as $ranked_songs) {
   echo $song -> name;
}

To przykład trochę 'na sucho', bowiem w przykładowej tabeli nie ma kolumny rank. Wystarczy, że demonstruje jak wybierać spośród powiązanych z obiektem rekordów te, które nas interesują. ;) Na dzisiaj to już wszystko, dziękuję wszystkim, którzy dotrwali jakoś do końca. :DDokumentacja ORM (w języku Szekspira naturalnie)

3 komentarze »
#1 by MWL 31 sierpnia 2009 o godzinie 8:29 Bardzo fajna notka, aż odpaliłem Eclipse. Mam nadzieję że będziesz pisał więcej o Kohanie.
#2 by Matix 31 sierpnia 2009 o godzinie 11:58 Widać, że Kohana idzie w kierunku Ruby On Rails, z którym tak czy tak - ze względu na sam język - nie ma szans. Ciekawy jestem jak z wydajnością. Mimo wszystko - poczekamy, zobaczymy. Może jednak kiedyś skorzystam z tego frameworka. Puki co pozostaję wierny Railsom, Pylonsowi i Symfony :)
#3 by zajefajnyx 23 listopada 2009 o godzinie 21:41 Chcemy wiecej :D