Do tej pory nauczyliśmy się:
- Ciąć szablony HTML na widoki
- Tworzyć kontrolery, widoki i modele
- Operować na bazie danych
- Tworzyć formularze, walidować dane
- Stworzyć prosty
Blog zawierający:
- Moduł Newsów
- Stronicowanie newsów na stronie głównej
- Słowa kluczowe
- Wyszukiwarka
- Moduł Komentarzy
- Dodawanie komentarzy przez użytkowników do danego newsa
UWAGA użytkownicy PHP4
W przykładach może pojawić się wywołanie więcej niż 1 metody na raz:
$obiekt->metoda1()->metoda2();
W PHP5 to zadziała lecz nie w PHP4. Należy rozbijać takie wywołania
$x = $obiekt->metoda1();
$x->metoda2();
Jako szablon wybrałem jesienny "Autumn 05" (link w warsztacie cięcia szablonów). Jest on podobny do Metrohackera. Zmiany wymagało:
:
- ścieżka do pliku CSS (umieszczony w /views)
- usunięcie kodu z index.html:
<?xml version="1.0" encoding="UTF-8"?>
- Szablon na starcie wyglądał tak:

- Korzystając przy tym z kontrolera "pomocniczego":
class Blog extends Controller
{
function index()
{
$this->load->view('index');
}
}
- Po tych zmianach zmieniłem nazwię pliku na index.php i rozpocząłem cięcie. Rozdzieliłem komórkę lewą i "newsa" ze środka do oddzielnych plików (left.php i main.php)
- Po rozdzieleniu i ładowaniu obu pomocniczych widoków szablon wyglądał tak:

