View Helper’ы Zend Framework в видах Symfony 2
В жизни возникает много всяких инетересных задач. Как правило, можно выделить задачи класса “реализовать такую-то фишку” и “извернуться, чтобы оно работало”. Вот, как раз, о последнем типе задач и пойдёт речь в этом посте.
Иногда в процессе разработки параллельно с разработкой идут несколько “миграций”. Ну например переход с SQL на NoSQL, я об этом как-то уже писал. Не суть. Так вот, представим, что идёт миграция с Zend Framework на Symfony (да простят меня любители ZF). Нам необходимо, чтобы наши горячо любимые (т.к. хорошо отлаженные) ZF View хелперы работали и в Sf-окружении. Как же это сделать.
Задача
Необходимо, чтобы одни и те же (да, без копипасты) Zend Framework View Helper-ы работали как в Zf окружении, так и в Sf окружении.
Решение
Многим разработчикам хорошо знаком механизм Zend_Application_Bootstrap в ZF. Очень, надо сказать, удобная штука. Там можно описывать зависимости одних классов от других и порядок их загрузки. Вернее так, сначала описываем зависимости, потом подгружаем нужный класс, а уж он подгружает своё дерево зависимостей.
Я был очень удивлён, но симфоневский Dependency Injection работает точно так же. Я просто перенес ресурсы в сервисы, определил зависимости, и они загрузились. Для решения нашей задачи нам понадобится Zend_layout. Воспользуемся механизмом factory_service/facctory_method
services.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Класс с фабриками для сервисов (аналог zf bootstrap файла) zf.bootstrap: class: OurNamespace\DefaultBundle\Service\Bootstrap zf.front_controller: class: Zend_Controller_Front factory_service: zf.bootstrap factory_method: createZfFrontController arguments: [] zf.layout: class: Zend_Layout factory_service: zf.bootstrap factory_method: createZfLayout arguments: [@zf.front_controller, @templating.helper.actions] |
Отлично, сразу как на ладони видны зависимости. Про содержимое методов не буду распространяться, если коротко, то в бандле есть специальный сервис, который называется (OurNamespace\DefaultBundle\Service\Bootstrap). Он содержит в себе методы, фабрики для классов-сервисов, которые аналогичны ресурсам в Zend_Application. А остальные сервисы используют его для ссылки на свои конструкторы.
Например, для создания объекта Zend_Controller_Front используется метод createZfFrontController@zf.bootstrap, т.е. OurNamespace\DefaultBundle\Service\Bootstrap:;createZfFrontController(). Для создания объекта лейаута используем OurNamespace\DefaultBundle\Service\Bootstrap::createZfLayout (Zend_Controller_Front, Symfony\Bundle\FrameworkBundle\Templating\Helper\ActionsHelper). В принципе на этом этапе всё понятно. Теперь, сама суть. Создаём сервис “zf.view_helpers_proxy”:
services.yml
1 2 3 4 5 6 |
zf.view_helpers_proxy: class: OurNamespace\DefaultBundle\Helper\ZendViewHelpersProxy arguments: - { layout: @zf.layout } tags: - { name: templating.helper, alias: zf } |
Тут используется уже не функция-фабрика из Bootstrap, а вполне себе обычны конструктор класса ZendViewHelpersProxy.
А вот и сам класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
ZendViewHelpersProxy.php <?php namespace OurNamespace\DefaultBundle\Helper; use Symfony\Component\Templating\Helper\Helper; class ZendViewHelpersProxy extends Helper { protected $options; protected $zendView = null; public function __construct(array $options) { $this->options = $options; if(!array_key_exists('layout', $options)) throw new \Exception('Layout object did not pass to ViewHelpersProxy'); /** @var $layout \Zend_Layout */ $layout = $options['layout']; $this->zendView = $layout->getView(); } protected function getOption($name) { if (array_key_exists($name, $this->options)) { return $this->options[$name]; } throw new Exception('Options does not exist'); } public function getName() { return 'zf'; } /** * Accesses a helper object from within a script. * * If the helper class has a 'view' property, sets it with the current view * object. * * @param string $name The helper name. * @param array $args The parameters for the helper. * @return string The result of the helper output. */ public function __call($name, $args) { // is the helper already loaded? $helper = $this->zendView->getHelper($name); // call the helper method return call_user_func_array( array($helper, $name), $args ); } } |
Обращаем вниманеи на функцию getName() и теги в описании сервиса { name: templating.helper, alias: zf }. Это позволит нам использовать ЛЮБОЙ Zend Framework View Helper в Symfony вот так:
1 |
<?= $view['zf']->ImagePath($image, 129, 158); ?> |
По сути обычный прокси, который позволит обеспечить совместимость и избежать дублирования кода. А со временем можно будет потихоньку пеерписать хелперы. Главное, что таким образом процесс миграции пойдёт плавно без авралов и факапов.
Ссылки
http://blog.logicexception.com/2011/11/creating-symfony2-helper.html
http://symfony.com/doc/2.0/book/templating.html