...

суббота, 12 октября 2013 г.

[recovery mode] Phalcon PHP фрейморк. Работа с аннотациями

«vivo, presto, prestissimo...»

О 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 действительно поражают.

index.php


<?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();
}







IndexController.php


<?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';
}
}







EntityForm.php


<?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();
}
}







Users.php


<?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;
}







form.volt


<h2>Test form in Volt</h2>
<hr>
{{ myform.renderform() }}
<hr>



This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий