Привет Хаброобществу.
Однажды мне на глаза попалась статья с хабра ссылка. Попробовав ту систему, пришел к выводу, что у нее много недостатков, да и перевод оригинальной статьи присутствовал не полностью. Да, статья старючая, но в то же время хотелось написать свой небольшой фреймворк для создания простых и средних по сложности сайтов. Так как тащить с собой 2-3 мегабайта какого-либо серьезного фреймворка для создания сайта-визитки считаю кощунством (хоть и не всегда).
Вообщем, решил исправить бросающиеся в глаза недостатки и полученным результатом поделиться с хаброжителями.
Итак, что же было усовершенствованно?
1. Избавился от использования класса Registry. Зачем он нам, если в PHP5 присутствуют такие замечательные методы как __set и __get?
2. Вместо PDO написал некий класс, названный мной как Active Records, позволяющий общаться с базой данных через прослойку. Приводить исходный код не буду, так как качество кода там не ахти и нуждается в рефакторинге. Но если будет нужно — добавлю.
3. Переписан немного класс Template, добавлена возможность делать вложенные шаблоны. Теперь он имеет такой вид:
4. Был изменен роутер, появилась поддержка нескольких параметров в URL. Хотя подозреваю, что роутер все еще с недостатками, которые буду со временем устранять. Но пока работает :)
5. Добавлен класс Loader, в котором будет происходить подзагрузки моделей (реализовано), библиотек (реализовано). В дальнейшем, думаю, прикрутить туда и загрузку хелперов.
Здесь, как мы видим, что в любую модель или библиотеку передается вышеупомянутый класс ActiveRecords (который в данный момент является singletone). Благодаря этому, в моделях и библиотеках становится доступной работа с базой данных (чего и следует ожидать от концепта MVC). Но присутствуют ограничения на названия моделей и библиотек — нельзя, например, использовать модель с именем load.
Сам же базовый класс модели, от которого наследуются все последующие в проекте, выглядит таким образом:
6. Ввиду вышеупомянутых изменений, базовый класс контроллера, от которого наследуются все последующие контроллеры, теперь выглядит так:
Таким образом в каждом контроллере доступен класс Loader и Template. И мы можем писать в коде подобные вещи:
Тоесть все методы модели становятся доступными по такому вот типу:
7. Также в стартап скрипт добавлены небольшие изменения — инициализация сессии (в последующем, планирую добавить сессии с БД), добавлена наряду с функцией __autoload также часто используемая функция redirect:
а также соединение с базой данных (вот тут не уверен, может имеет смысл вынести подключение к базе данных в класс Active Records? )
8. И что самое важное — наш главный файл index.php, который обрабатывает все запросы. Теперь он имеет такой вот вид:
Исходный код фреймворка можно скачать здесь: ссылка. Обращаю ваше внимание на то, что здесь исходный код не “из коробки”. Дан лишь некоторый код, кроме базового ядра, необходимый, как пример использования.
Буду рад вашим комментариям, замечаниям и наставлениям.
P.S. И да — самое главное. Рабочий пример небольшого ресурса, созданного с применением ресурса — ссылка. Сервис позволяет хранить ссылки по категориям онлайн. Написано под себя, так как существующие хранилища по ряду причин не устраивают. К тому же нужно было на чем-то тестировать ядро.
Однажды мне на глаза попалась статья с хабра ссылка. Попробовав ту систему, пришел к выводу, что у нее много недостатков, да и перевод оригинальной статьи присутствовал не полностью. Да, статья старючая, но в то же время хотелось написать свой небольшой фреймворк для создания простых и средних по сложности сайтов. Так как тащить с собой 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. И да — самое главное. Рабочий пример небольшого ресурса, созданного с применением ресурса — ссылка. Сервис позволяет хранить ссылки по категориям онлайн. Написано под себя, так как существующие хранилища по ряду причин не устраивают. К тому же нужно было на чем-то тестировать ядро.