Создание скомпилированных MVC фреймворков для PHP не раз приходила на ум кодерам.
Достоинства такого подхода:
Высокая производительность
Малая нагрузка файловой системы
Меньший расход памяти (при строгой типизированности)
Частичная обработка данных без интерпритации
И само собой не менее явные недостатки:
Если Вы не знаете C, то Вы полностью зависите от разработчиков
Проект может в любую секунду сдуться
В зависимости от архитектуры, часть модулей все равно приходится писать самому, что уменьшает выигрыш
В этой статье я попытаюсь познакомить с еще одной попыткой сделать скомпилированный PHP MVC Framework.
Знакомство
Сам я в поисках быстрого, надежного и способного фреймворка. Гуляя по зарубежным форумам и блогам, наткнулся на интересный бенчмарк, где некий фреймворк на «Hello World!» уделывал пузатых знаменитостей в несколько раз.
Пройти мимо такого я не смог и принялся узнавать, как это работает и развивается.
Проекту менее года. Первый коммит на github — 10 января 2012. И да, это open source проект. Насколько я понял, сейчас им более менее активно занимаются 3 человека.
Авторы пытаются дать возможность людям писать код, не задумываясь о его производительности, придерживаясь стандартов и давая доступ к большинству используемых методов, которые загружаются в память со стартом сервера.
Пример
Лучший способ изучить framework — сделать что-нибудь на нем, параллельно изучая документацию.
Я сделал 3-х страничное приложение, показывающее некоторые возможности phalcon, а именно:
Роутинг
Работа с БД, на внутреннем языке PHQL
Работа с events
Фильтрация данных
Кеширование
DI (dependency injection)
Возможности встроенного движка шаблонов Volt
Кроме того, разработчики заявляют гораздо большее:
ODM — работа с документо-ориентированными БД
Многоязычность
Автоматическое создание приложений CRUD из консоли или веб-морды
Весь код будет лежать по пути /apps/, инициализация приложения /public/index.php. На последний файл собственно и нужно настроить редирект несуществующих страниц, через apache (.htaccess в папках / и /public/ ) или nginx.
Инициализация:
Про инициализацию вкратце под спойлером.
<?php
//загружаем конфигурационные данные из ini файла
$config = new PhalconConfigAdapterIni( '../apps/config/config.ini' );
//Подключаем загрузчик, показывая ему, где будут лежать вызываемые классы
$loader = new PhalconLoader();
$loader->registerDirs(array(
$config->application->controllersDir,
$config->application->modelsDir,
$config->application->myDir,
));
$loader->register();
//Подкючаем DI
$di = new PhalconDI();
//Компонент, отвечающий за обработку url.
$di->set('url', function() use ($config){
$url = new PhalconMvcUrl();
return $url;
});
//Подключаем модель соединения с БД, доступны ( Mysql, PostgreSQL, SQLite)
$di->set('db', function() use ($config) {
$connection = new PhalconDbAdapterPdoMysql(array(
"host" => $config->database->host,
"username" => $config->database->username,
"password" => $config->database->password,
"dbname" => $config->database->name
));
return $connection;
});
//2 вспомогательных компонента для работы с БД
$di->set('modelsManager', function(){
return new PhalconMvcModelManager();
});
//Указываем, где хранится мета-данным из БД, доступны (Apc, Files, Memory, Session)
$di->set('modelsMetadata', function(){
return new PhalconMvcModelMetadataMemory();
});
//Подключаем роутер
$di->set('router', 'PhalconMvcRouter');
//Подключаем диспетчер, чтобы иметь доступ к методам view из контроллера. Можно назначить свой обработчик.
$di->set('dispatcher', function() use ($di) {
$dispatcher = new PhalconMvcDispatcher();
return $dispatcher;
});
//Обработка ответов и запросов
$di->set('response', 'PhalconHttpResponse');
$di->set('request', 'PhalconHttpRequest');
//Подключение фильтра данных
$di->set('filter', function(){
return new PhalconFilter();
});
//Подключаем сервис внутреннего движка Volt с настройками
$di->set('voltService', function($view, $di) use ($config) {
$volt = new PhalconMvcViewEngineVolt($view, $di);
$volt->setOptions(array(
"compiledPath" => $config->application->templCompDir,
"compiledExtension" => ".compiled"
));
return $volt;
});
//Подключаем компонент, отвечающий за вид. Также назначаем уме eventManager,
//который, получает несколько событий выбрасываемых view. Таким образом мы
//можем манипулировать с шаблонами как захотим
$di->set('view', function() use ($config) {
$eventsManager = new PhalconEventsManager();
$viewManager = new ViewManager();
$eventsManager->attach('view', $viewManager);
$view = new PhalconMvcView();
$view->setViewsDir( $config->application->viewsDir );
$view->registerEngines(array(
".phtml" => 'voltService'
));
$view->setEventsManager($eventsManager);
return $view;
});
//Подключаем фронт и бакенд кеширование, доступны front (Base64, Data, None, Output) и backend (Apc, File, Memcache, Mongo), само собой, можно дописать и свои
$di->set('cache', function(){
$frontCache = new PhalconCacheFrontendData(array(
"lifetime" => 60
));
$cache = new PhalconCacheBackendApc($frontCache);
return $cache;
});
//собственно инициализация и вывод ошибок как есть на экран
try {
$application = new PhalconMvcApplication();
$application->setDI($di);
echo $application->handle()->getContent();
}
catch(PhalconException $e){
echo $e->getMessage();
}
Большую часть можно подключить автоматически, вот так:
$di = new PhalconDIFactoryDefault();
Контроллеры и свои классы:
Теперь, в Вашем приложении доступен стандартный роутинг /baseUri/ControllerName/ActionName/, подключены методы, которые через di наследуют контроллеры $this->cache, $this->dispatcher, $this->db…
Также Вы можете вызывать свои классы, находящиеся в папке /apps/My/
Класс ViewManager получит все события, генерируемые View. На интересует beforeRender. В нем, мы передадим в вид переменные, содержащие название класса и действия.
Также мы создадим класс BaseController
class BaseController extends PhalconMvcController
{
public function initialize()
{
PhalconTag::prependTitle('Example | ');
}
}
class IndexController extends BaseController {
public function initialize()
{
//Устанавливаем текст в тег title, также можно добавлять постфикс (сделано в контроллере Poll)
PhalconTag::setTitle('Index');
parent::initialize();
}
public function indexAction(){
}
}
Он наследует стандартный класс. Наши контроллеры, будут уже наследовать BaseController и получать префикс к html тегу title.
View
Phalcon предоставляет довольно удобный метод шаблонов, где существует иерархия.
Шаблоны хранятся в папке /apps/views/
Корневой шаблон /apps/views/index.phtm рендерится всеми контроллерами, кроме тех, где явно задан другой путь.
Этот файл может содержать в себе дальнейший путь иерархии:
{{ content() }}
Я использовал синтаксис встроенного движка шаблонов. Для других движков тоже есть свои аналоги.
Эта функция вызывает следующий шаблон, расположенный по пути: /apps/view/layer/ControllerName.phtml
По сути, задавая особое оформление для определенного контроллера, как я и сделал в своем приложении. В нем также можно вызвать content(), тем самым подгрузив шаблон /apps/views/ControllerName/ActionName.phtml
В шаблонах можно подгружать другие шаблоны, заменять предустановленные блоки, вызывать пользовательские и предопределенные движком функции, обращаться к методам Models.
Только Volt их молодой проект, поэтому многих функций в нем нет, но они запланированы на ближайшие релизы.
Из коробки, поддерживают Slim, Smarty, Mustache…
Из коробки есть возможность кеширования скомпилированных шаблонов, только в версии до 0.6.0 включительно, есть баг, который обнуляет кеш и выдает пустую страницу.
Модели
Подключение к БД, осуществляется с помощью PDO. Имеется 2 реализации работы с базой данных, объектно-ориентированный путь и PHQL, свой обработчик псевдо SQL строк.
К сожалению, первый подходит только для довольно простых запросов, а второй будет сложным, для тех, кто привык к первому.
Модель в приложении
class Poll extends PhalconMvcModel
{
//объявление используемых переменных в строках, явное указание, какие доступны напрямую public
public $id;
public $gender;
public $age;
public $read_modern;
public $authors;
public $genre;
public $method;
public $timestamp;
//метод вызывается при инициализации БД, должен возвращать имя используемой таблицы
public function getSource()
{
return 'answers';
}
//выбор метода соединения при инициализации
public function initialize()
{
$this->setConnectionService('db');
}
}
В моделях, для себя, ничего нового и интересного не нашел, разве что хранение мета данных в разных местах. Также их можно полностью предопределить.
Недостатки
Я не до конца разобрался во всем, что может этот фреймворк, но все же заметил недостатки:
У меня так и не получилось собрать dll под windows. Разработчики так и не ответили.
На vps не смог собрать версию 0.6.1, сообщал разработчикам, так и не смогли разобраться.
Не нашел способа настроить роутинг для статичного файла example.com/tp/score.json Файла нет — роутер к контроллеру и методу, иначе статичный файл. Буду разбираться.
Очень мало возможностей у встроенного шаблонизатора Volt.
Не нашел способа вывода Flash сообщений без использования сессий, не отключая рендеринг.
Сильно разбитая документации по моделям — 3 отдельных раздела, нет структурности.
В большинстве случаев невозможно использовать на shared хостингах.
Трудно назвать это недостатками. Разве что нет прямого общения с разработчиками, кроме issue list на гитхабе.
Функционал приложения
Имеем приложение с главной страницей, страницей опроса и вывода результатов. Отсюда работа с БД, отправка данных с фильтрацией, и получение с минутным кешированием в Apc.
Синтетические тесты:
1. -c50 -d5 -r10
2. -c100 -d2 -r10
3. -c300 -d2 -r20 (LA — 0.7)
4. -c400 -d1 -r20 (LA — 0.47)
5. -c800 -d1 -r30 (LA — 1.1)
6. -c1000 -b -r20 (LA — 2) (-с: конкуренты, -d: задержка перед повторным вызовом, -r: количество повторений, -b: вызов без задержки, LA: load average)
Siege делает в рандомном порядке 3 запроса, все динамические. 2 из них с запросами к БД, с кешированием в 1 минуту.
При большой конкурентности появились ошибки (до 2% запросов).
Максимально обрабатывал 516 запросов в секунду. Довольно недурно. Если хранить запрашиваемые json в файлах и обновлять их по cron, то можно нагрузку в разы сократить.