Blog tutorial – część 5

Ostatnio zajęliśmy się procesem logowania do naszej aplikacji – dzisiaj nadszedł czas na dodawanie wpisów do naszego bloga.

Wszystko odbywać się będzie za pośrednictwem kontrolera Posts oraz modelu Post_model. Przypomnę jeszcze szybko założenia: przeglądać wpisy mogą wszyscy użytkownicy, jednak dodawanie i ich edycja odbywa się tylko za pośrednictwem uwierzytelnionego użytkownika. Przy przeglądaniu listy wpisów dostępne będzie stronicowanie (paginacja). Zatem do dzieła, zacznijmy od modelu i utwórzmy plik post_model.php w katalogu application/models:

<?php defined('BASEPATH') OR exit('No direct script access allowed');

class Post_model extends CI_Model
{
    // Nazwa tabeli, z której będziemy korzystać w modelu
    public $table = 'posts';
    
    /**
     * Zwraca wpisy z podanego przedziału
     *
     * @access  public
     * @param   int Limit
     * @param   int Offset
     * @return  mixed
     */
    public function get_all($limit, $offset)
    {
        return $this->db
                    ->limit($limit, $offset)
                    ->order_by($this->table.'.id', 'desc')
                    ->get($this->table)
                    ->result_array();
    }

    /**
     * Zwraca łączną liczbę wszystkich wpisów w tabeli
     *
     * @access  public
     * @return  int
     */
    public function count_all()
    {
        return $this->db->count_all($this->table);
    }

    /**
     * Zwraca wpisy o podanym numerze Id
     *
     * @access  public
     * @param   int Numer Id wpisu
     * @return  mixed
     */
    public function get($id)
    {
        return $this->db->where('id', $id)->get($this->table)->row_array();
    }

    /**
     * Dodaje wpis do tabeli
     *
     * @access  public
     * @param   array Dane do dodania
     * @return  bool
     */
    public function add($data)
    {
        return $this->db->insert($this->table, $data);
    }

    /**
     * Aktualizuje wpis w tabeli
     *
     * @access  public
     * @param   int Numer Id
     * @param   array Dane do zaktualizowania
     * @return  bool
     */
    public function update($id, $data)
    {
        return $this->db->where('id', $id)->update($this->table, $data);
    }
}
/* End of file user_model.php */
/* Location: ./application/models/user_model.php */

W kontrolerze Posts znajdują się trzy metody. Każda z nich jest odpowiedzialna kolejno, za: wyświetlanie listy wpisów, dodawanie wpisu oraz jego edycję. Warto zwrócić uwagę na to w jaki sposób sprawdzamy w konstruktorze, czy dany użytkownik ma prawo dostępu do konkretnej metody. Plik application/controllers/posts.php:

<?php defined('BASEPATH') OR exit('No direct script access allowed');

class Posts extends CI_Controller
{
    // W tej tablicy przechowujemy nazwy metod, do których ma dostęp jedynie zalogowany użytkownik
    public $restricted = array('add', 'edit');

    public function __construct()
    {
        parent::__construct();

        // Sprawdzamy, czy obecnie wywoływana metoda znajduje się w tablicy $resricted
        if (in_array($this->uri->rsegment(2), $this->restricted))
        {
            // Sprawdzamy, czy użytkownik jest zalogowany poprzez sprawdzenie istnienia zmiennej sesyjnej 'user_id'.
            // Ta zmienna jest ustawiana tylko w momencie poprawnego zalogowania.
            if ( ! $this->session->userdata('user_id'))
            {
                // Wyświetlamy stonę błędu, ale równie dobrze możemy zwrócic inny komunikat,
                // np. taki, który informuje o konieczności zalogowania do aplikacji lub 
                // przekierować użytkownika do strony logowania.
                show_404();
            }
        }
        // Ładujemy bibliotekę walidacji formularza
        $this->load->library('form_validation');
        // Określamy jakie tagi będą otaczać komunikat błędu walidacji. 
        // To kwestia stricte kosmetyczna - dostosowanie wyglądu do Twitter Bootstrap.
        $this->form_validation->set_error_delimiters('<div class="alert alert-error"><a class="close" data-dismiss="alert" href="#">×</a>', '</div>');

        // Ładujemy model
        $this->load->model('post_model');
    }

