
Создание скомпилированных 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 из консоли или веб-морды
- Поддержка нескольких популярных движков шаблонов
- Микро приложения (для создания REST API)
- Создание логгера любой конфигурации
- Создание своего event manager с fire и catch
- Встроенный обработчик html тегов для шаблонов
- Поддержка nemespace и мульти-модульности
- Обработка сессий и flash сообщений
- ACL
Иерархия проекта:
xmpl/ apps/ app/config/ app/controllers/ app/models/ app/views/ app/My/ public/ public/img/ public/css/ public/js/
Весь код будет лежать по пути /apps/, инициализация приложения /public/index.php. На последний файл собственно и нужно настроить редирект несуществующих страниц, через apache (.htaccess в папках / и /public/ ) или nginx.
Инициализация:
Про инициализацию вкратце под спойлером.
<?php
//загружаем конфигурационные данные из ini файла
$config = new Phalcon\Config\Adapter\Ini( '../apps/config/config.ini' );
//Подключаем загрузчик, показывая ему, где будут лежать вызываемые классы
$loader = new \Phalcon\Loader();
$loader->registerDirs(array(
$config->application->controllersDir,
$config->application->modelsDir,
$config->application->myDir,
));
$loader->register();
//Подкючаем DI
$di = new \Phalcon\DI();
//Компонент, отвечающий за обработку url.
$di->set('url', function() use ($config){
$url = new \Phalcon\Mvc\Url();
return $url;
});
//Подключаем модель соединения с БД, доступны ( Mysql, PostgreSQL, SQLite)
$di->set('db', function() use ($config) {
$connection = new \Phalcon\Db\Adapter\Pdo\Mysql(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 Phalcon\Mvc\Model\Manager();
});
//Указываем, где хранится мета-данным из БД, доступны (Apc, Files, Memory, Session)
$di->set('modelsMetadata', function(){
return new \Phalcon\Mvc\Model\Metadata\Memory();
});
//Подключаем роутер
$di->set('router', 'Phalcon\Mvc\Router');
//Подключаем диспетчер, чтобы иметь доступ к методам view из контроллера. Можно назначить свой обработчик.
$di->set('dispatcher', function() use ($di) {
$dispatcher = new Phalcon\Mvc\Dispatcher();
return $dispatcher;
});
//Обработка ответов и запросов
$di->set('response', 'Phalcon\Http\Response');
$di->set('request', 'Phalcon\Http\Request');
//Подключение фильтра данных
$di->set('filter', function(){
return new \Phalcon\Filter();
});
//Подключаем сервис внутреннего движка Volt с настройками
$di->set('voltService', function($view, $di) use ($config) {
$volt = new Phalcon\Mvc\View\Engine\Volt($view, $di);
$volt->setOptions(array(
"compiledPath" => $config->application->templCompDir,
"compiledExtension" => ".compiled"
));
return $volt;
});
//Подключаем компонент, отвечающий за вид. Также назначаем уме eventManager,
//который, получает несколько событий выбрасываемых view. Таким образом мы
//можем манипулировать с шаблонами как захотим
$di->set('view', function() use ($config) {
$eventsManager = new \Phalcon\Events\Manager();
$viewManager = new ViewManager();
$eventsManager->attach('view', $viewManager);
$view = new \Phalcon\Mvc\View();
$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 Phalcon\Cache\Frontend\Data(array(
"lifetime" => 60
));
$cache = new Phalcon\Cache\Backend\Apc($frontCache);
return $cache;
});
//собственно инициализация и вывод ошибок как есть на экран
try {
$application = new \Phalcon\Mvc\Application();
$application->setDI($di);
echo $application->handle()->getContent();
}
catch(Phalcon\Exception $e){
echo $e->getMessage();
}
Большую часть можно подключить автоматически, вот так:
$di = new Phalcon\DI\FactoryDefault();
Контроллеры и свои классы:
Теперь, в Вашем приложении доступен стандартный роутинг /baseUri/ControllerName/ActionName/, подключены методы, которые через di наследуют контроллеры $this->cache, $this->dispatcher, $this->db…
Также Вы можете вызывать свои классы, находящиеся в папке /apps/My/
Класс ViewManager получит все события, генерируемые View. На интересует beforeRender. В нем, мы передадим в вид переменные, содержащие название класса и действия.
Также мы создадим класс BaseController
Он наследует стандартный класс. Наши контроллеры, будут уже наследовать BaseController и получать префикс к html тегу title.
class BaseController extends \Phalcon\Mvc\Controller
{
public function initialize()
{
Phalcon\Tag::prependTitle('Example | ');
}
}
class IndexController extends BaseController {
public function initialize()
{
//Устанавливаем текст в тег title, также можно добавлять постфикс (сделано в контроллере Poll)
Phalcon\Tag::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 Phalcon\Mvc\Model
{
//объявление используемых переменных в строках, явное указание, какие доступны напрямую 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.
Железо: VPS хостинг. Процессор х1 500 МГц, ОП 256 Мб. Лошадка слабая.
Подключен APC. Настройки все дефолтные. Сервер: Nginx — PHP-FPM — PHP — MySQL
Синтетические тесты:
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, то можно нагрузку в разы сократить.
Провел еще пару тестов с siege. -c500 -r10 -d1 -i
1. APC отключен
2. APC включен с маленьким буфером и stat=0

Ссылки
Сайт Phalcon
Репозиторий Phalcon
Работающий пример
Исходники примера на GitHub
P.S.
Опрос в приложении не просто так выбран, пожалуйста, отвечайте честно ;)
И извиняюсь за сумбур, в одной статье выложить все невозможно.
19.11.2012 23:00
Кто-то решил заддосить по запросу, возвращающему результаты с БД. Логи растут как на дрожжях. LA колеблется от 1 до 4. Сайт грузится. Я очень рад :)
Ддосят при помощи wrk. Судя по логам доходит до 60 запросов в секунду. ip 188.254.105.18
19.11.2012 23:43
Наспамил мне 0.5ГБ лог и хватит. Пока ддос.