symfony framework

индекс
85,28

Symfony2 Routing. Что новенького?

Недавно вышла Symfony 2 Preview Release. Я хочу рассказать какие изменения претерпела система роутинга во второй ветке фреймворка.


Итак начнем.

В статье я не описываю как работает роутинг в symfony. Об этом можно почитать тут или посмотреть код! Дальше я опишу, что изменилось во второй ветке фреймворка.

Что новенького?


  1. Система роутинга вынесена в компоненты, теперь ее можно использовать отдельно от фреймворка.
  2. Само собой переработан код, повышена производительность. На каждое правило роутинга теперь не создается объект (если конечно используется кэширование!).
  3. При сопоставлении роута и url'a. Сначала используется функция strpos (когда это возможно), а потом только preg_match.
  4. Генерацией url'ов и их сопоставлением занимаются разные объекты (ProjectUrlGenerator, ProjectUrlMatcher).
  5. Любой класс системы роутинга теперь можно заменить, путем передачи соответствующих параметров в конструктор класса Symfony\Components\Routing\Router.
  6. Помимо дампа правил в php, есть дампер для Apache. Думаю можно будет написать и дампер файлов для nginx при желании.


Чего нет?


  1. Убрали (а может еще не сделали) возможность использования звезд в паттернах. Правило "/:module/:action/*" работать как прежде не будет.


Ниже я привожу участки кода иллюстрирующие новенькое ). Роутинг я тестил отдельно от фреймворка, так что данные в правилах отличаются, но суть остается прежней. А именно отличается массив $defaults. Вместо параметров _bundle, _controller, _action, я сделал module, action.

Пример дампа правил в Symfony 1.2:


<?php
// auto-generated by sfRoutingConfigHandler
// date: 2010/03/14 00:46:57
return array(
'addTopic' => new sfRoute('blogs/add-topic/', array (
 'module' => 'blogsEdit',
 'action' => 'addTopic',
), array (), array ()),
'editTopic' => new sfRoute('blogs/edit-topic/:id/', array (
 'module' => 'blogsEdit',
 'action' => 'editTopic',
), array (), array ()),
'themeBlog' => new sfRoute('blogs/:name/', array (
 'module' => 'blogs',
 'action' => 'blog',
), array (), array ())
// .. и т.д.
);


* This source code was highlighted with Source Code Highlighter.

Пример дампа правил в Symfony 1.4:


<?php
// auto-generated by sfRoutingConfigHandler
// date: 2010/03/14 00:46:57

$this->routes['route-1'] = unserialize('Сериализованный объект');
$this->routes['route-2'] = unserialize('Сериализованный объект');
$this->routes['route-6'] = unserialize('Сериализованный объект');
$this->routes['route-7'] = unserialize('Сериализованный объект');
$this->routes['route-8'] = unserialize('Сериализованный объект');
$this->routes['route-9'] = unserialize('Сериализованный объект');
$this->routes['route-10'] = unserialize('Сериализованный объект');
$this->routes['route-11'] = unserialize('Сериализованный объект');
$this->routes['route-12'] = unserialize('Сериализованный объект');

$this->routes['homepage'] = unserialize('Сериализованный объект');
$this->routes['default_index'] = unserialize('Сериализованный объект');
$this->routes['default'] = unserialize('Сериализованный объект');

* This source code was highlighted with Source Code Highlighter.


Пример правил роутинга в Symfony 2:


# blog
addTopic:
 pattern: blogs/add-topic/
 defaults: { module: blogsEdit, action: addTopic }

editTopic:
 pattern: blogs/edit-topic/:id/
 defaults: { module: blogsEdit, action: editTopic }

themeBlog:
 pattern: blogs/:name/
 defaults: { module: blogs, action: blog }


* This source code was highlighted with Source Code Highlighter.


Пример класса ProjectUrlMatcher в Symfony 2:


<?php