    public function index()
    {
        // Ładujemy bibliotekę odpowiedzialną za stronicowanie
        $this->load->library('pagination');

        // Określamy początkowy adres url dla paginacji
        $config['base_url'] = site_url('posts/index/');
        // Zliczamy ilość wszystkich wpisów
        $config['total_rows'] = $this->post_model->count_all();
        // Określamy liczbę wpisów na stronie
        $config['per_page'] = 1;

        // Zmienne odpowiedzialne za ustalenie wyglądu paginacji (dla Twitter Bootstrap)
        $config['full_tag_open'] = '<div class="pagination"><ul>';
        $config['full_tag_close'] = '</ul></div>';
        $config['first_link'] = false;
        $config['last_link'] = false;
        $config['first_tag_open'] = '<li>';
        $config['first_tag_close'] = '</li>';
        $config['prev_link'] = '&larr;';
        $config['prev_tag_open'] = '<li class="prev">';
        $config['prev_tag_close'] = '</li>';
        $config['next_link'] = '&rarr;';
        $config['next_tag_open'] = '<li>';
        $config['next_tag_close'] = '</li>';
        $config['last_tag_open'] = '<li>';
        $config['last_tag_close'] = '</li>';
        $config['cur_tag_open'] =  '<li class="active"><a href="#">';
        $config['cur_tag_close'] = '</a></li>';
        $config['num_tag_open'] = '<li>';
        $config['num_tag_close'] = '</li>';
        
        // Inicjalizujemy bibliotekę paginacji z powyższymi ustawieniami
        $this->pagination->initialize($config);

        // Przypisujemy do zmiennej numery dostępnych stron
        $data['pagination'] = $this->pagination->create_links();
        
        // Wczytujemy wpisy, jako parametry podając kryteria
        $data['posts'] = $this->post_model->get_all($config['per_page'], $this->uri->rsegment(3));

        // Zwracamy widoki, przypisując do jednego z nich tablicę $data
        $this->load->view('partials/header');
        $this->load->view('post_index', $data);
        $this->load->view('partials/footer');
    }

    public function add()
    {   
        // Ustalamy reguły walidacji
        $this->form_validation->set_rules('title', 'Tytuł', 'required|trim');
        $this->form_validation->set_rules('body', 'Treść', 'required|trim');

        // Sprawdzamy, czy formularz został wysłany i czy wystąpiły błędy walidacji
        if ($this->form_validation->run() == FALSE)
        {
            // Wyświetlamy widoki - ewentualne błędy walidacji zostaną przekazane automatycznie
            $this->load->view('partials/header');
            $this->load->view('post_add');
            $this->load->view('partials/footer');
        }
        else
        {
            // Przypisujemy zmienne POST z formularza do tablicy $data
            $data['title'] = $this->input->post('title');
            $data['body'] = $this->input->post('body');
            // Ustawiamy zmienną $data['user_id'] na identyfikator zalogowanego użytkownika, 
            // który przechowujemy w sesji (ustawiany jest w momencie logowania). 
            $data['user_id'] = $this->session->userdata('user_id');
            // Wysyłamy tablicę $data do modelu i w zależności od zwróconego wyniku wykonujemy poniższe czynności
            if ($this->post_model->add($data))
            {
                // Ustawiamy zmienną flashadata o nazwie success i przypisujemy do niej komunikat o powodzeniu dodania wpisu.
                $this->session->set_flashdata('success', 'Wpis został dodany.');                
            }
            else
            {
                // Ustawiamy zmienną flashadata o nazwie error i przypisujemy do niej komunikat o błędzie.
                $this->session->set_flashdata('error', 'Wystąpił błąd i wpis nie mógł zostać dodany.');
            }
            // Przekierowujemy użytkownika pod adres http://localhost/blogtutorial/index.php/posts
            redirect('posts');
        }
    }

