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> 

ссылка на оригинал статьи http://habrahabr.ru/post/197254/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *