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

Ponad trzy i pół roku temu na moim blogu pojawił się cykl MVC w praktyce. Mimo, że minęło już tyle lat od publikacji nadal cieszy się dużą popularnością. Postanowiłem go odświeżyć i wykorzystać aktualne narzędzia przydatne w programowaniu PHP.

Uwaga: Żeby w pełni zrozumieć ideę tego wzorca projektowego czytelnik musi mieć solidne podstawy znajomości PHP oraz potrafić programować obiektowo.

Co nowego?

Najważniejszymi zmianami w kodzie są:

  • Wykorzystanie przestrzeni nazw
  • Użycie Composer
  • Wykorzystanie standardu PSR-4. Więcej o tym standardzie przeczytasz na blogu Dominika Marczuka
  • Aplikacja zawiera router do tworzenia przyjaznych linków SEO

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 i plików:

src/ /* Katalog z kodem aplikacji */
src/Controller        /* Miejsce na kontrolery */
src/Model             /* Miejsce na modele */
src/View              /* Miejsce na widoki */
src/template          /* Miejsce na szablony HTML */
src/Engine            /* Silnik aplikacji */
src/Engine/Router     /* Router aplikacji */
vendor/               /* Tu będą pliki Composer */
.htaccess
composer.json         /* Konfiguracja aplikacji dla Composer */
config.php            /* Konfiguracja aplikacji */
config-router.php     /* Tablica dla routera */
index.php

Tworzymy szkielet aplikacji

Mając strukturę katalogów i plików możemy przejść do tworzenia kodu PHP.

Zaczynamy od kilku podstawowych plików

.htacess

Options +FollowSymLinks
RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [NC,L]

Plik config.php zawiera dane dostępu do aplikacji oraz podstawowe ścieżki aplikacji.

<?php
define('DATABASE_NAME', '****');
define('DATABASE_USER', '****');
define('DATABASE_HOST', '****');
define('DATABASE_PASSOWD', '****');
define('DIR_VENDOR', '/sciezka/do/katalogu/vendor/');
define('DIR_TEMPLATE', '/sciezka/do/katalogu/template/');
define('DIR_CONTROLLER', '/sciezka/do/katalogu/Controller/');
define('HTTP_SERVER', 'http://adres-aplikacji.pl/');

Aktualizacja 1.07.2015: Ścieżki w pliku config.php muszą być bezwzględne. Z maili od was wynika, że dla części z Was nie było to jasne.

Plik config-router.php zawiera kolekcję linków dla routera. Więcej o routerze wspomnę później.

<?php
$collection = new \RacyMind\MVCWPraktyce\Engine\Router\RouteCollection();

$collection->add('category/delete', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'kategorie/usun/<id>?',
    array(
        'file' => DIR_CONTROLLER.'Category.php',
        'method' => 'delete',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Category'
    ),
    array(
        'id' => '\d+'
    ),
    array(
        'id' => 0
    )
));
$collection->add('category/add', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'kategorie/dodaj',
    array(
        'file' => DIR_CONTROLLER.'Category.php',
        'method' => 'add',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Category'
    )
));
$collection->add('category/index', new \RacyMind\MVCWPraktyce\Engine\Router\Route(
    HTTP_SERVER.'kategorie',
    array(
        'file' => DIR_CONTROLLER.'Category.php',
        'method' => 'index',
        'class' => '\RacyMind\MVCWPraktyce\Controller\Category'
    )
));
$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 = new \RacyMind\MVCWPraktyce\Engine\Router\Router($_SERVER['REQUEST_URI'], $collection);

index.php