- Wszystko gotowe. Dokonałem jeszcze drobych poprawek w głównym szablonie zmieniając tytuł i takie tam by otrzymać:
Teraz rozszerzymy nieco "cięcie" szablonów do widoków. Otóż widok index.php to szkielet strony, niezmienny dla wszystkich modułów. Zmieniać się będzie zawartość głównej kolumny i ew. lewych bloków. Obecnie index.php ładuje widok "main" i dla każdego kontrolera np. wyświetlającego newsy czy komentarze trzeba byłoby tworzyć kopie i "main" (co jest oczywiste) jak i widoku "index" by ładował odpowiednią wersję widoku "main".
Po prostu
< ?PHP $this->load->view('main'); ?>
Zastąpię
< ?PHP echo $content ?>
I pod zmienną
$content zawsze będę podstawiał wynik odpowiedniego widoku "main" :) Próba wyświetlenia szablonu po podmianie wyrzuci "
Undefined variable: content" - co zrozumiałe - zmienna $content nie istnieje, kontroler jej (jeszcze) nie przekazuje. Nie należy panikować.
By stworzyć tabele można skorzystać z phpMyAdmina albo jak go nie mamy z metody $this->db>query() wykonującej nasze zapytanie SQL. Jeżeli chcesz skorzystać z tej drugiej opcji to musisz najpierw skonfigurować dane bazy danych w CI o czym napiszę za chwilę. Jeżeli korzystasz z bazy SQLite czy PostgreSQL zapytania będą podobne lecz nie identyczne. Najpierw tabele. Dla
modułu newsów skorzystam z rozbudowanej wersji tabeli z poprzedniego warsztatu. Tabela tworzona poleceniem SQL:
CREATE TABLE kurs_news (
news_id smallint(5) unsigned NOT NULL auto_increment,
news_title varchar(255) default NULL,
news_text text,
news_author varchar(255) default NULL,
news_date int unsigned NOT NULL,
news_keywords varchar(255) default NULL,
PRIMARY KEY (news_id)
) ENGINE=MyISAM;
Zawiera trzy dodatkowe pola -
news_author na nazwę autora newsa,
news_date - data publikacji,
news_keywords - słowa kluczowe.
Tabela komentarzy wygląda podobnie:
CREATE TABLE kurs_comments (
com_id smallint(5) unsigned NOT NULL auto_increment,
news_id smallint(5) unsigned NOT NULL,
com_title varchar(255) default NULL,
com_text text,
com_author varchar(255) default NULL,
com_date int unsigned NOT NULL,
PRIMARY KEY (com_id)
) ENGINE=MyISAM;
Nie ma pola na słowa kluczowe ale jest pole
news_id. Będzie ono przechowywało numer ID news do którego jest to komentarz. Unikalność wartości pola
auto_increment pozwala nam na tak łatwe rozwiązanie problemu.
Model newsów w porównaniu z ostatnim warsztatem poszerzy się o jedną metodę:
<?PHP
class News extends Model
{
function News()
{
parent::Model();
}
function get_news()
{
// Wszystkie newsy sortowane malejąco po news_id
$this->db->orderby("news_id", "desc");
return $this->db->get('news');
}
function get_news_by_id($id)
{
// Pobiera określony news
$this->db->where('news_id', $id);
return $this->db->get('news');
}
function get_news_by_keyword($keyword)
{
// Wszystkie newsy z danym słowem kluczowym sortowane malejąco po news_id
$this->db->like($keyword);
$this->db->orderby("news_id", "desc");
return $this->db->get('news');
}
function add_news($data)
{
// dodanie newsa
return $this->db->insert('news', $data);
}
function update_news($id, $data)
{
// zmiana newsa o podanym numerze news_id
$this->db->where('news_id', $id);
return $this->db->update('news', $data);
}
function delete_news($id)
{
// skasowanie newsa o podanym news_od
$this->db->where('news_id', $id);
return $this->db->delete('news');
}
}
?>
Mamy metodę
get_news_by_keyword, która zwraca newsy dla danego słowa kluczowego. W naszym przykładzie nie stosujemy kategorii lecz luźne słowa kluczowe, które obecnie są "trendy" :) Po prostu jest to łańcuch zawierający jakieś słowa oddzielone przecinkami. Zapytanie z warunkiem LIKE zwróci te newsy, które mają w polu "news_keywords" podany element (słowo kluczowe).
Model komentarzy wygląda już nieco inaczej (comments.php):
class Comments extends Model
{
function Comments()
{
parent::Model();
}
function get_comments_for_news($news_id)
{
// Komentarze dla danego newsa
$this->db->where('news_id', $news_id);
$this->db->orderby("com_id", "asc");
return $this->db->get('comments');
}
function add_comment($data)
{
// dodanie komentarza
return $this->db->insert('comments', $data);
}
function delete_comment($id)
{
// skasowanie komentarza o podanym ID
$this->db->where('com_id', $id);
return $this->db->delete('comments');
}
function are_comments_for_news($news_id)
{
// ile jest komentarzy dla danego newsa
// tego Active Records nie potrafi
return $this->db->query("SELECT COUNT(*) AS comnumber FROM ".$this->db->dbprefix."comments WHERE news_id = '".$news_id."'");
}
}
Zrezygnowałem z opcji edycji komentarzy, mało przydatne (jak na razie). Mamy metodę
are_comments_for_news, która zwraca liczbę komentarzy dla danego newsa (określonego jego numerem ID). SELECT COUNT to najwydajniejszy sposób liczenia wierszy jaki zostałyby zwrócone. Pobranie np. numerów id i wykonanie funkcji PHP "count" na tablicy wyników to najgorsze rozwiązanie. Fraza
AS comnumber oznacza że wynik będzie dostępny pod podanym aliasem (traktować przy wyświetlaniu tak jakby to było zwykłe pole).
Teraz warto odwiedzić katalog konfiguracyjny naszego projektu. Z
autoload.php:
$autoload['libraries'] = array('database', 'validation');
$autoload['helper'] = array('url', 'form');
database.php - podajemy dane bazy danych. Powyższe zapytania SQL tworzą tablice kurs_nazwa co oznacza (w modelach nie ma kurs_) prefiks:
$db['default']['dbprefix'] = "kurs_";
routes.php nazwa domyślnego kontrolera. U mnie blog:
$route['default_controller'] = "blog";
Zabieramy się za nasz kontroler
Blog. Listowanie wszystkich newsów na stronie głównej zrobić łatwo:
class Blog extends Controller
{
// konstruktor
function Blog()
{
parent::Controller();
$this->response = array();
}
function index()
{
$this->load->model('News');
$query = $this->News->get_news();
$content = '';
// czy są jakieś wiersze?
if ($query->num_rows() > 0)
{
foreach($query->result() as $item)
{
$content .= $this->load->view('news_loop', $item, True);
}
}
else
{
// brak newsów
$content = '<h1>Brak newsów</h1>';
}
// przekazanie danych do szablonu
$this->response['content'] = $content;
$this->load->view('index', $this->response);
}
}
Tablica, która
zawsze będzie trafiała do widoku
index jest
$this->response - globalna w klasie tablica zainicjowana w konstruktorze. (może okazać się to pomocne w dalszych etapach). Odnośnie metody
index - ładujemy model
News i wywołujemy metodę
$query = $this->News->get_news(); co zwraca uchwyt do wyników. Za pomocą
num_rows upewniamy się czy jakieś dane są, jeżeli tak to możemy otworzyć pętlę foreach i je wyświetlić.
Widok news_loop to nic innego jak przerobiony widok
main w postaci:
<h1><?PHP echo $news_title ?></h1>
<p class="date"><?PHP echo date("Y-m-d", $news_date); ?></p>
<p><?PHP echo $news_text; ?></p>
<div class="rule"></div>
Obecnie strona główna naszego bloga pokaże:

Teraz musimy dodać możliwość dodawania i edycji wpisów. Wszystko co ma znaleźć się w tzw. "Panelu Admina" umieścimy w oddzielnym kontrolerze o nazwie Admin:
class Admin extends Controller
{
// konstruktor
function Admin()
{
parent::Controller();
$this->response = array();
}
function index()
{
$this->response['content'] = '<h1><a href="'.site_url('admin/news_add').'">Dodaj Newsa</a></h1>';
// wylistujmy sobie newsy (tytuły) z linkami do edycji i kasowania
$this->load->model('News');
$query = $this->News->get_news();
if ($query->num_rows() > 0)
{
$this->response['content'] .= '<table width="100%" border="1" cellspacing="3" cellpadding="3">';
foreach($query->result() as $item)
{
$this->response['content'] .= $this->load->view('news_loop_admin', $item, True);
}
$this->response['content'] .= '</table>';
}
$this->load->view('index', $this->response);
}
// dodanie newsa
function news_add()
{
$data["tytul"] = array('name' => 'tytul');
$data['tresc'] = array('name' => 'tresc', 'rows' => 8, 'cols' => 50);
$data['autor'] = array('name' => 'autor');
$data['keywords'] = array('name' => 'keywords');
$rules['tytul'] = "required|max_length[250]|xss_clean";
$rules['tresc'] = "required|xss_clean";
$rules['keywords'] = "required|max_length[250]|xss_clean";
$this->validation->set_rules($rules);
if ($this->validation->run() == FALSE)
{
$data['tytul']['value'] = $this->input->post('tytul');
$data['tresc']['value'] = $this->input->post('tresc');
$data['autor']['value'] = $this->input->post('autor');
$data['keywords']['value'] = $this->input->post('keywords');
$this->response['content'] = $this->load->view('news_add', $data, True);
}
else
{
$this->load->model('News');
$this->News->add_news(array('news_title' => $this->input->post('tytul'), 'news_text' => $this->input->post('tresc'), 'news_author' => $this->input->post('autor'), 'news_date' => time(), 'news_keywords' => $this->input->post('keywords')));
$this->response['content'] = '<h1>Dane zapisane</h1><META HTTP-EQUIV="Refresh" CONTENT="1; URL='.site_url('admin').'">';
}
$this->load->view('index', $this->response);
}
// edycja newsa
function news_edit()
{
$this->load->model('News');
$id = $this->uri->segment(3);
IF(isset($id) and is_numeric($id))
{
$ar = $this->News->get_news_by_id($id)->result_array();
$data["tytul"] = array('name' => 'tytul');
$data['tresc'] = array('name' => 'tresc', 'rows' => 8, 'cols' => 50);
$data['autor'] = array('name' => 'autor');
$data['keywords'] = array('name' => 'keywords');
$rules['tytul'] = "required|max_length[250]|xss_clean";
$rules['tresc'] = "required|xss_clean";
$rules['keywords'] = "required|max_length[250]|xss_clean";
$this->validation->set_rules($rules);
if ($this->validation->run() == FALSE)
{
IF(strlen($this->input->post('tytul')) > 0)
{
$data['tytul']['value'] = $this->input->post('tytul');
$data['tresc']['value'] = $this->input->post('tresc');
$data['autor']['value'] = $this->input->post('autor');
$data['keywords']['value'] = $this->input->post('keywords');
}
else
{
$data['tytul']['value'] = $ar[0]['news_title'];
$data['tresc']['value'] = $ar[0]['news_text'];
$data['autor']['value'] = $ar[0]['news_author'];
$data['keywords']['value'] = $ar[0]['news_keywords'];
}
$this->response['content'] = $this->load->view('news_edit', $data, True);
}
else
{
$this->News->update_news($id, array('news_title' => $this->input->post('tytul'), 'news_text' => $this->input->post('tresc'), 'news_author' => $this->input->post('autor'), 'news_date' => time(), 'news_keywords' => $this->input->post('keywords')));
$this->response['content'] = '<h1>Zmiany zapisane</h1><META HTTP-EQUIV="Refresh" CONTENT="1; URL='.site_url('admin').'">';
}
}
else
{
$this->response['content'] = '<h1>Niepoprawny URL</h1>';
}
$this->load->view('index', $this->response);
}
// usunięcie newsa
function news_delete()
{
$id = $this->uri->segment(3);
IF(isset($id) and is_numeric($id))
{
$this->load->model('News');
$this->News->delete_news($this->uri->segment(3));
$this->response['content'] = '<h1>News skasowany</h1><META HTTP-EQUIV="Refresh" CONTENT="1; URL='.site_url('admin').'">';
$this->load->view('index', $this->response);
}
}
// usunięcie komentarza
function com_delete()
{
$id = $this->uri->segment(3);
IF(isset($id) and is_numeric($id))
{
$this->load->model('Comments');
$this->Comments->delete_comment($this->uri->segment(3));
$this->response['content'] = '<h1>Komentarz skasowany</h1><META HTTP-EQUIV="Refresh" CONTENT="1; URL='.site_url('admin').'">';
$this->load->view('index', $this->response);
}
}
}
5 metod, pierwsza,
index jest niejako główną stroną Panelu Admina i zawiera link do formularza dodawania Newsów oraz listę newsów wraz z linkami do edycji i kasowania. Widok
news_loop_admin wygląda tak:
<tr><td><?PHP echo $news_title ?></td><td width="100"><center>[<a href="<?PHP echo site_url('admin/news_edit/'.$news_id); ?>">Edytuj</a>]</center></td><td width="100">[<a href="<?PHP echo site_url('admin/news_delete/'.$news_id); ?>" >Kasuj</a>]</td></tr>
Przy linki
kasowania mamy dodatkow trochę javascriptu. Funkcja JS
confirm wyświetli okienko z pytaniem czy "Usnąć News?" i dwoma przyciskami "Anuluj" i "OK". W przypadku kliknięcia "Anuluj" nic się nie stanie.
Następnie mamy metodę dodającą dane, co już było przerabiane we wcześniejszym warsztacie, oraz drugą edytującą, która ma jedną większą zmianę:
IF(strlen($this->input->post('tytul')) > 0)
Jako że chcemy mieć dotychczasowe dane do edycji to musimy je przypisać do formularza jeżeli tej nie był jeszcze wysłany. Funkcja
strlen zwraca rozmiar podanego łańcucha. Jeżeli formularz nie został wysłany to długość łańcucha z jakiegoś jego pola na pewno będzie równa 0. Jeżeli jest większa znaczy że formularz został wysłany ale pojawiły się problemy - przypisujemy wartości z samego formularza.
W kodzie pojawia się:
$this->uri->segment(3)
Metoda
$this->uri>segment(ID) zwróci wartość danego segmentu URLa. Np. index.php/foo/bar rozbija się na składowe: 1 to "foo", 2 to "bar". Wewnątrz CI nie ma tablic _POST, _GET, _cookie czy _SERVER (ze względów bezpieczeństwa i nie tylko). By pobrać określony element trzeba użyć owej metody. W naszym przypadku do kasowania i edycji newsów potrzebny jest numer ID danego newsa. Zakładamy URLe postaci
index.php/news_OPERACJA/NUMER_ID i gotowe :)
Widok
news_add.php
<h1>Dodaj news</h1>
<center><?=$this->validation->error_string; ?></center>
<?php echo form_open('admin/news_add'); ?>
<table width="90%" border="0" cellspacing="3" cellpadding="3">
<tr><td width="180"><B>Tytuł</B></td><td><?php echo form_input($tytul); ?></td></tr>
<tr><td width="180"><B>Treść</B></td><td><?php echo form_textarea($tresc); ?></td></tr>
<tr><td width="180"><B>Autor</B></td><td><?php echo form_input($autor); ?></td></tr>
<tr><td width="180"><B>Słowa Kluczowe</B></td><td><?php echo form_input($keywords); ?></td></tr>
<tr><td> </td><td><?php echo form_submit('submit', 'Zapisz'); ?></td></tr>
</table>
<?php echo form_close(); ?>
<div class="rule"></div>
Widok
news_edit.php:
<h1>Edytuj news</h1>
<center><?=$this->validation->error_string; ?></center>
<?php echo form_open('admin/news_edit/'.$this->uri->segment(3)); ?>
<table width="90%" border="0" cellspacing="3" cellpadding="3">
<tr><td width="180"><B>Tytuł</B></td><td><?php echo form_input($tytul); ?></td></tr>
<tr><td width="180"><B>Treść</B></td><td><?php echo form_textarea($tresc); ?></td></tr>
<tr><td width="180"><B>Autor</B></td><td><?php echo form_input($autor); ?></td></tr>
<tr><td width="180"><B>Słowa Kluczowe</B></td><td><?php echo form_input($keywords); ?></td></tr>
<tr><td> </td><td><?php echo form_submit('submit', 'Zapisz'); ?></td></tr>
</table>
<?php echo form_close(); ?>
<div class="rule"></div>
Teraz gdy dodamy kilka newsów nasz blog zacznie powoli wyglądać:


- Logowanie do PA
- Dodawanie i usuwanie komentarza
- Wyszukiwarka
- Inne fajne dodatki