Фреймворк своими руками на чистом php​В сети большое количество мануалов по созданию сайтов на готовой CMS или фреймворке. Однако, работая фрилансером, часто встречаю сайты на самописных системах. Программисты пишут их не от хорошей жизни. В зависимости от степени простоты(сложности) проекта чрезмерно или наоборот недостаточно, применение готовой системы, и на ее переделки уходит больше времени, чем на создание сайта с нуля. К примеру, для сайта одностраничника не нужно тяжелой системы типа Joomla или фреймворка типа Yii, а у CMS типа Texpattern может не хватить функционала. Плюс задачи, которые ставит заказчик, могут быть весьма специфичными, и достаточно тяжело реализуемыми на готовой системе.

Для примера можно взять работы с моделями в Yii. Речь идет об ActiveRecord. У Yii на официальном сайте есть отличный мануал по созданию блога. Если придерживаться его, и делать все, как написано, то через пару часов изучения, можно получить полноценный блог. С категориями, метками, пользователями и административной панелью.

Проблемы начинаются тогда, когда вы попытаетесь сделать что-то, что не входит в мануал. Вывести под статьей похожие материалы. Для этого, надо составлять реляционную модель, гораздо более сложную, чем представлена в примерах.

Как сказал один наш комик - "тут начинается вестерн". То, что в SQL бы заняло 3 строчки кода, в Active Record займет пару ночей чтения мануалов, и экспериментов. Потому что, на первый взгляд тривиальная задача, вдруг вызывает необъяснимый баг Yii, о котором слышали полтора человека и оба китайцы.  Пример не надуманный, те кто программировал на Yii используя Actve Record поддержат.

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

В мире CMS тоже далеко ходить не надо. При работе над модулем Яндекс карт для Joomla нужно было подключить в админке сайта javascript  файл. Недельное изучение системы ничего не дало. Такого функционала в модулях попросту нет. Надо сказать, что я выкрутился используя функционал расширенных полей подключил нужный файл. Но то, сколько времени у меня на это ушло, несоизмеримо с тем, если бы система была построена по моим законам, и я знал, что и где в ней подключается.

Об этом расскажу в этой статье. Как написать php фреймворк с нуля. Опишем основные техники проектирования MVC фреймворков на чистом php без использования сторонних библиотек. 

Фреймворки пишут такие же программисты, как и вы. Нет ничего невозможного. Написать для себя каркас на котором потом можно будет строить конструкции нетипичных сайтов.

Хочу предостеречь вас, не стоит писать свой велосипед, без особой надобности и опыта. Большую часть кода придется писать руками. Тут не будет готовых модулей и расширений. 

Однако и плюсы тоже есть, система будет полностью под вашим контролем. Никаких ограничений API и требований системы. Только чистый vanilaPHP и ничего лишнего. 

Оставим лирику, приступим наконец к коду.

 

Исходные материалы к статье скачать и посмотреть на github. Статьи по этой теме будут публиковаться с тегом ideal, а последнюю версию фреймворка можно будет найти на gihub

Точка входа

Любой сайт, CMS или фреймворк начинается с точки входа. Обычно это index.php в корне сайта. Но мы не будем так делать.  Чтобы программист, который работает с нашим фреймворком сразу разобрался, что все данные идут через точку входа, назовем файл main.php. Тогда будет очевидно - все запросы перенаправляются через .htaccess

Код .htaccess 

AddDefaultCharset utf-8

RewriteEngine on

php_value upload_max_filesize 50M
php_value post_max_size 50M
php_value display_errors 1


DirectoryIndex main.php?controller=index

ErrorDocument 404 /main.php?controller=error

RewriteRule ^index.html$ main.php

RewriteCond %{REQUEST_FILENAME} !-f 
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ main.php?route=$1 [L,QSA]

Первые 8 строк - это вспомогательные настройки, которые пригодятся для фреймворка в будущем. Мы установили кодировку utf-8 по умолчанию, сайт будет работать на ней. Включили модуль apache Rewrite, для того чтобы перенаправить все не статичные запросы на main.php. Это и делают 3 последние строчки. Вся строка запроса, которая идет после домена, будет передана в переменную $_GET['route']. Т.е. запрос http://sitename.ru/kolesa/perellli/?id=5 превратится в http://sitename.ru/main.php?route=kolesa/perellli/&id=5

Дальнейшие манипуляции с разбором URL производятся с помощью PHP. Это распространенный прием, вы легко можете воспользоваться им в других системах. Так работает и Yii. Но наш фреймворк в отличие от Yii без .htaccess работать не сможет.

Файл main.php не должен делать много, он лишь определит константы путей, подключит фреймворк и запустит приложение

<?php
define('ROOT',dirname(__FILE__).'/');
define('IDEAL',dirname(__FILE__).'/ideal/');
define('APP',dirname(__FILE__).'/application/');
include IDEAL.'framework.php';
app::gi()->start();

Структура

Теперь продумаем структуру нашего фреймворка. Удобно, чтобы его файлы лежали в отдельной папке. Сделаем такую структуру

