MVC w praktyce z composer – tworzymy system artykułów. cz. 3

W ostatniej części artykułu o wzorcu MVC stworzymy pozostałe elementy prostego systemu artykułów.

Dobrą praktyką przy budowaniu aplikacji z użyciem wzorca MVC jest „rozbicie” całego kodu na poszczególne, mniejsze moduły. W poprzedniej części stworzyliśmy fragmenty kodu do obsługi kategorii, teraz zajmiemy się artykułami.

Tworzymy kontroler artykułów

src/Controller/Article.php

<?php
namespace RacyMind\MVCWPraktyce\Controller;


/**
 * Kontroler do artykułów.
 * @package RacyMind\MVCWPraktyce\Controller
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 */
class Article extends \RacyMind\MVCWPraktyce\Engine\Controller
{
    /**
     * Wyświetla listę artykułów
     */
    public function index()
    {
        $model = new \RacyMind\MVCWPraktyce\Model\Article();
        $articles = $model->getAll();
        $view = new \RacyMind\MVCWPraktyce\View\Article();
        $view->articles = $articles;
        $view->renderHTML('index', 'front/article/');
    }
    /**
     * Wyświetla jeden artykuł
     */
    public function one()
    {
        $model = new \RacyMind\MVCWPraktyce\Model\Article();
        $article = $model->getOne($_GET['id']);
        $view = new \RacyMind\MVCWPraktyce\View\Article();
        $view->article = $article;
        $view->renderHTML('one', 'front/article/');
    }

    /**
     * Wyświetla formularz i dodaje artykuł
     */
    public function add()
    {
        $model = new \RacyMind\MVCWPraktyce\Model\Article();
        if (!empty($_POST)) {
            $model->insert($_POST);
            $this->redirect($this->generateUrl('article/index'));
        } else {
            $modelCategory=new \RacyMind\MVCWPraktyce\Model\Category();
            $view = new \RacyMind\MVCWPraktyce\View\Article();
            $view->categories=$modelCategory->getAll();
            $view->renderHTML('add', 'front/article/');
        }
    }

    /**
     * Usuwa artykuł
     */
    public function delete()
    {
        $model = new \RacyMind\MVCWPraktyce\Model\Article();
        $model->delete($_GET['id']);;
        $this->redirect($this->generateUrl('article/index'));
    }
}

Kontroler artykułów posiada 4 akcje:

  • index() – metoda ta pobiera z modelu wszystkie artykuły i przekazuje je do widoku. Na koniec ładuje szablon HTML
  • one() – metoda ta pobiera z modelu jeden artykuł i przekazuje go do widoku. Na koniec ładuje szablon HTML
  • add() – metoda wyświetla szablon z formularzem dodawania artykułu oraz przekazuje dane z tablicy $_POST do modelu.
  • delete() – wywołuje metodę modelu usuwającą artykuł z bazy danych. Następnie przekierowuje użytkownika na listę artykułów.

W jaki sposób wywołać odpowiednie akcje kontrolera?

W mojej przykładowej aplikacji odpowiedzialny jest za to router. W pliku config-router.php znajduje się taki fragment kodu:

$collection->add('article/delete', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'artykuly/usun/<id>?',
    array(
        'file' => DIR_CONTROLLER.'Article.php',
        'method' => 'delete',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Article'
    ),
    array(
        'id' => '\d+'
    ),
    array(
        'id' => 0
    )
));
$collection->add('article/one', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'artykuly/wyswietl/<id>?',
    array(
        'file' => DIR_CONTROLLER.'Article.php',
        'method' => 'one',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Article'
    ),
    array(
        'id' => '\d+'
    ),
    array(
        'id' => 0
    )
));
$collection->add('article/add', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'artykuly/dodaj',
    array(
        'file' => DIR_CONTROLLER.'Article.php',
        'method' => 'add',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Article'
    )
));
$collection->add('article/index', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'artykuly',
    array(
        'file' => DIR_CONTROLLER.'Article.php',
        'method' => 'index',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Article'
    )
));
$collection->add('homepage', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'',
    array(
        'file' => DIR_CONTROLLER.'Article.php',
        'method' => 'index',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Article'
    )
));

Router porównuje adres URL strony z powyższymi regułami. Jeżeli jakaś reguła pasuje do adresu URL wywołuje metodę odpowiedniego kontrolera. Uwaga. Jeżeli skrypt znajdzie pasującą regułę nie sprawdza już kolejnych, a więc najbardziej ogólne reguły adresu URL powinny być na końcu.

Tworzymy model artykułów

Model artykułów będzie w pliku src/Model/Article.php.

<?php

namespace RacyMind\MVCWPraktyce\Model;

/**
 * Model artykułów.
 * @package RacyMind\MVCWPraktyce\Model
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 */
class Article extends \RacyMind\MVCWPraktyce\Engine\Model
{
    /**
     * Zwraca z bazy danych wszystkie artykuły
     * @return array|null Tablica z artykułami
     */
    public function getAll()
    {
        $query = $this->pdo->query("SELECT a.id, a.title, a.date_add, a.author, c.name FROM articles AS a LEFT JOIN categories AS c ON a.id_categories=c.id");
        $items = $query->fetchAll(\PDO::FETCH_ASSOC);
        if (isset($items)) {
            return $items;
        } else {
            return null;
        }
    }