<?php
require_once 'config.php';
$loader = include DIR_VENDOR.'autoload.php';
require_once 'config-router.php';
$router = new \RacyMind\MVCWPraktyce\Engine\Router\Router('http://'.$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"]);
$router->run();
$file=$router->getFile();
$classController=$router->getClass();
$method=$router->getMethod();
require_once($file);
$obj = new $classController();
$obj->$method();
Abstrakcyjne klasy dla kontrolera, widoku i modelu

Kolejną rzeczą jaką stworzymy są abstrakcyjne klasy, które będą dziedziczone przez konkretne kontrolery, widoki i modele.

src/Engine/Controller.php
<?php

namespace RacyMind\MVCWPraktyce\Engine;
/**
 * This class includes methods for controllers.
 * @package RacyMind\MVCWPraktyce\Engine
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version: 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 *
 * @abstract
 */
abstract class Controller
{
    /**
     * Przekierowuje na wskazany adres
     *
     * @param string $url URL do przekierowania
     *
     * @return void
     */
    public function redirect($url)
    {
        header("location: " . $url);
    }

    /**
     * Generuje link.
     * @param $name
     * @param null $data
     * @return bool|string
     */

    public function generateUrl($name, $data = null)
    {
        $router = new \RacyMind\MVCWPraktyce\Engine\Router\Router('http://' . $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"]);
        $collection = $router->getCollection();
        $route = $collection->get($name);
        if (isset($route)) {
            return $route->geneRateUrl($data);
        }
        return false;
    }
}

Klasa Controller zawiera 2 metody. Metoda redirect() przekierowuje użytkownika na wskazany adres URL. Metoda generateUrl() tworzy adres URL na postawie kolekcji zawartej w pliku config.router.php.

src/Engine/Model.php
<?php

namespace RacyMind\MVCWPraktyce\Engine;
use \PDO;

/**
 * @package RacyMind\MVCWPraktyce\Engine
 * @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 {
            $this->pdo = new PDO('mysql:host=' . DATABASE_HOST . ';dbname=' . DATABASE_NAME, DATABASE_USER, DATABASE_PASSOWD, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (DBException $e) {
            echo 'The connect can not create: ' . $e->getMessage();
        }
    }

}

Klasa Model tworzy połączenie z bazą danych. Do manipulacji bazą danych wykorzystuję PDO.

src/Engine/View.php
<?php

namespace RacyMind\MVCWPraktyce\Engine;
 
/**
 * This class includes methods for views.
 * @package RacyMind\MVCWPraktyce\Engine
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version: 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 *
 * @abstract
 */
abstract class View{

    /**
     * Generuje link.
     * @param $name
     * @param null $data
     * @return bool|string
     */

    public function generateUrl($name, $data=null) {
        $router = new \RacyMind\MVCWPraktyce\Engine\Router\Router('http://' . $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"]);
        $collection = $router->getCollection();
        $route=$collection->get($name);
        if (isset($route)) {
            return $route->geneRateUrl($data);
        }
        return false;
    }

    /**
     * Wyświetla kod HTML szablonu
     *
     * @param string $name Nazwa pliku
     * @param string $path Ścieżka do szablonu
     *
     * @return void
     */
    public function renderHTML($name, $path='') {
        $path=DIR_TEMPLATE.$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;
        }
    }

    /**
     * Wyświetla dane JSON.
     * @param array $data Dane do wyświetlenia
     */
    public function renderJSON($data) {
        header('Content-Type: application/json');
        echo json_encode($data);
        exit;
    }

    /**
     * Wyświetla dane JSONP.
     * @param array $data Dane do wyświetlenia
     */
    public function renderJSONP($data) {
        header('Content-Type: application/json');
        echo $_GET['callback'] . '(' . json_encode($data) . ')';
        exit();
    }

    /**
     * Ładuje nagłówek strony
     */
    public function getHeader() {
        return $this->renderHTML('header', 'front/');
    }

    /**
     * Ładuje stopkę strony
     */
    public function getFooter() {
        return $this->renderHTML('footer', 'front/');
    }
    /**
     * It sets data.
     *
     * @param string $name
     * @param mixed $value
     *
     * @return void
     */
    public function set($name, $value) {
        $this->$name=$value;
    }
    /**
     * 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;
    }
    /**
     * It gets data.
     *
     * @param string $name
     *
     * @return mixed
     */
    public function __get($name) {
        if( isset($this->$name) )
            return $this->$name;
        return null;
    }
}

Opis najważniejszych metod:

  • generateUrl() – podobnie jak w kontrolerze, metody tworzy odpowiedni adres URL na podstawie konfiguracji routera.
  • renderHTML() – wyświetla wskazany plik z szablonem HTML.
  • renderJSON() – zwraca dane w formacie JSON.
  • renderJSONP() – zwraca dane w formacie JSONP.
  • getHeader() – wyświetla nagłówek strony.
  • getFooter() – wyświetla stopkę strony.
Nagłówek i stopka strony

Nagłówek i stopka będą wyświetlane na każdej podstronie aplikacji.

src/template/front/header.html.php

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="pl-PL">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta charset="utf-8"/>
    <title>MVC</title>

</head>
<body>
<ul>
    <li><a href="<?php echo $this->generateUrl('category/add'); ?>">Dodaj kategorię</a></li>
    <li><a href="<?php echo $this->generateUrl('category/index'); ?>">Lista kategorii</a></li>
    <li><a href="<?php echo $this->generateUrl('article/add'); ?>">Dodaj artykuł</a>
    <li><a href="<?php echo $this->generateUrl('article/index'); ?>">Lista artykułów</a>
</ul>

src/template/front/footer.html.php

</body>
</html>
Routing linków

W aplikacji MVC wykorzystuję opisany już jakiś czas temu router linków. Dzięki temu w łatwy sposób mogę tworzyć linki przyjazne dla SEO. Jeżeli jesteś ciekaw na jakiej zasadzie działa router zapraszam do lektury :).

src/Engine/Router/Route.php
<?php

namespace RacyMind\MVCWPraktyce\Engine\Router;

/**
 * Klasa zawiera pojedyńczy element do routingu.
 * @package RacyMind\MVCWPraktyce\Engine\Router
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version 1.0
 */
class Route
{
    /**
     * @var string Ścieżka URL
     */
    protected $path;
    /**
     * @var string Ścieżka do kontrolera
     */
    protected $file;
    /**
     * @var string Nazwa klasy
     */
    protected $class;
    /**
     * @var string Nazwa metody
     */
    protected $method;
    /**
     * @var array Zawiera wartości domyślne dla parametrów
     */
    protected $defaults;
    /**
     * @var array Zawiera reguły przetważania dla parametrów
     */
    protected $params;

    /**
     * @param string $path Ścieżka URL
     * @param array $config Tablica ze ścieżką do kontrolera oraz nazwą metody
     * @param array $params Tablica reguł przetważania dla parametrów
     * @param array $defaults Tablica wartości domyślne parametrów
     */
    public function __construct($path, $config, $params = array(), $defaults = array())
    {
        $this->path = $path;
        $this->file = $config['file'];
        $this->method = $config['method'];
        $this->class = $config['class'];
        $this->setParams($params);
        $this->setDefaults($defaults);
    }

    /**
     * @param string $file
     */
    public function setFile($file)
    {
        $this->file = $file;
    }

    /**
     * @return string
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * @param string $class
     */
    public function setClass($class)
    {
        $this->class = $class;
    }

    /**
     * @return string
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * @param array $defaults
     */
    public function setDefaults($defaults)
    {
        $this->defaults = $defaults;
    }

    /**
     * @return array
     */
    public function getDefaults()
    {
        return $this->defaults;
    }

    /**
     * @param string $method
     */
    public function setMethod($method)
    {
        $this->method = $method;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * @param array $params
     */
    public function setParams($params)
    {
        $this->params = $params;
    }

    /**
     * @return array
     */
    public function getParams()
    {
        return $this->params;
    }

    /**
     * @param string $path
     */
    public function setPath($path)
    {
        $this->path =$path;
    }

    /**
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Generuje przyjazny link.
     * @param array $data
     * @return string
     */
    public function generateUrl($data)
    {
        if (is_array($data) && sizeof($data)>0) {
            $key_data = array_keys($data);
            foreach ($key_data as $key) {
                $data2['<' . $key . '>'] = $data[$key];
            }
            $url = str_replace(array('?', '(', ')'), array('', '', ''), $this->path);
            return str_replace(array_keys($data2), $data2, $url);
        } else {
            $url=preg_replace("#<[a-zA-Z0-9]*>#", '', $this->path, 1);
            return str_replace(array('?', '(', ')'), array('', '', ''), $url);
        }
    }
} 
src/Engine/Router/RouteCollection.php
<?php

namespace RacyMind\MVCWPraktyce\Engine\Router;

/**
 * Klasa zawiera kolekcję elementów klasy Route.
 * @package RacyMind\MVCWPraktyce\Engine\Router
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version 1.0
 */
class RouteCollection
{
    /**
     * @var array Tablica obiektów klasy Route
     */
    protected $items;

    /**
     * Dodaje obiekt Route do kolekcji
     * @param string $name Nazwa elementu
     * @param Route $item Obiekt Route
     */
    public function add($name, $item)
    {
        $this->items[$name] = $item;
    }

    public function get($name)
    {
        if (array_key_exists($name, $this->items)) {
            return $this->items[$name];
        } else {
            return null;
        }
    }

    /**
     * Zwraca wszystkie obiekty kolekcji
     * @return array array
     */
    public function getAll()
    {
        return $this->items;
    }
} 
src/Engine/Router/Router.php
<?php

namespace RacyMind\MVCWPraktyce\Engine\Router;

/**
 * Klasa Routera.
 * @package RacyMind\MVCWPraktyce\Engine\Router
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version 1.0
 */
class Router
{
    /**
     * @var String URL do przetworzenia
     */
    protected $url;
    /**
     * @var array Zawiera objekt RouteCollecion.
     */
    protected static $collection;
    /**
     * @var string Ścieżka do kontrolera
     */
    protected $file;
    /**
     * @var string Nazwa klasy
     */
    protected $class;
    /**
     * @var string Nazwa metody
     */
    protected $method;

    public function __construct($url, $collection = null)
    {
        if ($collection != null) {
            Router::$collection = $collection;
        }
        $url=explode('?', $url);
        $this->url = $url[0];
    }


    /**
     * @param array $collection
     */
    public function setCollection($collection)
    {
        Router::$collection = $collection;
    }

    /**
     * @return array
     */
    public function getCollection()
    {
        return Router::$collection;
    }

    /**
     * @param string $class
     */
    public function setClass($class)
    {
        $this->class = $class;
    }

    /**
     * @return string
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * @param string $file
     */
    public function setFile($file)
    {
        $this->file = $file;
    }

    /**
     * @return string
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * @param string $method
     */
    public function setMethod($method)
    {
        $this->method = $method;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }


    /**
     * @param String $url
     */
    public function setUrl($url)
    {
        $this->url = $url;
    }

    /**
     * @return String
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Sprawdza czy URL pasuje do przekazanej reguły.
     * @param Route $route Obiekt reguły
     * @return bool
     */
    protected function  matchRoute($route)
    {
        $params = array();
        $key_params = array_keys($route->getParams());
        $value_params = $route->getParams();
        foreach ($key_params as $key) {
            $params['<' . $key . '>'] = $value_params[$key];
        }
        $url = $route->getPath();
        // Zamienia znaczniki na odpowiednie wyrażenia regularne
        $url = str_replace(array_keys($params), $params, $url);
        // Jeżeli brak znacznika w tablicy $params zezwala na dowolny znak
        $url = preg_replace('/<\w+>/', '.*', $url);
        // sprawdza dopasowanie do wzorca
        preg_match("#^$url$#", $this->url, $results);
        if ($results) {
            $this->url=str_replace(array($this->strlcs($url, $this->url)), array(''), $this->url);
            $this->file = $route->getFile();
            $this->class = $route->getClass();
            $this->method = $route->getMethod();
            return true;
        }
        return false;
    }

    /**
     * Szuka odpowiedniej reguły pasującej do URL. Jeżeli znajdzie zwraca true.
     * @return bool
     */
    public function run()
    {
        foreach (Router::$collection->getAll() as $route) {
            if ($this->matchRoute($route)) {
                $this->setGetData($route);
                return true;
            }
        }
        return false;
    }

    /**
     * @param Route $route Obiekt Route pasujący do reguły
     */
    protected function setGetData($route)
    {
        $routePath=str_replace(array('(', ')'), array('', ''), $route->getPath());
        $trim=explode('<', $routePath);
        $parsed_url=str_replace(array(HTTP_SERVER), array(''), $this->url);
        $parsed_url=preg_replace("#$trim[0]#", '', $parsed_url, 1);
        // ustawia parametry przekazane w URL
        foreach ($route->getParams() as $key => $param) {
            if($parsed_url[0]=='/') {
                $parsed_url = substr($parsed_url, 1);
            }
            preg_match("#$param#", $parsed_url, $results);
            if (!empty($results[0])) {
                $_GET[$key] = $results[0];
                $temp_url=explode($results[0], $parsed_url, 2);
               // $parsed_url=str_replace($results[0], '', $temp_url[1]);
                //$parsed_url=preg_replace($patern, '', $temp_url[1], 1);
                $parsed_url=$temp_url[1];
            }
        }
        // jezeli brak parametru w URL ustawia go z tablicy wartości domyślnych
        foreach ($route->getDefaults() as $key => $default) {
            if (!isset($_GET[$key])) {
                $_GET[$key] = $default;
            }
        }
    }

    /**
     * Zwraca część wspólną ciągów
     * @param string $str1 Ciąg 1
     * @param string $str2 Ciąg 2
     * @return string część wspólna
     */
    protected function strlcs($str1, $str2){
        $str1Len = strlen($str1);
        $str2Len = strlen($str2);
        $ret = array();

        if($str1Len == 0 || $str2Len == 0)
            return $ret; //no similarities

        $CSL = array(); //Common Sequence Length array
        $intLargestSize = 0;

//initialize the CSL array to assume there are no similarities
        for($i=0; $i<$str1Len; $i++){
            $CSL[$i] = array();
            for($j=0; $j<$str2Len; $j++){
                $CSL[$i][$j] = 0;
            }
        }

        for($i=0; $i<$str1Len; $i++){
            for($j=0; $j<$str2Len; $j++){
//check every combination of characters
                if( $str1[$i] == $str2[$j] ){
//these are the same in both strings
                    if($i == 0 || $j == 0)
//it's the first character, so it's clearly only 1 character long
                        $CSL[$i][$j] = 1;
                    else
//it's one character longer than the string from the previous character
                        $CSL[$i][$j] = $CSL[$i-1][$j-1] + 1;

                    if( $CSL[$i][$j] > $intLargestSize ){
//remember this as the largest
                        $intLargestSize = $CSL[$i][$j];
//wipe any previous results
                        $ret = array();
//and then fall through to remember this new value
                    }
                    if( $CSL[$i][$j] == $intLargestSize )
//remember the largest string(s)
                        $ret[] = substr($str1, $i-$intLargestSize+1, $intLargestSize);
                }
//else, $CSL should be set to 0, which it was already initialized to
            }
        }
//return the list of matches
        if(isset($ret[0])) {
            return $ret[0];
        } else {
            return '';
        }
    }


} 

Instalacja Composer

Composer jest bardzo przydatnym narzędziem z dwóch powodów. Zapewnia prosty w użyciu autoloader oraz ułatwia dodawanie do projektu zewnętrznych bibliotek.

Opis jak zainstalować composer w systemie znajdziesz tutaj.

W naszej aplikacji plik composer.json wygląda następująco.

{
    "name": "RacyMind/MVCWPraktyce",
    "description": "MVCWPraktyce od Racy Mind",
    "keywords": ["MVCWPraktyce", "Racy Mind"],
    "homepage": "http://racymind.pl",
    "type": "education, mvc",
    "license": "GNU GPL",
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-4": {
            "RacyMind\\MVCWPraktyce\\": "src/"
        }
    }
}

Tworzymy tabele bazy danych

Na zakończenie tej części stwórzmy tabele kategorii i artykułów w bazy danych:

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) COLLATE utf8_polish_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci;

CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) COLLATE utf8_polish_ci DEFAULT NULL,
  `content` text COLLATE utf8_polish_ci,
  `date_add` datetime DEFAULT NULL,
  `author` varchar(100) COLLATE utf8_polish_ci DEFAULT NULL,
  `id_categories` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id_categories` (`id_categories`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci;

W kolejnej części cyklu zaczniemy już dokładniej poznawać idee MVC. Stworzymy fragment aplikacji odpowiedzialny za dodawanie kategorii.

Przejdź do drugiej części cyklu MVC w praktyce z composer

Print Friendly, PDF & Email