/**
* ProjectUrlMatcher
*
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\\Components\\Routing\\Matcher\\UrlMatcher
{
 /**
  * Constructor.
  */
 public function __construct(array $context = array(), array $defaults = array())
 {
  $this->context = $context;
  $this->defaults = $defaults;
 }

 public function match($url)
 {
  $url = $this->normalizeUrl($url);

  if (0 === strpos($url, '/blogs/add-topic') && preg_match('#^/blogs/add\-topic$#x', $url, $matches))
   return array_merge($this->mergeDefaults($matches, array ( 'module' => 'blogsEdit''action' => 'addTopic',)), array('_route' => 'addTopic'));

  if (0 === strpos($url, '/blogs/edit-topic') && preg_match('#^/blogs/edit\-topic/(?P<id>[^/\.]+?)$#x', $url, $matches))
   return array_merge($this->mergeDefaults($matches, array ( 'module' => 'blogsEdit''action' => 'editTopic',)), array('_route' => 'editTopic'));

  if (0 === strpos($url, '/blogs') && preg_match('#^/blogs/(?P<name>[^/\.]+?)$#x', $url, $matches))
   return array_merge($this->mergeDefaults($matches, array ( 'module' => 'blogs''action' => 'blog',)), array('_route' => 'themeBlog'));
   
   // и т.д.

  return false;
 }
}

* This source code was highlighted with Source Code Highlighter.


Пример класса ProjectUrlGenerator Symfony 2:


<?php

/**
* ProjectUrlGenerator
*
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlGenerator extends Symfony\\Components\\Routing\\Generator\\UrlGenerator
{
 /**
  * Constructor.
  */
 public function __construct(array $context = array(), array $defaults = array())
 {
  $this->context = $context;
  $this->defaults = $defaults;
 }

 public function generate($name, array $parameters, $absolute = false)
 {
  if (!method_exists($this, $method = 'get'.$name.'RouteInfo'))
  {
   throw new InvalidArgumentException(sprintf('Route "%s" does not exist.', $name));
  }

  list($variables, $defaults, $tokens) = $this->$method();

  return $this->doGenerate($variables, $defaults, $tokens, $parameters, $name, $absolute);
 }

 protected function getaddTopicRouteInfo()
 {
  return array(array (), array_merge($this->defaults, array ( 'module' => 'blogsEdit''action' => 'addTopic',)), array ( 0 =>  array (  0 => 'text',  1 => '/',  2 => '',  3 => NULL, ), 1 =>  array (  0 => 'text',  1 => '/',  2 => 'add-topic',  3 => NULL, ), 2 =>  array (  0 => 'text',  1 => '/',  2 => 'blogs',  3 => NULL, ),));
 }

 protected function geteditTopicRouteInfo()
 {
  return array(array ( 'id' => ':id',), array_merge($this->defaults, array ( 'module' => 'blogsEdit''action' => 'editTopic',)), array ( 0 =>  array (  0 => 'text',  1 => '/',  2 => '',  3 => NULL, ), 1 =>  array (  0 => 'variable',  1 => '/',  2 => ':id',  3 => 'id', ), 2 =>  array (  0 => 'text',  1 => '/',  2 => 'edit-topic',  3 => NULL, ), 3 =>  array (  0 => 'text',  1 => '/',  2 => 'blogs',  3 => NULL, ),));
 }

 protected function getthemeBlogRouteInfo()
 {
  return array(array ( 'name' => ':name',), array_merge($this->defaults, array ( 'module' => 'blogs''action' => 'blog',)), array ( 0 =>  array (  0 => 'text',  1 => '/',  2 => '',  3 => NULL, ), 1 =>  array (  0 => 'variable',  1 => '/',  2 => ':name',  3 => 'name', ), 2 =>  array (  0 => 'text',  1 => '/',  2 => 'blogs',  3 => NULL, ),));
 }
}

* This source code was highlighted with Source Code Highlighter.


Пример дампа правил для Apache в Symfony 2:


RewriteCond %{PATH_INFO} ^/blogs/add\-topic/$
RewriteRule .* index.php [QSA,L,E=_ROUTING__route:addTopic,E=_ROUTING_module:blogsEdit,E=_ROUTING_action:addTopic]

RewriteCond %{PATH_INFO} ^/blogs/edit\-topic/([^/\.]+?)/$
RewriteRule .* index.php [QSA,L,E=_ROUTING__route:editTopic,E=_ROUTING_id:%1,E=_ROUTING_module:blogsEdit,E=_ROUTING_action:editTopic]

RewriteCond %{PATH_INFO} ^/blogs/([^/\.]+?)/([^/\.]+?)/$
RewriteRule .* index.php [QSA,L,E=_ROUTING__route:topicInThemeBlog,E=_ROUTING_name:%1,E=_ROUTING_id:%2,E=_ROUTING_module:blogs,E=_ROUTING_action:topic]

* This source code was highlighted with Source Code Highlighter.


Вывод:
Во второй ветке Symfony система роутинга переписана полностью. Раньше я считал ее громоздкой. Теперь же беру свои слова обратно. Очень приятно, что разработчики оптимизировали генерируемые файлы и сделали возможность дампить правила в apache. То что систему роутинга можно использовать одтельно от фреймворка безусловно плюс! Как по мне такие компоненты улучшают жизнь разработчика!
+26
15 марта 2010, 22:34
9

комментарии (19)

–15
shemsu #
Я всегда считал, что язык программирования — это всего лишь инструмент, каждый вид которого подходит для конкретных целей. Но почему я захожу в пост про пятитысячный фреймворк на PHP и в очередной раз вижу какую-то лапшу? Прям классика, ну нельзя же так.
0
develop7 #
Не обращайте внимания.
0
Novikov #
Покажете пример не-лапши?
+2
puzzo #
эээээ, всё было круто, но я так и не понял о чём статья((( Хотя элементы кода просто захватывают своей поражабельностью
+1
witdex #
С трудом понятна суть статьи обо всем и ни о чем.
Кому интересно Фабьен сделал слайды недавно там все наглядно видно как роуйтинг работает slideshare.net/fabpot/symfony-components-3393116
0
puzzo #
Спасибо огромное, теперь разобрался хоть о чём статья…
0
z0rg #
не особо понял про версии 1.2 и 1.4 но есть проект на 1.0 и там yml файл схожей структуры с 2 версией симфонии, то есть что, опять пришли к тому с чего начинали?
0
Davert #
Где-то так :) Симфони 1.1-1.4 явно с роутингом перемудрили, раньше каждый роут был элементом массива, а сейчас класс со своими обработчиками.
–1
4pcbr #
Раньше было ровно так же: каждый роут — отдельный класс.
–1
4pcbr #
* простите, объект
–1
Davert #
Вроде как нет. Было в 1.0

$routes = sfRouting::getInstance();
$routes->setRoutes(
array (
'search' =>
array (
0 => '/search',
1 => '#^/search$#',
2 =>
array (
),
3 =>
array (
),
4 =>
array (
'module' => 'search',
'action' => 'category',
),
5 =>
array (
),
6 => '',
),

Стало в 1.2

$this->routes['sf_media_browser_rename'] = unserialize('C:7:«sfRoute»:1287:{a:10:{i:0;a:4:{i:0;a:4:{i:0;s:9:«separator»;i:1;s:0:"";i:2;s:1:"/";i:3;N;}i:1;a:4:{i:0;s:4:«text»;i:1;s:1:"/";i:2;s:16:«sf_media_browser»;i:3;N;}i:2;a:4:{i:0;s:9:«separator»;i:1;s:0:"";i:2;s:1:"/";i:3;N;}i:3;a:4:{i:0;s:4:«text»;i:1;s:1:"/";i:2;s:6:«rename»;i:3;N;}}i:1;a:9:{s:18:«load_configuration»;b:1;s:6:«suffix»;s:0:"";s:14:«default_module»;s:7:«default»;s:14:«default_action»;s:5:«index»;s:5:«debug»;s:0:"";s:7:«logging»;s:0:"";s:21:«generate_shortest_url»;b:1;s:32:«extra_parameters_as_query_string»;b:1;s:18:«segment_separators»;a:1:{i:0;s:1:"/";}}i:2;a:15:{s:6:«suffix»;s:0:"";s:17:«variable_prefixes»;a:1:{i:0;s:1:":";}s:18:«segment_separators»;R:32;s:14:«variable_regex»;s:8:"[\\w\\d_]+";s:10:«text_regex»;s:3:".+?";s:21:«generate_shortest_url»;b:1;s:32:«extra_parameters_as_query_string»;b:1;s:18:«load_configuration»;b:1;s:14:«default_module»;s:7:«default»;s:14:«default_action»;s:5:«index»;s:5:«debug»;s:0:"";s:7:«logging»;s:0:"";s:21:«variable_prefix_regex»;s:6:"(?:\\:)";s:24:«segment_separators_regex»;s:5:"(?:/)";s:22:«variable_content_regex»;s:5:"[^/]+";}i:3;s:24:"/sf_media_browser/rename";i:4;s:24:"/sf_media_browser/rename";i:5;s:29:"#^/sf_media_browser/rename$#x";i:6;a:0:{}i:7;a:2:{s:6:«module»;s:14:«sfMediaBrowser»;s:6:«action»;s:6:«rename»;}i:8;a:0:{}i:9;s:0:"";}}');
0
4pcbr #
Простите, подумал что вы о 1.1-1.4 Vs 2.0.

Это действительно полезная фича на больших проектах со сложной системой урлов.

Наример.

У нас в проекте в зависимости от выбранной страны генерируется урл для странового домена, если страница вне контекста какой-то страны — урл строится для основного домена. Страновые домены — 2го уровня. Очень удобно в таком случае наследование от класса sfRoute и указание кастомного класса роутинга.
0
Davert #
Не, статья оч и очень интересная. Всмысле, на примере слайдов всё было не настолько понятно, а из любопытства таскать проект с гитхаба и разворачивать не хотелось.

Меня тоже бесил роутинг симфони версий 1.2 и 1.4 из-за излишней громоздкости. В Симфони 2 увидел то чего так давно ждал: использование модулей в роутинге (ресурсы) и нормальный url_match без инциализации классов.

Спасибо, что показали сгенеренные файлы. Очень порадовали полезной информацией.
0
Melz #
А теперь вернемся к производительности :) Уж больно у них красивые цифры получаются.

Paul M. Jones, методы которого использовались для тестов полностью их раскритиковал.

paul-m-jones.com/archives/1222

В частности:
* We should use Siege 2.69, not 2.66, for more accurate benchmarking of baseline responsiveness. If we notice that HTML is slower than PHP, it’s a sign that something is wrong.
+1
gen #
Там же смотрим результаты теста для Siege 2.69:
The rankings and relative ratings appear similar to the Siege 2.66 run; Symfony 2 at .1370 is about 20% faster than Solar at .1126.

В конечном итоге Paul M. Jones'у удалось улучшить результаты только применив патч к своему фреймворку.
Интересно так же почитать ответ Фабьена:

To be fair, you should note that the routing system of Solar is a bit different than the routing system of all other frameworks in the test. During my test, I found it’s not a bi-directional routing system like others (correct me if I’m wrong).


Так что с производительностью, думаю, всё в порядке.
0
Melz #
| Similarly, Symfony 2 caches its application classes into another single file (hello/cache
| /prod/classes.php). From what I can tell, none of the other frameworks are doing anything like that;
| they are reading class files individually as needed.

Не смотря на всю гениальность этой идеи это таки малость кэширование. И что будет при увеличении количества классов сложно сказать (vs. классической autoload).

Кстати в комментариях есть еще один интересный линк на тестирование Zend :)

blog.astrumfutura.com/archives/421-PHP-Framework-Benchmarks-Entertaining-But-Ultimately-Useless.html

Практическая ценность близится к нулю, зато некоторые вещи умиляют.

Some recent attention in the PHP framework community has been focused on the recent publication of Symfony 2 Preview benchmarks showing that Symfony 2 outperforms Zend Framework by a factor of 3.5. It also outperforms every other benchmarked framework. This reminded me of the earlier Yii Framework benchmarks which allegedly proved that Yii outperforms Zend Framework by a factor of 7.
At this point, the spirit of Glenn Beck would have me demonstrate that 3.5 multiplied by 2 (the number of eyes in Barrack Obama's skull) equals 7, thus proving the existence of a Liberal conspiracy led by a closet Muslim. That's probably bullshit though.
0
gen #
Улучшать результаты можно долго, заменяя тяжелые части фреймворка голым php. Фабьен же сравнивал решения из коробки, без дополнительных пачтей и оптимизаций, о чем он честно и написал.

Всё это лишний раз доказывает, что такие тесты мало что решают, поэтому делать какие-то выводы о производительности фреймворка на их основе не совсем некорректно. Имхо есть куча куда более важных критериев выбора, нежели скорость отдачи «Hello World».
0
gen #
* не совсем корректно
0
HongKilDong #
Автор, напиши про sfRequestHandler. Желательно свою интерпретацию — хочется понять с чем его…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.