- application
-- controllers
-- models
-- views
-- config.php
- ideal
-- classes
-- framework.php
-- config.php
.htaccess
main.php

в папке ideal будет лежать наш идеальный =) движок, а в папке application файлы пользователя.

в файле  framework.php определим автозагрузчики классов, для того чтобы не писать руками каждый раз include 'classname.php'; Я уже писал про эту технику

<?php
function class_autoload($class_name) {
	$file = IDEAL . 'classes/'.$class_name.'.php';
	if( file_exists($file) == false )
		return false;
	require_once ($file);
}
function controller_autoload($class_name) {
	$file = APP . 'controllers/'.$class_name.'.php';
	if( file_exists($file) == false )
		return false;
	require_once ($file);
}
function model_autoload($class_name) {
	$file = APP . 'models/'.$class_name.'.php';
	if( file_exists($file) == false )
		return false;
	require_once ($file);
}

spl_autoload_register('class_autoload');
spl_autoload_register('controller_autoload');
spl_autoload_register('model_autoload');

в этом файле будут еще и другие системные действия. Но пока хватит и этого. 

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

Теперь опишем класс App, он находится в папке classes движка.Его метод start и будет запускать наше приложение.

<?php
class App extends Singleton{
	function start(){
		Router::gi()->parse();
		$controller = app::gi(Router::gi()->controller.'Controller');
		$controller->__call('action'.Router::gi()->action);
	}
}

Класс наследует абстрактный класс Singleton. Удобство в том, что экземпляр любого класса, который наследует от Singleton, можно получить из любого места программы через его метод gi() К примеру, экземпляр нашего приложения можно получить App::gi(). Этот метод вернет единственный экземпляр класс App. Один экземпляр может создать проблему, когда нужно использовать несколько баз данных. Поэтому к классу db его лучше не применять.

В коде появляются класс Router. Он будет парсить переменную $_GET['route'] и и возвращать контроллер и action - т.е. метода этого контроллера. Так как пока мы пишем лишь каркас, то этот класс ничего делать не будет. Лишь заполнит свои соответствующие поля.

<?php
class Router extends Singleton{
	public $action = 'index';
	public $controller = false;
	function parse(){
		if( isset($_REQUEST['controller']) )
			$this->controller = $_REQUEST['controller'];
		if( isset($_REQUEST['action']) )
			$this->action = $_REQUEST['action'];
	}
}

Далее используя метод App::gi() как фабрику, создается экземпляр класса контроллера. А затем вызывается его метод. Класс контроллера наследуется от класса classes/Controller.php

<?php
class Controller extends Singleton{
	function __call( $methodName,$args=array() ){
		if( is_callable( array($this,$methodName) ) )
			return call_user_func_array(array($this,$methodName),$args);
		else
			throw new Except('In controller '.get_called_class().' method '.$methodName.' not found!');
	}
}

Каркас фреймворка готов. Теперь создадим один жизненно важный контроллер application/controllers/UserController.php

Пользователи нужны в любой системе.

<?php
class UserController extends Controller{
	function actionIndex(){
		$model = new User();
		include ROOT.'application/views/user/index.php';
	}
}

Он пока ничего не делает. Лишь создает модель User и выводит свое представление на экран. Модель User наследует класс classes/Model.php

<?php
class Model{
	private $data = array();
	function __get($name){
		return isset($this->data[$name])?$this->data[$name]:null;
	}
	function __set($name,$value){
		$this->data[$name] = $value;
	}
}

и код модели application/models/User.php 

<?php
class User extends Model{
	public $name = 'Valeriy';
}

Как видите он тоже ничего особого не делает. 

Содержание application/views/user/index.php может быть любым. Но чтобы продемонстрировать взаимодействие контроллера и представления оно будет таким

Hello <?=$model->name?>!!!

Таким образом структура нашего фреймворка примет такой вид

- application
-- controllers
--- UserController.php
-- models
--- User.php
-- views
--- user
---- index.php
-- config.php
- ideal
-- classes
-- framework.php
-- config.php
.htaccess
main.php

Запустив в адресной строке 

http://sitename/index.html?controller=user&action=index в браузере мы увидим заветное

Hello Valeriy!!!

Вам сейчас кажется, что все это ерунда и можно было сделать все это проще. Но если вдуматься, у нашего фреймворка огромные возможности. Он полностью структурирован. Расширить его возможности не составит большого труда. Добавляем класс для работы с БД и класс для работы с шаблонами и вот у нас полноценный фреймоворк. Самое важное, что вы поймете написав такой велосипед, это принцип работы MVC фреймворков таких как Zend или Yii. А понимание принципов работы это 99% процентов успеха в любой разработке.

 

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

Данный цикл статей продолжиться. Следите за новостями. Желаю удачных велосипедов. 

Рассказать друзьям
author.jpg

Платная консультация по вопросам 2500 руб/час

Прочитали статью и остались вопросы? Меня зовут Валерий и я её автор. С радостью объясню Вам в скайпе все затруднительные моменты, которые остались за рамками статьи!

Подробнее ...

Добавить комментарий


Комментарии