У меня была задача для сайта, который я делал на фрилансе.
Задача следующая – есть модуль Page, который для этого же проекта и был написан. Он достаточно прост, в нем происходит сохранение страниц в бд, поиск, роутинг к страницам вида “/page/pagename/” и базовые шаблоны.
Но по функционалу и дизайну сайта необходимо было выводить некоторые страницы с предпросмотром на главную (или где угодно).
И поможет в решении данной задачи конечно же Zend\View\Helper.
Я хотел вызывать в view шаблонах некий хелпер следующим образом:
1 2 3 4 5 6 7 8 9 |
<article class="col-lg-4 col-md-4 col-sm-4 welcomeBox"> <h2><?php echo $this->pageHelper("welcome")->title(); ?></h2> <hr> <p><?php echo $this->pageHelper("welcome")->shortArticle(500); ?></p> <a href="<?php echo $this->pageHelper("welcome")->link(); ?>" class="btn-default btn1">Дальше</a> </article> |
Т.е. где-то в произвольном шаблоне я подгружаю по alias нужную мне страницу и вывожу с нее данные. Кстати, эта же страница открывается по адресу “/page/welcome”, но уже с полным текстом. В примере выше я показал уже конечный вариант, но именно так я его и задумывал.
Для создания своего View Helper класс должен наследоваться от Zend\View\Helper\AbstractHelper.
Для того, чтобы вызвать свой класс как хелпер, он обязательно должен реализовывать магический метод __invoke().
Далее необходимо зарегестрировать наш класс как хелпер в Service. Это можно сделать двумя способами.
Первый не очень хороший, но рабочий:
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 |
namespace Page; use Zend\ModuleManager\Feature\AutoloaderProviderInterface, Zend\ModuleManager\Feature\ConfigProviderInterface, Zend\ModuleManager\Feature\ViewHelperProviderInterface; class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ViewHelperProviderInterface { public function getAutoloaderConfig(){/*common code*/} public function getConfig(){ /*common code */ } public function getViewHelperConfig() { return array( 'factories' => array( 'pageHelper' => function($sm) { $helper = new Page\View\Helper\PageHelper(); //$sm передать в конструкторе или сеттере //если он нужен внутри хелпера return $helper; } ) ); } } |
Это похоже на обычный способ заведения сервиса в service manager. Так же можно вынести создающий код в Factory.
Второй способ более компактный и имеет свою особенность. Им я и воспользовался.
В файле module.config.php:
1 2 3 4 5 6 7 |
'view_helpers' => array( 'invokables' => array( 'pageHelper' => 'Page\View\Helper\PageHelper', ) ) |
Так будет работать, но где же нужный для решения задачи ServiceManager? А он будет записан в объект автоматически при создании, при условии, что класс реализует интерфейс ServiceLocatorAwareInterface.
Далее надо добавить в класс хелпера такой код.
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
/** * Description of PageHelper * * @author seyfer */ class PageHelper extends AbstractHelper implements ServiceLocatorAwareInterface { /** * * @var ServiceLocatorInterface */ private $serviceLocator; /** * * @var EntityManager */ private $em; /** * * @var Page */ private $currentPage; /** * cache for loaded pages * @var array */ private static $pagesCache = []; /** * Set the service locator. * * @param ServiceLocatorInterface $serviceLocator * @return PageHelper */ public function setServiceLocator(ServiceLocatorInterface $serviceLocator) { $this->serviceLocator = $serviceLocator; return $this; } /** * Get the service locator. * * @return \Zend\ServiceManager\ServiceLocatorInterface */ public function getServiceLocator() { return $this->serviceLocator; } /** * * @return EntityManager */ protected function getEM() { $serviceLocator = $this->getServiceLocator()->getServiceLocator(); $em = $serviceLocator->get("Doctrine\ORM\EntityManager"); if (!$em) { throw new \Exception(__METHOD__ . " can't acces to Doctrine\ORM\EntityManager"); } $this->em = $em; return $em; } |
Я добавил стандартные методы для интерфейса ServiceLocatorAwareInterface, свойство для сохранения менеджера и еще добавил метод, который достает Doctrine\ORM\EntityManager из сервисов. Он будет нужен для загрузки страницы.
Можно заметить некоторую особенность в строке
1 2 3 |
$serviceLocator = $this->getServiceLocator()->getServiceLocator(); |
Дело в том, что сервис был зарегистрирован как хелпер, следовательно при создании в него будет помещен не ServiceManager сразу, а Zend\View\HelperPluginManager, который дает доступ к другим хелперам. И у него то уже и надо вызвать метод getServiceLocator() для доступа к нужному ServiceManager.
Далее дело за __invoke() и другими методами хелпера. В моем случае __invoke() выглядит так:
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 |
/** * @param string $alias * @return \Page\View\Helper\PageHelper * @throws \Exception */ public function __invoke($alias) { if (!$alias) { throw new \Exception(__METHOD__ . " need page alias as param"); } $page = self::$pagesCache[$alias]; if (!$page) { $em = $this->getEM(); $pageRepo = $em->getRepository("\Page\Entity\Page"); $page = $pageRepo->findOneBy(['alias' => $alias]); self::$pagesCache[$alias] = $page; } if (!$page) { throw new \Exception(__METHOD__ . " there is no page with alias" . $alias); } $this->currentPage = $page; return $this; } |
В коде видно, что я реализовал некий кеш в массиве, чтобы не делать лишние запросы в базу данных. Для этого создана статическая переменная $pagesCache, в которую сохраняется выбранный объект по alias.
Так же я сохраняю текущую запрошенную страницу в переменную, и возвращаю $this, чтобы я мог делать следующее:
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 |
public function title() { return $this->currentPage->getTitle(); } public function article() { return $this->currentPage->getArticle(); } /** * * @param type $size * @return string */ public function shortArticle($size) { return $this->currentPage->getShortArticle($size); } public function link() { return "/page/" . $this->currentPage->getAlias(); } |
Пример клиентского кода, который пользуется этими методами уже был приведен выше.
Объект Page, который я тут запрашиваю – это стандартный Doctrine Entity. Конфиги, как и весь остальной код, связанный с Page организован в модуль и возможно, он будет опубликован, когда я его закончу.