MVC w praktyce – tworzymy system artykułów. cz. 1
Tworząc różnego rodzaju aplikacje natrafiamy na poważny problem utrzymania dobrej organizacji kodu – przejrzystej oraz łatwej w rozbudowie. Z pomocą przychodzą nam wzorce projektowe, które wymuszają na nas pewną organizację kodu aplikacji. W świecie aplikacji www najbardziej popularny jest wzorzec MVC. Jego ideę pokażę w praktyce – pisząc prosty system artykułów.
Uwaga. Pojawił się zaktualizowany cykl o MVC – przejdź
Żeby w pełni zrozumieć ideę tego wzorca projektowego czytelnik musi mieć solidne podstawy znajomości PHP oraz potrafić programować obiektowo.
Trochę teorii…
Model-View-Controller został zaprojektowany w 1979 roku przez norweskiego programistę Trygve Reenskaug pracującego wtedy nad językiem Smalltalk w laboratoriach Xerox i początkowo nosił nazwę Model-View-Editor.
Ideą tego wzorca jest rozdzielenie kodu odpowiedzialnego za przetworzenie danych od kodu odpowiedzialnego za ich wyświetlanie.
Model-View-Controller zakłada podział aplikacji na trzy główne części:
- Model – jest pewną reprezentacją problemu bądź logiki aplikacji.
- Widok – opisuje, jak wyświetlić pewną część modelu w ramach interfejsu użytkownika.
- Kontroler – przyjmuje dane wejściowe od użytkownika i reaguje na jego poczynania, zarządzając aktualizacje modelu oraz odświeżenie widoków.
Brzmi strasznie, ale w praktyce okazuje się, że to wcale nie jest takie trudne …
No to zaczynamy!
Na samym początku stwórzmy szkielet katalogów:
config/ controller/ model/ view/ templates/
Mając hierarchię katalogów stwórzmy szkielet plików wzorca MVC:
controller/controller.php
<?php /** * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version: 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ /** * This class includes methods for controllers. * * @abstract */ abstract class Controller{ /** * It redirects URL. * * @param string $url URL to redirect * * @return void */ public function redirect($url) { } /** * It loads the object with the view. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadView($name, $path='') { } /** * It loads the object with the model. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadModel($name, $path='') { } }
model/model.php
<?php /** * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version: 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ /** * This class includes methods for models. * * @abstract */ abstract class Model{ /** * object of the class PDO * * @var object */ protected $pdo; /** * It sets connect with the database. * * @return void */ public function __construct() { } /** * It loads the object with the model. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadModel($name, $path='') { } /** * It selects data from the database. * * @param string $from Table * @param <type> $select Records to select (default * (all)) * @param <type> $where Condition to query * @param <type> $order Order ($record ASC/DESC) * @param <type> $limit LIMIT * @return array */ public function select($from, $select='*', $where=NULL, $order=NULL, $limit=NULL) { } }
view/view.php
<?php /** * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version: 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ /** * This class includes methods for views. * * @abstract */ abstract class View{ /** * It loads the object with the model. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadModel($name, $path='') { } /** * It includes template file. * * @return void */ public function render() { } /** * It sets data. * * @param string $name * @param mixed $value * * @return void */ public function set($name, $value) { } /** * It gets data. * * @param string $name * * @return mixed */ public function get($name) { } }
Pliki te zawierają zarys podstawowych metod, które wykorzystamy w tworzeniu systemu artykułów używając wzorca Model-View-Controller. Klasy oznaczyłem jako abstrakcyjne, gdyż będą one dziedziczone po bardziej specjalistycznych elementach – wykonujących już konkretne czynności. W dalszej części będziemy je sukcesywnie wypełniać kodem.
Tworzenie kodu aplikacji zacznijmy od controller/controller.php
<?php /** * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version: 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ /** * This class includes methods for controllers. * * @abstract */ abstract class Controller{ /** * It redirects URL. * * @param string $url URL to redirect * * @return void */ public function redirect($url) { header("location: ".$url); } /** * It loads the object with the view. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadView($name, $path='view/') { $path=$path.$name.'.php'; $name=$name.'View'; try { if(is_file($path)) { require $path; $ob=new $name(); } else { throw new Exception('Can not open view '.$name.' in: '.$path); } } catch(Exception $e) { echo $e->getMessage().'<br /> File: '.$e->getFile().'<br /> Code line: '.$e->getLine().'<br /> Trace: '.$e->getTraceAsString(); exit; } return $ob; } /** * It loads the object with the model. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadModel($name, $path='model/') { $path=$path.$name.'.php'; $name=$name.'Model'; try { if(is_file($path)) { require $path; $ob=new $name(); } else { throw new Exception('Can not open model '.$name.' in: '.$path); } } catch(Exception $e) { echo $e->getMessage().'<br /> File: '.$e->getFile().'<br /> Code line: '.$e->getLine().'<br /> Trace: '.$e->getTraceAsString(); exit; } return $ob; } }
Metoda redirect() służy do przekierowania strony na wskazany adres. Z kolei metody loadModel() i loadView() inicjują obiekty klas widoku oraz modelu.
Plik model/model.php będzie wyglądać tak:
<?php /** * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version: 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ /** * This class includes methods for models. * * @abstract */ abstract class Model{ /** * object of the class PDO * * @var object */ protected $pdo; /** * It sets connect with the database. * * @return void */ public function __construct() { try { require 'config/sql.php'; $this->pdo=new PDO('mysql:host='.$host.';dbname='.$dbase, $user, $pass); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(DBException $e) { echo 'The connect can not create: ' . $e->getMessage(); } } /** * It loads the object with the model. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadModel($name, $path='model/') { $path=$path.$name.'.php'; $name=$name.'Model'; try { if(is_file($path)) { require $path; $ob=new $name(); } else { throw new Exception('Can not open model '.$name.' in: '.$path); } } catch(Exception $e) { echo $e->getMessage().'<br /> File: '.$e->getFile().'<br /> Code line: '.$e->getLine().'<br /> Trace: '.$e->getTraceAsString(); exit; } return $ob; } /** * It selects data from the database. * * @param string $from Table * @param <type> $select Records to select (default * (all)) * @param <type> $where Condition to query * @param <type> $order Order ($record ASC/DESC) * @param <type> $limit LIMIT * @return array */ public function select($from, $select='*', $where=NULL, $order=NULL, $limit=NULL) { $query='SELECT '.$select.' FROM '.$from; if($where!=NULL) $query=$query.' WHERE '.$where; if($order!=NULL) $query=$query.' ORDER BY '.$order; if($limit!=NULL) $query=$query.' LIMIT '.$limit; $select=$this->pdo->query($query); foreach ($select as $row) { $data[]=$row; } $select->closeCursor(); return $data; } }
Konstruktor klasy Model ma nawiązać połączenie z bazą danych (używamy do tego PDO). Metoda loadModel() ma za zadanie stworzyć obiekt z klasą modelu. Natomiast select() będziemy używać do pobieranie danych.
Dodajmy jeszcze kod do pliku view/view.php:
<?php /** * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version: 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ /** * This class includes methods for views. * * @abstract */ abstract class View{ /** * It loads the object with the model. * * @param string $name name class with the class * @param string $path pathway to the file with the class * * @return object */ public function loadModel($name, $path='model/') { $path=$path.$name.'.php'; $name=$name.'Model'; try { if(is_file($path)) { require $path; $ob=new $name(); } else { throw new Exception('Can not open model '.$name.' in: '.$path); } } catch(Exception $e) { echo $e->getMessage().'<br /> File: '.$e->getFile().'<br /> Code line: '.$e->getLine().'<br /> Trace: '.$e->getTraceAsString(); exit; } return $ob; } /** * It includes template file. * * @param string $name name template file * @param string $path pathway * * @return void */ public function render($name, $path='templates/') { $path=$path.$name.'.html.php'; try { if(is_file($path)) { require $path; } else { throw new Exception('Can not open template '.$name.' in: '.$path); } } catch(Exception $e) { echo $e->getMessage().'<br /> File: '.$e->getFile().'<br /> Code line: '.$e->getLine().'<br /> Trace: '.$e->getTraceAsString(); exit; } } /** * It sets data. * * @param string $name * @param mixed $value * * @return void */ public function set($name, $value) { $this->$name=$value; } /** * It gets data. * * @param string $name * * @return mixed */ public function get($name) { return $this->$name; } }
Metoda loadModel() działa tak samo jak w poprzednich plikach. Metodę render() będziemy wykorzystywać do dołączania plików z kodem HTML.
Tworzymy tabele bazy danych
Na zakończenie tej części stwórzmy tabele kategorii i artykułów w bazy danych:
CREATE TABLE categories( id integer auto_increment, name varchar(100), primary key(id) ); CREATE TABLE articles( id integer auto_increment, title varchar(100), content text, date_add datetime, autor varchar(100), id_categories integer, primary key(id), foreign key(id_categories) references categories(id) );
W pliku config/sql.php zapiszmy dane dostępu do bazy danych.
<?php $host='localhost'; $dbase='baza danych'; $user='user'; $pass='hasło';
W kolejnej części cyklu zaczniemy już dokładniej poznawać idee MVC. Stworzymy fragment aplikacji odpowiedzialny za dodawanie kategorii.