    public function edit($id)
    {
        // Ustalamy reguły walidacji
        $this->form_validation->set_rules('title', 'Tytuł', 'required|trim');
        $this->form_validation->set_rules('body', 'Treść', 'required|trim');

        // Sprawdzamy, czy formularz został wysłany i czy wystąpiły błędy walidacji
        if ($this->form_validation->run() == FALSE)
        {
            // Pobieramy dane z modelu po zmiennej $id
            $data = $this->post_model->get($id);

            // Przypisujemy otrzymane dane do widoku
            // Ewentualne błędy walidacji zostaną przekazane automatycznie
            $this->load->view('partials/header');
            $this->load->view('post_edit', $data);
            $this->load->view('partials/footer');
        }
        else
        {
            // Przypisujemy zmienne POST z formularza do tablicy $data
            $data['title'] = $this->input->post('title');
            $data['body'] = $this->input->post('body');

            // Wysyłamyzmienną $id i tablicę $data do modelu i w zależności od zwróconego wyniku wykonujemy poniższe czynności
            if ($this->post_model->update($id, $data))
            {
                // Ustawiamy zmienną flashadata o nazwie success i przypisujemy do niej komunikat o powodzeniu dodania wpisu.
                $this->session->set_flashdata('success', 'Wpis został zaktualizowany.');
                // Przekierowujemy użytkownika pod adres http://localhost/blogtutorial/index.php/posts  
                redirect('posts');          
            }
            else
            {
                // Ustawiamy zmienną flashadata o nazwie error i przypisujemy do niej komunikat o błędzie.
                $this->session->set_flashdata('error', 'Wystąpił błąd i wpis nie mógł zostac zaktualizowany.');
                // Przekierowujemy użytkownika pod adres edycji wpisu http://localhost/blogtutorial/index.php/posts/edit/numer_id_wpisu 
                redirect('posts/edit/'. $id);
            }
        }
    }
    
}
/* End of file posts.php */
/* Location: ./application/controllers/posts.php */

Teraz pora na widok, który jest odpowiedzialny za generowanie listy wpisów. Warto zwrócić uwagę na to, w jaki sposób sprawdzamy, czy dany użytkownik powinien zobaczyć przycisk edycji danego wpisu. Plik application/views/post_index.php":

<div class="row">
    <div class="span12">
        <!-- Sprawdzamy, czy są jakiekolwiek wpisy w bazie danych -->
        <?php if ($posts): ?>
            <div class="page-header">
                <h1>Dostępne wpisy</h1>
            </div>
            <!-- Dla każdego wpisu wykonujemy pętlę -->
            <?php foreach ($posts as $p): ?>
                <h2><?php echo htmlspecialchars($p['title']); ?></h2>
                <p><?php echo nl2br(htmlspecialchars($p['body'])); ?></p>
                <!-- Sprawdzamy, czy użytkownik jest zalogowany -->
                <?php if ($this->session->userdata('user_id')): ?>
                    <div>
                        <a href="<?php echo site_url('posts/edit/'.$p['id']); ?>" class="btn"><i class="icon-edit"></i> <strong>Edytuj</strong></a>
                    </div>
                <?php endif; ?>
            <?php endforeach; ?>
            <hr>
            <!-- Wyświetlamy kod html odpowiedzialny za paginację s-->
            <?php echo $pagination; ?>
        <?php else: ?>
            <div class="well">
                Brak wpisów w bazie danych.
            </div>
        <?php endif; ?>
    </div>
</div>

Zarówno w widoku post_add.php jak i post_edit.php korzystamy z funkcji set_value(), aby umieścić wartość dla danego pola w formularzu. Jeśli chciałbyś dowiedzieć się więcej o przetwarzaniu formularzy, powinieneś zapoznać się z klasą Form_validation. Plik application/views/post_add.php:

<div class="page-header">
	<h1>Dodaj wpis</h1>
</div>