    /**
     * Zwraca z bazy danych jeden artykuł
     * @param int $id ID artykułu
     * @return array|null Tablica z danymi jednego artykułu
     */
    public function getOne($id)
    {
        $query = $this->pdo->query("SELECT a.id, a.title, a.date_add, a.author, c.name, a.content FROM articles AS a LEFT JOIN categories AS c ON a.id_categories=c.id where a.id=".(int)$id);
        $items = $query->fetchAll(\PDO::FETCH_ASSOC);
        if (isset($items[0])) {
            return $items[0];
        } else {
            return null;
        }
    }

    /**
     * Dodaje artykuł do bazy
     * @param $data Dane do zapisu
     */
    public function insert($data) {
        $ins=$this->pdo->prepare('INSERT INTO articles (title, content, date_add, author, id_categories) VALUES (
            :title, :content, :date_add, :author, :id_categories)');
        $ins->bindValue(':title', $data['title'], \PDO::PARAM_STR);
        $ins->bindValue(':content', $data['content'], \PDO::PARAM_STR);
        $ins->bindValue(':date_add', $data['date_add'], \PDO::PARAM_STR);
        $ins->bindValue(':author', $data['author'], \PDO::PARAM_STR);
        $ins->bindValue(':id_categories', $data['cat'], \PDO::PARAM_INT);
        $ins->execute();
    }

    /**
     * Usuwa artykuł z bazy
     * @param int $id ID artykułu
     */
    public function delete($id) {
        $del=$this->pdo->prepare('DELETE FROM articles where id=:id');
        $del->bindValue(':id', $id, \PDO::PARAM_INT);
        $del->execute();
    }
}

Model ten posiada 4 metody:

  • getAll() – pobiera wszystkie rekordy z tabeli categories
  • getOne() – pobiera jeden artykuł o podanym ID z bazy danych
  • insert() – dodaje nowy artykuł do bazy
  • delete() – usuwa artykuł o podanym ID z bazy danych

Tworzymy widok artykułów

src/View/Article.php

<?php

namespace RacyMind\MVCWPraktyce\View;

/**
 * Widok dla artykułów.
 * @package RacyMind\MVCWPraktyce\View
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 */
    class Article extends \RacyMind\MVCWPraktyce\Engine\View{
        public function __construct() {
        }
    }

Widok ten dziedziczy tylko metody po klasie View. W bardziej zaawansowanych aplikacjach konstrukcja taka umożliwia dopisywanie dodatkowych metod dla poszczególnych modułów.

Tworzymy szablony HTML dla artykułów

Na koniec stworzymy jeszcze trzy szablony HTML.

src/template/front/article/index.html.php

<?php $this->getHeader(); ?>
<h1>Lista artykułów</h1>
<table>
    <tr>
        <td>Tytuł</td>
        <td>Data dodania</td>
        <td>Autor</td>
        <td>Kategoria</td>
        <td>&nbsp;</td>
    </tr>
    <?php foreach($this->articles as $item): ?>
        <tr>
            <td><a href="<?php echo $this->generateUrl('article/one', array('id' => $item['id'])); ?>"><?php echo $item['title']; ?></a></td>
            <td><?php echo $item['date_add']; ?></td>
            <td><?php echo $item['author']; ?></td>
            <td><?php echo $item['name']; ?></td>
            <td><a href="<?php echo $this->generateUrl('article/delete', array('id' => $item['id'])); ?>">usuń</a></td>
        </tr>
    <?php endforeach; ?>
    </tbody>
</table>
<?php $this->getFooter(); ?>

src/template/front/article/add.html.php

<?php $this->getHeader(); ?>
<h1>Dodaj artykuł</h1>
<form action="" method="post">
    Tytuł: <input type="text" name="title" /><br />
    Autor: <input type="text" name="author" /><br />
    Data dodania: <input type="text" name="date_add" value="<?php echo date("Y:m:d"); ?>" /><br />
    Treść:<br />
    <textarea name="content"></textarea><br />
    Kategoria: <select name="cat" size="0">
        <?php foreach($this->categories as $item): ?>
            <option value="<?php echo $item['id'] ;?>"><?php echo $item['name']; ?></option>
        <?php endforeach; ?>
    </select><br />
    <input type="submit" value="Dodaj" />
</form>
<?php $this->getFooter(); ?>

src/template/front/article/one.html.php

<?php $this->getHeader(); ?>
<h1><?php echo $this->article['title']; ?></h1>
autor: <?php echo $this->article['author']; ?>, data dodania: <?php echo $this->article['date_add']; ?><br />
Kategoria: <?php echo $this->article['name']; ?>

<p><?php echo $this->article['content']; ?></p>
<?php $this->getFooter(); ?>

Szablony korzystają z pól i metod klasy \View\ARticle.

Podsumowanie

Tak stworzyliśmy działający skrypt oparty na wzorcu MVC. Dzięki rozdzieleniu warstwy logiki od formy prezentacji dalsza rozbudowa staje się znacznie prostsza. Możemy teraz dodać szablon HTML, bez potrzeby zaglądania w kontrolery oraz modele. Jest to szczególnie przydatne przy pracy nad większymi aplikacjami – osoby odpowiedzialne za wygląd mogą pracować praktycznie bez pomocy programistów.

W porównaniu z kodem z 2011 roku aplikacja jest bardziej czytelna i łatwiejsza do rozbudowy. Dzięki wykorzystaniu przestrzeni nazw oraz standardu PSR-4 struktura plików jest przejrzysta i logiczna. Z kolei composer ułatwia dołączanie zewnętrznych bibliotek.

Cały kod można pobrać stąd.

Print Friendly, PDF & Email