Shadez Framework [php, mvc]

в 5:33, , рубрики: framework, mvc, php, Веб-разработка, метки: , ,

Всем доброго.

Хочу представить вам результат своего вот уже как почти 9-ти месячного кодинга очередного MVC-фреймворка на PHP.

Зачем? Изначально писалось для одного проекта, готовое решение использовать было нежелательно, да и времени разбираться в громадинах вроде Zend Framework, Yii и прочих совсем не было. Релиз делаю ради «просто поделиться». Заодно и послушать критику в адрес моего кода :)

Итак, кого заинтересовал новый (и, возможно, интересный) продукт, прошу за мной.

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

Features

Как и большинство PHP фреймворков, мой написан с использованием паттерна MVC, используется система компонентов для удобного обмена сообщениями между классами, i18n, PDO для БД, Query Builder ООП-слои для сессий, cookies и т.д.
Работает всё это достаточно быстро, в основном — за счет ленивой подгрузки файлов компонентов. Вчера провел тесты ApacheBench, результаты (количество Requests Per Second) следующие:
Shadez Framework [php, mvc]
Zend Framework 1 тоже тестировался, но в его результаты что-то лично мне слабо верится, поэтому в график включать не стал — 1324 RPS. Логи ab доступны здесь, конфигурация машины:

OS: Microsoft Windows 7 Ultimate (x64)
CPU: AMD Athlon 64x2 4200+
Memory: 4GB
Web: Apache 2.4.1
PHP: 5.4
Ab: ab -t 30 -c 10 URL

Компоненты

Компонент — это обычный PHP класс. В нём нет кучи разных файлов, множества конфигураций на разных языках и т.д. Только один файл, только один класс.
Все компоненты наследуются от базового родительского класса Component и именуются по шаблону «class <Name>_[Type]_Component extends Component», где Name — имя компонента (например, ImageUploader), а Type — его тип (или группа). В случае, если тип не задан, компонент помещается в папку app/components/, иначе — в app/components/<TypeName/), где TypeName — тот же самый Type.

Таким образом, компонент ImageUploader_Component можно будет найти в файле app/components/Imageuploader.php, а компонент Users_Model_Component — в app/components/model/Users.php.

Модели

По умолчанию модели определяют принадлежность к конкретной таблице конкретной БД — описывают поля, алиасы, информацию для генерации веб-форм. Кроме того, могут выполнять CRUD-операции.
Получить данные модели можно таким образом:

// this->i() возвращает новый инстанс компонента $name (с типом $type, если указан)
// почти аналогичная функция this->c() возвращает уже ранее созданный объект компонента (для использования одиночек)
$model = $this->i('SomeModel', 'Model');
// загружаем запись с ID == 1
$model->load(array(':id' => 1));
echo 'field1: ' . $model->field1 . ', field2: ' . $model->field2;
$model->field1 = 'New Value';
$model->save(); // Update
$model->delete(); // Delete from DB

Более подробно — в документации.

Контроллеры

Каждый контроллер наследуется от класса Controller_Component, в котором реализованы все базовые функции абстрактного контроллера.
Работа контроллера заключается в следующем:

  • Поиск и вызов необходимого действия (action)
  • Хранение блоков представления
  • Инициализация бизнес-логики приложения

При создании у каждого контроллера будут вызваны следующие методы:

  1. Controller_Component::run() — используется для выполнения повторяющихся действий в пределах одного контроллера
  2. Controller_Component::action<действие>() — в зависимости от запроса вызывается конкретное действие (например, при запросе /docs/controllers у контроллера Docs запускается метод actionControllers). Если действие не задано, вызывается метод actionIndex
  3. Controller_Component::finish() — по умолчанию используется для вызова компонента представления (рендеринг регионов и блоков)

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

<?php

class Example_Controller_Component extends Controller_Component
{
	// Номер индекса массива-запроса (URL), в котором нужно искать действие
	// http://example.org/ru/example/info - индекс у "info" равен 1 (нумерация идет с 0)
	// Язык из URL-запроса за действие не считается и в массив-запрос не добавляется
	protected $m_webActionIndex = 1;

	protected function actionIndex()
	{
		// задаем заголовок страницы (будет иметь вид "Action Index :: Website Title")
		$this->m_pageTitle = 'Action Index';
		// задаем блок, который будет рендериться
		$this->m_blocks = 'index';
	}

	protected function actionInfo()
	{
		$this->m_pageTitle = 'Action Info';
		$this->m_blocks = array('info', 'footer');
	}

	protected function finish()
	{
		$this->c('View')
			// строим необходимые блоки
			->buildBlocks($this->m_blocks)
			// строим страницу с шаблоном layout.tpl из папки templates/application/layout
			->buildPage('layout', 'application.layout');
	}

	public function block_index()
	{
		return $this->i('Block')
			->setName('index') // задаем имя блока
			->setTemplate('index', 'application.contents') // задаем шаблон и путь к нему
			->setRegion('pagecontent'); // задаем регион блока
	}

	public function block_info()
	{
		return $this->i('Block')
			->setName('info')
			->setTemplate('info', 'application.contents')
			->setRegion('pagecontent');
	}

	public function block_footer()
	{
		return $this->i('Block')
			->setName('footer')
			->setTemplate('footer', 'application.contents')
			->setRegion('footer');
	}
};

Код должен находиться в файле app/components/controller/Example.php.
Забыл упомянуть, что роутинг работает на mod_rewrite.

Более подробно — в документации.

Представление

В данном фреймворке к представлению относятся следующие вещи:

  • i18n/l10n
  • Выдача ссылок на клиентские файлы (CSS/JS)
  • Выдача мета-тегов
  • «Рендеринг» (по сути — простой require_once, т.к. файл шаблона — такой же PHP скрипт, только с расширением tpl) и отображение регионов и блоков

Первые 3 пункта должны быть понятны (надеюсь), поэтому здесь пока что расскажу о блоках и регионах.
Регион — конкретное место в шаблоне страницы (header, footer, menu, etc.). Блоки являются содержимым регионов. Их можно создавать либо вручную для каждого контроллера, либо задать их конфигурацию в файле настроек.
Вывод определенного региона выглядит следующим образом:

echo $this->getRegionContents('pagecontent'); // напрямую из шаблона
// либо
echo $this->c('View')->getRegionContents('pagecontent'); // из компонента
// либо
echo $this->c('View')->getRegion('pagecontent')->getRegionHTML(); // обращение непосредственно к самому региону
// либо HTML одного блока (в случае, если к региону подключено несколько блоков)
echo $this->c('View')->getRegion('pagecontent')->getBlock('info')->getBlockHTML();

Подробности — как обычно в документации.

Заключение

Если копнуть глубже, вести речь об отдельных компонентах и возможностях можно достаточно долго. В ядре есть реализации генератора запросов (QueryBuilder_Db_Component), поддержки локализаций (I18n_Component), журналирования (Log_Component), сервера API приложения (Api_Component и SiteApi_Component) и ещё много чего интересного.

На выпуске в паблик разработка не останавливается, проект так или иначе будет развиваться, даже если успеха и не будет — как минимум, он нужен мне для работы :)
Буду рад, если кто-то захочет попробовать поработать с фреймворком, в случае затруднений помогу разобраться.

Ссылки:
Сайт с документацией (основная часть написана, сейчас готовятся примеры использования отдельных компонентов) — www.phpmvc.ru
Github-репозиторий проекта — www.github.com/Shadez/Framework
Форум — community.phpmvc.ru

Спасибо за внимание!

Автор: Shadez


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js