Skoro udało mi się zbudować statyczną stronę na bazie jednego widoku i jednego kontrolera i jednego modelu generującego treść pora na rozwinięcie projektu i uruchomienie mechanizmu pobierania treści z bazy. Nie będę się rozwodzić nad konstrukcją samej bazy, bo to zależy od architektury budowanego serwisu i jego stopnia skomplikowania. Pokażę tylko co koniecznie trzeba zrobić, żeby skorzystać z dobrodziejstwa bazy danych.
Konfiguracja połączenia z bazą danych
Załóżmy, że posiadamy już bazę danych i jakiś
Pierwsze o co trzeba poszerzyć naszą aplikację to konfiguracja połączenia z bazą. W tym celu należy w katalogu z plikami konfiguracyjnymi aplikacji /application/config umieścić plik konfiguracyjny database.php, najlepiej skopiowany z katalogu /system/config (należy unikać edytowania jakichkolwiek plików w katalogu system dlatego też lepiej jest wykorzystać kaskadowość jaką oferuje Kohana).
Zawartość pliku konfiguracyjnego pokazałam poniżej. Jak widać została tam skonfigurowana baza MySQL. Najważniejsze parametry to adres, nazwa bazy, nazwa użytkownika i hasło dostępu. Czasem trzeba podać również port. Jeśli w bazie trzymamy tabele różnych aplikacji i rozróżniamy je prefiksem można specyficzny dla naszej bazy prefiks ustawić już tu i zapomnieć o nim w zapytaniach do bazy jakie będziemy konstruować później w kodzie.
$config['default'] = array( 'benchmark' => TRUE, 'persistent' => FALSE, 'connection' => array( 'type' => 'mysql', 'user' => 'nazwa_usera', 'pass' => 'haslo', 'host' => 'adres_hosta', 'port' => FALSE, 'socket' => FALSE, 'database' => 'nazwa_bazy' ), 'character_set' => 'utf8', 'table_prefix' => '', 'object' => TRUE, 'cache' => FALSE, 'escape' => TRUE );
Komunikacja z bazą – biblioteki
Po prawidłowym skonfigurowaniu połączeni w zasadzie można bez problemu korzystać z bazy w całej aplikacji. Póki co w Kohana są na to dwa sposoby (oczywiście poza rozwiązaniami bazującymi na standardowych funkcjach php). Framework daje nam do dyspozycji dwie biblioteki: ORM oraz Database.
O pierwszej z nich tylko kilka słów, bo jeszcze sie do niej nie przekonałam i z niej nie korzystam. ORM bazuje (jak wskazuje nazwa) na mapowaniu obiektowo-relacyjne (ang. Object-Relational Mapping). Jest to sposób odwzorowania obiektowej architektury systemu informatycznego na bazę danych. Korzystając z tej biblioteki w zasadzie nie piszemy żadnych zapytań do bazy. Ten system ma wiele wad i ograniczeń, ale jest przez wielu programistów uznawany za wygodny.
Ja w swoich aplikacjach tworzonych za pomocą Kohana wolę posługiwać się biblioteką Database. Oferuje ona zarówno mozliwość pisana zapytań ręcznie jak i składania ich za pomocą gotowych metod qurey buildiera. Poniżej przykład tego samego zapytania wykonanego na różne sposoby.
Można tak:
$db = new Database(); $result = $db->query('SELECT username,password,email FROM users WHERE id=5');
albo tak:
$db = new Database(); $db->select('username,password,email'); $db->from('users'); $db->where('id', 5); $db->get();
a nawet tak:
$db = new Database(); $db->select('username,password,email')->from('users')->where('id', 5)->get();
To tylko prosty przykład, a możliwości jakie daje qurey buildier są niemal nieograniczone.
Nieco bardziej uniwersalna konstrukcja kontrolera
W przypadku sztywnej architektury strony budowa kontrolera jaką zaproponowałam w artykule o tworzeniu strony statycznej w Kohana (dla każdego działu serwisu osobna metoda kontrolera) miała swoje uzasadnienie. W sytuacji, kiedy treść naszej strony przechowywana jest w bazie można by tę konstrukcje utrzymać ale tylko pod warunkiem poważnego ograniczenia możliwości modyfikacji tej treści. Jeśli założymy, że główne działy serwisu są żelazną, niezmienną ramą to możemy dla obsługi każdego z nich przeznaczyć w kontrolerze osobną funkcję i na sztywno do tych funkcji linkować.
Jeśli jednak chcemy pójść w kierunku tworzenia małego CMSa, który będzie nam pozwalał na modyfikację budowy strony już na poziomie tych głównych działów (dodawanie nowych, lub usuwanie niektórych) to niestety musimy nieco zmienić budowę kontrolera. Załóżmy, że nasza aplikacja będzie miała budowę taką jak poprzednio, czyli klikając na pozycję menu głównego wybieramy dział a na pozycję menu dodatkowego wybieramy podstronę należącą do tego działu. Poprzednio linki w menu wyglądały następująco:
- z menu głównego link ‘mojadomena.pl/home/index’ powodował wywołanie metody index() kontrolera
- z menu dodatkowego link ‘mojadomena.pl/home/index/0’ powodował wywołanie metody index() z parametrem 0
Jeśli chcemy aby zmieniała liczba działów sztywna konstrukcja odpowiedzialnych za nie metod nie ma sensu. W takim układzie w konstruktorze trzeba zrobić jedną metodę, która będzie przyjmowała dwa parametry (numer identyfikacyjny działu i numer identyfikacyjny podstrony).
Wystarczy więc w skonstruowanym poprzednio kontrolerze zastąpić wszystkie publiczne metody jedną uniwersalną:
public function index($i_Active, $i_Subactive=0){ $this->_set_content($i_Active,$i_Subactive); }
I już struktura serwisu jest elastyczna i niezależna od liczby działów. Oczywiście należy się zabezpieczyć i zawsze sprawdzać, czy parametry wpadające do metody index() mają jakiś sens, gdyż może się zdarzyć, że jakiś dowcipny użytkownik będzie próbował wpisywać w przeglądarce linki ręcznie wypróbowując zabezpieczenia jakich użyliśmy.
Konstrukcja modelu
W poprzednio opisanym przykładzie cała zawartość strony pobierana była z tabel, które były na sztywno zdefiniowane w modelu do którego odwoływała się prywatna metoda kontrolera _set_content(). Nic nie stoi na przeszkodzie, żeby teraz zamiast wyciągać te dane z tabel pobierać je z bazy za pomocą odpowiednich zapytań. Wygodnie jest więc dołożyć do modelu prywatną zmienną (property) na przykład $db, która będzie odpowiadała za połączenie z bazą.
<?php defined('SYSPATH') OR die('No direct access allowed.'); class Content_Model extends Model{ private $db; public function __construct(){ parent::__construct(); $this->db = new Database(); } } ?>
Oczywiście w tak prostym przykładzie może to być również zmienna lokalna inicjowana w każdej funkcji niezależnie.
Pozostałe zdefiniowane wcześniej metody modelu należy przedefiniować tak, aby pobierały odpowiednie dane z bazy. Oczywiście nie podaję tu konkretnych zapytań, gdyż jak pisałam te będą zależały od tego jak skonstruowana będzie baza.
private function get_title($i_Active){ $o_Records = $this->db->select(...)->...->get(); //tu trzeba pobrać dane do tablicy } private function get_main_menu($i_Active){ $o_Records = $this->db->select(...)->...->get(); //tu trzeba pobrać dane do tablicy } private function get_submenu($i_Active,$i_Subactive){ $o_Records = $this->db->select(...)->...->get(); //tu trzeba pobrać dane do tablicy } private function get_sections($i_Active,$i_Subactive){ $o_Records = $this->db->select(...)->...->get(); //tu trzeba pobrać dane do tablicy } public function get_content($i_Active,$i_Subactive){ $this->a_Content['title'] = $this->get_title($i_Active); $this->a_Content['main_menu'] = $this->get_main_menu($i_Active); $this->a_Content['submenu'] = $this->get_submenu($i_Active,$i_Subactive); $this->a_Content['sections'] = $this->get_sections($i_Active,$i_Subactive); }
Jak widać za każdym razem po wykonaniu zapytania przeba jeszcze przeanalizować uzyskane rekordy i pobrać je do tablicy, która później zostanie zwrócona do kontrolera a z niego przekazana do widoku. Można to zrobić za pomocą bardzo prostej pętli
$o_Records = $this->db->select('ID,name')->...->get(); if($o_Records->count()){ foreach($o_Records as $o_Row){ $a_Category[$o_Row->ID] = $o_Row->name; } return $a_Category; }else return false;
Najpierw za pomocą warunku sprawdzamy, czy w ogóle udało nam się pobrać cokolwiek z bazy. Najlepiej wykorzystać do tego metodę Database::count(), która zwraca ilość pobranych rekordów. Następnie wykonujemy pętlę, która przeczesuje wszystkie rekordy po kolei i zwraca każdy z nich w formie obiektu. Pola tego obiektu odpowiadają polom z tabeli jakie zostały wyszczególnione w zapytaniu do pobrania (w naszym przypadku pobieramy z jakiejś tabeli pola ID i name). I to właściwie wszystko co trzeba zrobić, żeby zamiast ze statycznych tablic pobierać dane z bazy danych.
Mały błąd literowy. Pod nagłówkiem: Konstrukcja modelu w kodzie jest napisane: privare $db;
:)
Dzięki, już poprawiłam :)
Wszystko dużo prostsze niż się wydawało, nie spodziewałem się że opanuje trzy tematy w jeden dzień, dziękuje za świetne materiały. Będę zaglądał jeszcze na bloga zapewne, jeszcze raz dziękuje :)
Jeszcze tylko jedna mała uwaga. Nie ma konieczności ładowania DB, jest ona ładowana domyślnie :)
Nie do końca rozumiem co masz na myśli pisząc o domyślnym ładowaniu DB.
Świetna seria tutoriali dla początkujących z kohaną! Czekam na kolejne pokazujące w praktyce wykorzystanie możliwości kohany. Deweloperzy kohany nie kwapią się do zamieszczania takich rzeczy na oficjalnych stronach więc to co znajduje się na Twoim blogu posłuży pewnie sporej liczbie osób. Życzę zapału do pisania bo robisz świetną robotę! Pozdrawiam.
Witam, bardzo ciekawy blog. Cieszę się, że ktoś pisze o Kohana, czekam na kolejne artykuły:).
Apropo domyślnego ładowania – klasa Model ma składową $db, do której w konstruktorze przypisywana jest instancja połączenia domyślnego. Więc jeżeli dziedziczymy po klasie Model to tak, jak zauważył pedro84 nie jest potrzebny zapis $this->db = new Database(); Chyba, że chcielibyśmy używać połączenia innego, niż default, to wówczas musimy nową instancję faktycznie utworzyć, niezapominając o podaniu jej nazwy. Powyższa uwaga dotyczy wersji 2.3. Pozdrawiam:)
Świetna seria tutoriali dla początkujących :)
W jeden dzień opanowałem kilka tematów. Mam nadzieję, że to nie koniec artykułów ;)
Pozdrawiam,
RW
Ja też mam taką nadzieję. W każdym razie kolejne artykuły już rozpoczęte :)
Super! ;)
Mam też pytanie – czy będzie coś o tworzeniu backendu strony z uzyciem Kohany? :)
Nie zastanawiałam się nad tym, ale może coś niecoś o tym napiszę :)
Ta linia w konstruktorze modelu jest wg mnie zbędna:
$this->db = new Database();
Jeżeli używasz parent::__construct(); to automatycznie do $this->db ładowana jest obsługa Database:)
Nie musisz również deklarować zmiennej private $db; :)
Zgadza się, ale już ktoś wcześniej zwrócił mi na to uwagę :)
Witam,
Znowu utknąłem :( Piszesz, że cyt: “Wystarczy więc w skonstruowanym poprzednio kontrolerze zastąpić wszystkie publiczne metody jedną uniwersalną:
public function index($i_Active, $i_Subactive=0){
$this->_set_content($i_Active,$i_Subactive);
}”
Czy tylko to należy zmienić, aby obsłużyć tą metodę? Zakopałem się w tej strukturze i nie mogę się odnaleźć. W którym momencie kodu jest później wywoływana metoda index()?
Ok, nie ważne ;) Już sobie przypomniałem odnośnie tej metody ;) Jest to główna metoda, która jest domyślnie wywoływana w kontrolerze. Teraz tylko męczę się z przerobieniem kodu, tak, żeby prawidłowo uruchamiały się subbmenmu. Aha, no i pierwsza sprawa, to w metodzie
public function index($i_Active, $i_Subactive=0){..} zmienna $i_Active powinna przyjąć jakieś wartości domyślnie. np $i_Active=0?