<div class="row">
  	<div class="span12">
  		<!-- Otwieramy formularz -->
    	<?php echo form_open('posts/add', array('class'=>'form-horizontal')); ?>
      		<fieldset>
        		<div class="control-group <?php echo form_error('title') ? 'error' : ''; ?>">
					<label class="control-label" for="title">Tytuł</label>
					<div class="controls">
						<input type="text" class="input-xlarge" id="title" name="title" value="<?php echo set_value('title'); ?>">
					</div>
        		</div>
		        <div class="control-group <?php echo form_error('body') ? 'error' : ''; ?>">
		        	<label class="control-label" for="body">Treść</label>
		          	<div class="controls">
		            	<textarea class="span10" id="body" name="body" rows="8" placeholder="Dodaj tekst..."><?php echo set_value('body'); ?></textarea>
		          	</div>
		        </div>
	        	<div class="form-actions">
	          		<button type="submit" class="btn btn-primary">Dodaj wpis</button>
	          		<a class="btn" href="<?php echo site_url('posts'); ?>">Anuluj</a>
	        	</div>
      		</fieldset>
      	<!-- Zamykamu formularz -->
    	<?php echo form_close(); ?>
  	</div>
</div>

Plik application/views/post_edit.php:

<div class="page-header">
	<h1>Edytuj wpis: <?php echo $title; ?></h1>
</div>

<div class="row">
  	<div class="span12">
  		<!-- Otwieramy formularz -->
    	<?php echo form_open('posts/edit/'.$id, array('class'=>'form-horizontal')); ?>
	      	<fieldset>
	        	<div class="control-group <?php echo form_error('title') ? 'error' : ''; ?>">
		          	<label class="control-label" for="title">Tytuł</label>
		          	<div class="controls">
		            	<input type="text" class="input-xlarge" id="title" name="title" value="<?php echo set_value('title', $title); ?>">
		          	</div>
	        	</div>
		        <div class="control-group <?php echo form_error('body') ? 'error' : ''; ?>">
			        <label class="control-label" for="body">Treść</label>
			        <div class="controls">
			        	<textarea class="span10" id="body" name="body" rows="8" placeholder="Dodaj tekst..."><?php echo set_value('body', $body); ?></textarea>
			        </div>
		        </div>
		        <div class="form-actions">
			        <button type="submit" class="btn btn-primary">Zapisz zmiany</button>
			        <a class="btn" href="<?php echo site_url('posts'); ?>">Anuluj</a>
		        </div>
	      	</fieldset>
	    <!-- Zamykamy formularz -->
    	<?php echo form_close(); ?>
  	</div>
</div>

To tyle. Dysponujemy teraz w pełni funkcjonalną aplikacją – a właściwie taką, która spełnia wszystkie nasze założenia, jakich dokonaliśmy na samym początku.

Podczas tej krótkiej serii, mogliście zobaczyć w jaki sposób można przygotować makietę aplikacji i jakimi narzędziami można się posłużyć, aby zbudować schemat bazy danych. W dalszej części zapoznaliśmy się z migracjami w CodeIgniterze. Na koniec stworzyliśmy prosty blog, który pozwala na zalogowanie się użytkownika, kontrolę dostępu do poszczególnych części aplikacji oraz dodawanie i edycję wpisów. Mogliśmy w praktyce zastosować takie biblioteki jak Database, Form_validation, czy Pagination oraz kilka helperów.

Starałem się nie stosować długich opisów i wszystkie potrzebne komentarze zamieszczać w kodzie – bo właśnie czytanie kodu jest najlepszym sposobem na poznanie tego, w jaki sposób pracować z danym frameworkiem. Mam nadzieję, że chociaż w małym stopniu spowoduje to, że chętniej będziecie zaglądać w cudzy kod w poszukiwaniu jeszcze lepszych rozwiązań i doskonaląc tym samym swoje własne umiejętności.

Jeśli to Wasze początki z CodeIgniterem i macie jakieś problemy (ale nie tylko), to pamiętajcie, że zawsze możecie zajrzeć na nasze forum i zadać pytanie. Bardzo chętnie Wam pomożemy.

