О Phalcon пока еще мало материалов, но фреймворк достаточно интересный и заслуживающий внимания. Одно из интересных решений Phalcon — расширенные возможности по использованию аннотаций. Парсер написан на C, работает очень быстро. Позволяет легко и непринужденно перенести часть настроек (чуть ли не большую часть) из конфигурационных файлов в код.
Часть I. Vivo (Быстро).
Назначение маршрутов в Phalcon довольно «творчеcкая» задача.
Многие примеры пестрят разными способами назначения маршрутов.
На Хабре даже проскочил пример использования файлов xml…
И это в то время, когда многие фреймворки предлагают маршрутизацию посредством аннотаций.
А что же Phalcon?
Phalcon скромно и тихо указывает в документации, что возможна маршрутизация на аннотациях.
И что для этого авторы Phalcon просто создали парсер аннотаций на C.
Впервые!
Что нужно для включения аннотаций?
Всего ничего.
В bootstrap файле внедряем сервис аннотаций, всего несколько строк кода. И все.
... //set routers $di->set('router', function() { $router = new \Phalcon\Mvc\Router\Annotations(false); $router->removeExtraSlashes(true); $router->setUriSource(\Phalcon\Mvc\Router::URI_SOURCE_SERVER_REQUEST_URI); $router->addResource('Index'); $router->notFound([ "controller" => "index", "action" => "page404" ]); return $router; }); ...
Теперь в маршруты (в том числе с префиксами), указываем прямо в контроллере.
... /** * @Post("/create") */ public function createAction() { /... } ...
Более подробно маршрутизация (типы запросов, пареметры) описана в документации.
Часть II. Presto (Быстро, насколько возможно)
Маршрутизация на аннотациях — это, конечно, хорошо. Но нам еще в проектах приходится иметь дело с данными. И здесь можно заметить одну особенность Phalcon.
При работе с базой данных он делает обычно 3 запроса.
1-й проверяет наличие таблицы в базе.
2-й получает метаданные таблицы.
3-й непосредственно запрос.
Не знаю, насколько это правильно, не буду спорить. Но несколько неудобно.
Нам же 3 запроса ни к чему. Нам хотелось бы иметь метаданные таблицы где-то в загашнике.
В кэше, например.
И Phalcon того же мнения. Поэтому предлагает сохранять метаданные в кэше. При этом можно использовать аннотации.
Опять DI, опять botstrap:
... //Set a models manager $di->set('modelsManager', new \Phalcon\Mvc\Model\Manager()); //Set the models cache service $di->set('modelsCache', function() { //Cache data for one day by default $frontCache = new \Phalcon\Cache\Frontend\Data([ "lifetime" => 86400 ]); $cache = new \Phalcon\Cache\Backend\Memcache($frontCache, [ "host" => "localhost", "port" => "11211", 'persistent' => TRUE, ]); return $cache; }); $di->set('modelsMetadata', function() { // Create a meta-data manager with APC //$metaData = new \Phalcon\Mvc\Model\MetaData\Apc([ // "lifetime" => 86400, // "prefix" => "general-phsql" //]); $metaData = new \Phalcon\Mvc\Model\MetaData\Memory([ 'prefix' => 'general', ]); $metaData->setStrategy(new StrategyAnnotations()); return $metaData; }); ...
После чего, метаданные, считанные единожды, хранятся в кэш.
На время разработки мы можем переключиться на хранение в памяти.
Здесь, в принципе, все. Опять же, более подробно в документации.
Что же, ничего удивительного в этих механизмах нет. За исключением того, что работает это очень быстро. Быстро так, насколько это может быть достигнуто в компоненте, созданном на C. То есть, практически незаметно.
Но эти механизмы есть и у других фрейморков.
Нам же хочется чего-то вкусненького, какой-то изюминки…
Часть III. Prestissimo (Еще быстрее).
Наверное, не секрет, что при проэктировании сайта приходится писать админку.
А это формы, формы, и еще раз формы. Много форм. А это утомляет…
Хочется автоматизации.
Из чего состоит форма? Конечно же, ключевой элемент — это тэг input.
Он, как правило, имеет тип type, длину length, шаблон заполнения pattern и т.д.
И все это для каждой формы нужно указывать… Для каждого поля таблицы…
Хочется автоматизации. При этом не хочется писать много кода.
А описать универсальную форму для любой сущности.
И здесь нам опять пригодятся аннотации. Парсер, опять же, на C.
Phalcon предлагает компонент Phalcon\Forms\Form.
Возьмем простейшую таблицу Users. Ее модель:
<?php namespace Frontend\Model; class Users extends \Phalcon\Mvc\Model { /** * @Primary * @Identity * @Column(type="integer", nullable=false) * @FormOptions(type=hidden) */ public $id; /** * @Column(type="string", nullable=false) * @FormOptions(type=text, length=32) */ public $name; /** * @Column(type="integer", nullable=false) * @FormOptions(type=email) */ public $email; /** * @Column(type="integer", nullable=false) * @FormOptions(type=text, length=9, pattern='[0-9]{9}') */ public $indcode; }
Здесь мы применяем собственную аннотацию, где указываем нужные нам для построения формы опции.
Да, эту аннотацию мы придумали сейчас, сами, для применения в конкретном случае.
У нас это @FormOptions. В атрибуте type мы указываем тип поля, необходимый нам для input.
Phalcon предлагает следующие типы Phalcon\Forms\Element :
Phalcon\Forms\Element\Check
Phalcon\Forms\Element\Date
Phalcon\Forms\Element\Email
Phalcon\Forms\Element\File
Phalcon\Forms\Element\Hidden
Phalcon\Forms\Element\Numeric
Phalcon\Forms\Element\Password
Phalcon\Forms\Element\Select
Phalcon\Forms\Element\Submit
Phalcon\Forms\Element\Text
Phalcon\Forms\Element\TextArea
Более, чем достаточно.
Осталось дело за малым…
Нужно как-то «научить» Phalcon распознавать наши аннотации…
Нет ничего проще!
bootstrap — включаем парсер аннотаций.
... //Annotations $di->set('annotations', function() { return new \Phalcon\Annotations\Adapter\Memory(); }); ...
В процессе разработки можно использовать адаптер памяти,
в производстве можно переключиться на хранение в файлах, APC, XCache.
Теперь создаем класс формы для любой сущности.
<?php use Phalcon\Forms\Form, \Phalcon\Forms\Element\Submit as Submit; class EntityForm extends Form { public $fields = []; private $classprefix = '\\Phalcon\\Forms\\Element\\'; public $action; /** * @param object $model, action */ public function initialize($model, $action) { $this->action = $action; //Заполняем поля формы данными из модели $object = $model; $this->setEntity($object); //Получаем атрибуты модели $attributes = $this->modelsMetadata->getAttributes($object); // Получаем аннотации из модели $metadata = $this->annotations->get($object); // Считыаем аннотацию @FormOptions foreach ( $attributes as $attribute ) { $this->fields[$attribute] = $metadata ->getPropertiesAnnotations()[$attribute] ->get('FormOptions') ->getArguments(); } // Создаем поля формы с учетом видимости foreach ($this->fields as $field => $type) { $fieldtype = array_shift($type); // атрибут type в аннотации нам более не нужен $fieldclass = $this->classprefix.$fieldtype; $this->add(new $fieldclass($field, $type)); //устанавливаем label если поле не скрыто if ( $fieldtype !== 'hidden') { $this->get($field)->setLabel($this->get($field)->getName()); } } // Добавляем кнопку отправки $this->add(new Submit('submit',[ 'value' => 'Send', ])); } public function renderform() { echo $this->tag->form([ $this->action, 'id' => 'actorform', ]); //fill form tags foreach ($this as $element) { // collect messages $messages = $this->getMessagesFor($element->getName()); if (count($messages)) { // each element render echo '<div class="messages">'; foreach ($messages as $message) { echo $message; } echo '</div>'; } echo '<div>'; echo '<label for="', $element->getName(), '">', $element->getLabel(), '</label>'; echo $element; echo '</div>'; } echo $this->tag->endForm(); } }
Здесь, при инициализации класса EntityForm мы считываем метаданные переданного объекта и его аннотации.
После этого внедряем все необходимые поля в форму.
Функция renderform просто выводит нашу форму в браузер.
Вернемся в контроллер, и создадим действие вывода формы:
... /** * @Get("/form") */ public function formAction() { $myform = new EntityForm(new Users(), 'create'); $this->view->setVars([ 'myform' => $myform, ]); } ...
и получателя:
... /** * @Post("/create") */ public function createAction() { echo '<pre>'; var_dump($_POST); echo '</pre>'; } ...
Остается только в шаблоне вывода (Volt) вывести форму:
<b>{{ myform.renderform() }}</b>
Вот и все.
Конечно же, необходимо добавить в класс формы CSRF-защиту, валидацию данных, сохранение.
Но задача этой статьи показать простоту и удобство использования аннотаций в Phalcon.
Эти возможности предоставлены нам благодаря мощному и быстрому парсеру аннотаций PhalconPHP.
И, когда начинаешь использовать Phalcon, понимаешь, что он действительно быстр.
И не только при выводе «Hello, world!».
Скорость и удобство работы с Phalcon действительно поражают.
<?php use Phalcon\Mvc\View\Engine\Volt; use Phalcon\Mvc\Model\MetaData\Strategy\Annotations as StrategyAnnotations; try { //Register an autoloader $loader = new \Phalcon\Loader(); $loader->registerDirs([ '../app/controllers/', '../app/models/', '../app/forms/' ]); $loader->registerNamespaces([ 'Frontend\\Model' => __DIR__.'/../app/models/', ]); $loader->register(); //Create a DI $di = new \Phalcon\DI\FactoryDefault(); //Set a models manager $di->set('modelsManager', new \Phalcon\Mvc\Model\Manager()); //Set the models cache service $di->set('modelsCache', function() { //Cache data for one day by default $frontCache = new \Phalcon\Cache\Frontend\Data([ "lifetime" => 86400 ]); $cache = new \Phalcon\Cache\Backend\Memcache($frontCache, [ "host" => "localhost", "port" => "11211", 'persistent' => TRUE, ]); return $cache; }); $di->set('modelsMetadata', function() { // Create a meta-data manager with APC //$metaData = new \Phalcon\Mvc\Model\MetaData\Apc([ // "lifetime" => 86400, // "prefix" => "general-phsql" //]); $metaData = new \Phalcon\Mvc\Model\MetaData\Memory([ 'prefix' => 'general', ]); $metaData->setStrategy(new StrategyAnnotations()); return $metaData; }); //SQL profiler $di->set('profiler', function(){ return new \Phalcon\Db\Profiler(); }, true); //set database connection $di->set('db', function() use ($di) { $eventsManager = new \Phalcon\Events\Manager(); //Get a shared instance of the DbProfiler $profiler = $di->getProfiler(); //Listen all the database events $eventsManager->attach('db', function($event, $connection) use ($profiler) { if ($event->getType() == 'beforeQuery') { $profiler->startProfile($connection->getSQLStatement()); } if ($event->getType() == 'afterQuery') { $profiler->stopProfile(); } }); $connection = new \Phalcon\Db\Adapter\Pdo\Mysql([ "host" => "localhost", "username" => "root", "password" => "12345", "dbname" => "general" ]); //Assign the eventsManager to the db adapter instance $connection->setEventsManager($eventsManager); return $connection; }); //Register Volt as a service $di->set('voltService', function($view, $di) { $volt = new Volt($view, $di); $volt->setOptions([ "compiledPath" => "../app/cache/", ]); return $volt; }); //Setting up the view component $di->set('view', function(){ $view = new \Phalcon\Mvc\View(); $view->setViewsDir('../app/views/'); $view->registerEngines([ ".volt" => 'voltService' ]); return $view; }); //Create Form manager $di->set('forms', function() { $forms = new \Phalcon\Forms\Manager(); return $forms; }); $di->set('session', function() use($di) { $session = new Phalcon\Session\Adapter\Files(); $session->setoptions([ 'uniqueId' => 'privatRsc', ]); $session->start(); return $session; }); //set routers $di->set('router', function() { $router = new \Phalcon\Mvc\Router\Annotations(false); $router->removeExtraSlashes(true); $router->setUriSource(\Phalcon\Mvc\Router::URI_SOURCE_SERVER_REQUEST_URI); $router->addResource('Index'); $router->notFound([ "controller" => "index", "action" => "page404" ]); return $router; }); //Annotations $di->set('annotations', function() { return new \Phalcon\Annotations\Adapter\Memory(); }); //Handle the request $application = new \Phalcon\Mvc\Application($di); echo $application->handle()->getContent(); } catch(\Phalcon\Exception $e) { echo "PhalconException: ", $e->getMessage(); }
<?php use \Frontend\Model\Users as Users; /** * @RoutePrefix("") **/ class IndexController extends \Phalcon\Mvc\Controller { /** * @Get("/") */ public function indexAction() { echo <h3>Index Action</h3>; } /** * @Get("/form") */ public function formAction() { $myform = new EntityForm(new Users(), 'create'); $this->view->setVars([ 'myform' => $myform, ]); } /** * @Post("/create") */ public function createAction() { echo '<pre>'; var_dump($_POST); echo '</pre>'; } /** * @Get("/page404") */ public function page404Action() { echo '404 - route not found'; } }
<?php use Phalcon\Forms\Form, \Phalcon\Forms\Element\Submit as Submit; class EntityForm extends Form { public $fields = []; private $classprefix = '\\Phalcon\\Forms\\Element\\'; public $action; /** * @param object $model, action */ public function initialize($model, $action) { $this->action = $action; //Set fields options from annotations $object = $model; $this->setEntity($object); $attributes = $this->modelsMetadata->getAttributes($object); $metadata = $this->annotations->get($object); foreach ( $attributes as $attribute ) { $this->fields[$attribute] = $metadata ->getPropertiesAnnotations()[$attribute] ->get('FormOptions') ->getArguments(); } foreach ($this->fields as $field => $type) { $fieldtype = array_shift($type); $fieldclass = $this->classprefix.$fieldtype; $this->add(new $fieldclass($field, $type)); if ( $fieldtype !== 'hidden') { $this->get($field)->setLabel($this->get($field)->getName()); } } $this->add(new Submit('submit',[ 'value' => 'Send', ])); } public function renderform() { echo $this->tag->form([ $this->action, 'id' => 'actorform', ]); //fill form tags foreach ($this as $element) { // collect messages $messages = $this->getMessagesFor($element->getName()); if (count($messages)) { // each element render echo '<div class="messages">'; foreach ($messages as $message) { echo $message; } echo '</div>'; } echo '<div>'; echo '<label for="', $element->getName(), '">', $element->getLabel(), '</label>'; echo $element; echo '</div>'; } echo $this->tag->endForm(); } }
<?php namespace Frontend\Model; class Users extends \Phalcon\Mvc\Model { /** * @Primary * @Identity * @Column(type="integer", nullable=false) * @FormOptions(type=hidden) */ public $id; /** * @Column(type="string", nullable=false) * @FormOptions(type=text, length=32) */ public $name; /** * @Column(type="integer", nullable=false) * @FormOptions(type=email) */ public $email; /** * @Column(type="integer", nullable=false) * @FormOptions(type=text, length=9, pattern='[0-9]{9}') */ public $indcode; }
<h2>Test form in Volt</h2> <hr> {{ myform.renderform() }} <hr>
ссылка на оригинал статьи http://habrahabr.ru/post/197254/
Добавить комментарий