Pull to refresh

И снова MVC

Reading time10 min
Views4.9K
Привет Хаброобществу.

Однажды мне на глаза попалась статья с хабра ссылка. Попробовав ту систему, пришел к выводу, что у нее много недостатков, да и перевод оригинальной статьи присутствовал не полностью. Да, статья старючая, но в то же время хотелось написать свой небольшой фреймворк для создания простых и средних по сложности сайтов. Так как тащить с собой 2-3 мегабайта какого-либо серьезного фреймворка для создания сайта-визитки считаю кощунством (хоть и не всегда).

Вообщем, решил исправить бросающиеся в глаза недостатки и полученным результатом поделиться с хаброжителями.

Итак, что же было усовершенствованно?

1. Избавился от использования класса Registry. Зачем он нам, если в PHP5 присутствуют такие замечательные методы как __set и __get?

2. Вместо PDO написал некий класс, названный мной как Active Records, позволяющий общаться с базой данных через прослойку. Приводить исходный код не буду, так как качество кода там не ахти и нуждается в рефакторинге. Но если будет нужно — добавлю.

3. Переписан немного класс Template, добавлена возможность делать вложенные шаблоны. Теперь он имеет такой вид:

<?php
 
class Template {
 
    private $vars = array();
 
    function __set($varname, $value) {
        $this->vars[$varname] = $value;
        return true;
    }
 
    function show($name, $data = false, $display = true) {
        $path = site_path . 'views' . DIRSEP . $name . '.php';
 
        if (!file_exists($path)) {
            trigger_error('Template `' . $name . '` does not exist.', E_USER_NOTICE);
            return false;
        }
 
        if ($data) {
            // fill with data
            if (is_array($data)) {
                foreach ($data as $key => $value) {
                    $this->__set($key, $value);
                }
            }
        }
 
        extract($this->vars);
 
        if ($display) {
            include ($path);
        } else {
            if (is_file($path)) {
                ob_start();
                include $path;
                $contents = ob_get_contents();
                ob_end_clean();
                return $contents;
            }
            return false;
        }
    }
}
 
?>
 


4. Был изменен роутер, появилась поддержка нескольких параметров в URL. Хотя подозреваю, что роутер все еще с недостатками, которые буду со временем устранять. Но пока работает :)

<?php
 
class Router {
 
    public $path;
    private $args = array();
 
    function setPath($path) {
        $path = trim($path, '/\\');
        $path .= DIRSEP;
 
        if (!is_dir($path)) {
            throw new Exception('Invalid controller path: `' . $path . '`');
        }
        $this->path = $path;
    }
 
    function delegate() {
        // analize path
        $this->getController($file, $controller, $action, $args);
        // is file available?
        if (!is_readable($file)) {
            trigger_error('File `' . $file . '` not found', E_USER_ERROR);
        }
        include ($file);
 
        // create controller
        $class = 'Controller_' . $controller;
        $controller = new $class();
 
        // action is available in object?
        if (!is_callable(array($controller, $action))) {
            trigger_error('No method `' . $action . '` was found in class', E_USER_ERROR);
        }
        // call action
        call_user_func_array(array($controller, $action), $args);
    }
 
    private function getController(&$file, &$controller, &$action, &$args) {
        $route = (empty($_GET['route'])) ? 'index' : $_GET['route'];
        // split called url
        $route = trim($route, '/\\');
        $parts = explode('/', $route);
 
        // find right controller
        $cmd_path = $this->path;
 
        // set default controller name (will be replaced if found another)
        $controller = 'index';
 
        foreach ($parts as $part) {
            $fullpath = $cmd_path . $part;
            // is dir?
            if (is_dir($fullpath)) {
                $cmd_path .= $part . DIRSEP;
                array_shift($parts);
                continue;
            }
 
            // is file?
            if (is_file($fullpath . '.php')) {
                $controller = $part;
                array_shift($parts);
                break;
            }
        }
 
        // get action method in controller
        $action = (isset($parts[0]) && !is_numeric($parts[0])) ? array_shift($parts) : 'index';
        $file = $cmd_path . $controller . '.php';
        $args = $parts;
    }
}
?>
 


5. Добавлен класс Loader, в котором будет происходить подзагрузки моделей (реализовано), библиотек (реализовано). В дальнейшем, думаю, прикрутить туда и загрузку хелперов.

<?php
 
class loader {
 
    public $instance;
    public $vars = array();
 
    function __construct($parent) {
        $this->instance = $parent;
    }
 
    function model($model_name) {
        $filename = strtolower($model_name) . '.php';
        $file = site_path . 'models' . DIRSEP . $filename;
        if (!file_exists($file)) {
            return false;
        }
        include ($file);
        $this->instance->$model_name = new $model_name(new ActiveRecords());
    }
 
    function library($name) {
        $filename = strtolower($name) . '.php';
        $file = site_path . 'libraries' . DIRSEP . $filename;
        if (!file_exists($file)) {
            return false;
        }
        include ($file);
        $this->instance->$name = new $name(new ActiveRecords());
    }
}
 
?>
 


Здесь, как мы видим, что в любую модель или библиотеку передается вышеупомянутый класс ActiveRecords (который в данный момент является singletone). Благодаря этому, в моделях и библиотеках становится доступной работа с базой данных (чего и следует ожидать от концепта MVC). Но присутствуют ограничения на названия моделей и библиотек — нельзя, например, использовать модель с именем load.

Сам же базовый класс модели, от которого наследуются все последующие в проекте, выглядит таким образом:

<?php
 
class Model {
 
    var $db;
 
    function __construct($db) {
        $this->db = $db;
    }
}
 
?>
 


6. Ввиду вышеупомянутых изменений, базовый класс контроллера, от которого наследуются все последующие контроллеры, теперь выглядит так:

<?php
 
class Controller_Base {
 
    public $template;
    public $load;
 
    function __construct() {
        $this->template = new Template();
        $this->load = new loader($this);
    }
 
    function __set($varname, $value) {
        $this->$varname = $value;
        return true;
    }
}
 
?>
 


Таким образом в каждом контроллере доступен класс Loader и Template. И мы можем писать в коде подобные вещи:

$this->load->model(‘links’);
$this->links->add (new);


Тоесть все методы модели становятся доступными по такому вот типу:

$this->[имя модели]->[имя метода в модели]


7. Также в стартап скрипт добавлены небольшие изменения — инициализация сессии (в последующем, планирую добавить сессии с БД), добавлена наряду с функцией __autoload также часто используемая функция redirect:

function redirect ($url)
{
   Header("Location: ".$url);
}


а также соединение с базой данных (вот тут не уверен, может имеет смысл вынести подключение к базе данных в класс Active Records? )

8. И что самое важное — наш главный файл index.php, который обрабатывает все запросы. Теперь он имеет такой вот вид:

<?php
 
require 'includes/start.php';
$router = new Router();
$router->setPath (site_path . 'controllers');
$router->delegate();
 
?>


Исходный код фреймворка можно скачать здесь: ссылка. Обращаю ваше внимание на то, что здесь исходный код не “из коробки”. Дан лишь некоторый код, кроме базового ядра, необходимый, как пример использования.

Буду рад вашим комментариям, замечаниям и наставлениям.

P.S. И да — самое главное. Рабочий пример небольшого ресурса, созданного с применением ресурса — ссылка. Сервис позволяет хранить ссылки по категориям онлайн. Написано под себя, так как существующие хранилища по ряду причин не устраивают. К тому же нужно было на чем-то тестировать ядро.
Tags:
Hubs:
Total votes 27: ↑12 and ↓15-3
Comments19

Articles