Na koniec – cały kod źródłowy blog tutorialu jest dostępny na githubie: https://github.com/codeigniter-polska/blog-tutorial

11 komentarzy do “Blog tutorial – część 5”

  1. Ładnie, bardzo ładnie tego można było się po tobie spodziewać ;)

    Dobrze, że pokazałeś chaining klasy DB, konfigurację paginacjii proponował bym przenosić do APPPATH/config/pagination.php żeby zminimalizować ilość kodu kontrolerów, to samo z walidacją.

    Ogólnie to moim zdaniem bardzo dobry tutek.

    Odpowiedz
    • Dzięki za miłe słowo :)

      Rzeczywiście masz rację z tym przeniesieniem danych konfiguracyjnych, chociaż sam bardzo rzadko to stosuję (to chyba kwestia przyzwyczajenia).

      Odpowiedz
  2. Hej, mam problem z sesją, robiłem wszystko według tego co pisałeś w tym tutorialu i mam błąd: In order to use the Session class you are required to set an encryption key in your config file.
    Co ciekawe ten sam błąd pojawia się gdy otwieram Twój kod dostępny z repozytorium.

    Odpowiedz
    • Witaj Grzegorz.

      Tak jak wskazuje na to komunikat błędu, musisz ustawić zmienną „encryption_key” dla sesji. Możesz to zrobić otwierając plik „application/config/config.php” – w okolicach 230 linijki znajdziesz wspomnianą zmienną, która w tej chwili jest pusta. Wystarczy podać tam jakąkolwiek wartość.

      Opis rozpoczęcia pracy z CI znajdziesz też tutaj: http://www.codeigniter.org.pl/rozpoczecie-pracy-z-codeigniter/

      Odpowiedz
  3. Witam

    A czy pokaża się inne tutoriale wykorzystujące codeigniter. Jestem początkujący i dopiero zaczynam przygode z MVC, obiektówką i Codeigniter. Szczeże mówiąc to nawet nie wiem o co pytać ale jakieś tutki z wykorzystaniem baz danych łączenie zapytań przez join itp…

    w strukturalnym wydawało mi się o wiele prościej chodź już na początku widać równice na lepsze, żałuje ze wcześniej nie przyjżałem się temu freamworkowi.

    Bardzo fajnie, że powstała taka strona bo cięzko znaleść jakieś informacje na polskich forach.
    Dla informacji lutym ukaże się książka w helion.pl na temat Codeigniter:

    „CodeIgniter. Zaawansowane tworzenie stron w PHP” – Łukasz Sosna
    Planowany termin wydania: luty 2013

    Pozdrawiam

    Odpowiedz
    • Witaj.

      O książce wiem już od pewnego czasu – jeśli się ukaże, to z pewnością coś o niej skrobniemy.

      Co do tutoriali, to pewnie tak (chociaż potrzeba do tego trochę wolnego czasu z naszej strony). Jeśli będziesz miał jakieś konkretne propozycje, to daj znać.

      Jeśli chodzi o korzystanie z biblioteki bazy danych, to tutaj nie ma zbyt dużej filozofii – myślę, że wystarczy się zapoznać z odpowiednim rozdziałem z podręcznika (http://podrecznik.codeigniter.org.pl/database/index.html). Biorąc pod uwagę fakt, że ten rozdział jest w całości przetłumaczony (o ile mnie pamięć nie myli), nie powinno być większych problemów ze zrozumieniem co i jak.

      Odpowiedz
  4. Cześć mam pytanie czy można zrobić indywidualna wiadomość błędu za pomocą form_error() dla radio buttona? Czy tego akurat CI nie obsługuje? Dla inputa, selecta działa super natomiast dla radio nie chce działać. pozdrawiam

    Odpowiedz

Dodaj komentarz

Ta strona używa Akismet do redukcji spamu. Dowiedz się, w jaki sposób przetwarzane są dane Twoich komentarzy.