Поговорим о Zend_Navigation

    Выход ZF 1.8 порадовал нас несколькими новыми (а главное очень полезными) компонентами. В этой статье я хочу рассказать о практике использования Zend_Navigation для построения меню сайта, карты сайта, хлебных крошек. Особое внимание уделю использованию Zend_Navigation в связке с Zend_Acl.

    Это перевод статьи с моего блога. Так как она публикуется на нескольких сайтах, то самой полной версией всегда будет оригинал (на украинском).

    Для начала создам каркас проекта используя Zend_Tool.
    $ zf create project ./


    1. Меню
    Для настройки Zend_View добавляю в «application/configs/application.ini» следующий код:
    ; Views
    resources.view.encoding = "UTF-8"
    resources.view.basePath = APPLICATION_PATH "/views"
    resources.view.helperPath.Application_View_Helper = APPLICATION_PATH "/views/helpers"
     
    Дальше в файле «application/Bootstrap.php» создаю новый метод _initNavigation() (просьба читать комментарии в коде):
    class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
    {
     
        /*
         * Инициализируем объект навигатора и передаем его в View
         *
         * @return Zend_Navigation
         */

        public function _initNavigation()
        {
            // Бутстрапим View
            $this->bootstrapView();
            $view = $this->getResource('view');
     
            // Структура простого меню (можно вынести в XML)
            $pages = array(
                array(
                    'controller' => 'index',
                    'label'         => _('Главная страница'),
                ),
                array(
                    'controller' => 'users',
                    'action'        => 'index',
                    // Я обворачиваю текст в _(), чтобы потом вытянуть его парсером gettext'а
                    'label'         => _('Пользователи'),
                    'pages' => array (
                        array (
                            'controller' => 'users',
                            'action'        => 'new',
                            'label'         => _('Добавить пользователя'),
                        ),
                    )
                ),
                array (
                    'controller' => 'users',
                    'action'        => 'registration',
                    'label'         => _('Регистрация'),
                ),
                array (
                    'controller' => 'users',
                    'action'        => 'login',
                    'label'         => _('Авторизация'),
                ),
                array (
                    'controller' => 'users',
                    'action'        => 'logout',
                    'label'         => _('Выход'),
                )
            );
     
            // Создаем новый контейнер на основе нашей структуры
            $container = new Zend_Navigation($pages);
            // Передаем контейнер в View
            $view->menu = $container;
     
            return $container;
        }
     
    }

    Если следовать здоровой логике, то меню должно присутствовать на всех страницах сайта (или большинстве). Для этого идеально подходит Zend_Layout.
    mkdir application/layouts
    mkdir application/layouts/scripts
    touch application/layouts/scripts/default.phtml
    Добавляю в шаблон «application/layouts/scripts/default.phtml» вывод меню и контента страницы:
    <div id="menu">
        <h3>
            <?php echo $this->translate('Меню'); ?>:
        </h3>
     
        <?php echo $this->navigation()->menu($this->menu); ?>
    </div>
     
    <div id="content">
        <?php echo $this->layout()->content; ?>
    </div>

    А в «application/configs/application.ini» выношу настройки для ресурса layout, который инициализирует Zend_Layout:
    ; Layout
    resources.layout.layout     = "default"
    resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"

    И запускаю (оригинал статьи я писал на украинском и скриншоты переделывать чесно говоря лень :)):
    Менюшка :)
    Вуаля :). Видим готовою, расхлопнутую менюшку! Активный пункт помечен как class=«active».

    2. Хлебные крошки
    Ок, менюшка готова. Теперь я хочу, чтобы при пользовании сайтом пользователь всегда видел свое текущее месторасположение. Для этого можно использовать «хлебные крошки».

    Чтобы приложение не ругалось на отсутсвие нужного контроллера или вьюшек я с помощью Zend_Tool создам контроллер Users добавлю к нему необходимые экшены. Все очень просто:
    $ zf create controller users
    $ zf create action new --controller-name users
    $ zf create action registration --controller-name users
    $ zf create action login --controller-name users
    $ zf create action logout --controller-name users

    Ну и добавлю немного нового кода в шаблон layout'a (между меню и контентом):
    <div id="breadcrumbs">
        <h3>
            <?php echo $this->translate('Хлебные крошки'); ?>:
        </h3>
        <?php echo $this->navigation()->breadcrumbs($this->menu)->setLinkLast(true); ?>
    </div>

    Смотрю, что вышло:
    Хлебные крошки
    Прикольно :)?
    Метод setLinkLast(true) означает, что последнюю крошку нужно отображать как ссылку. Также можна указывать разделитель и минимальную глубину — смотрите API

    3. Sitemap
    С сайтмапом все так же просто. Все делается по аналогии. Вот мануал, а вот минимальный код:
        <?php echo $this->navigation()->sitemap($this->menu); ?>


    4. Zend_Navigation && Zend_Acl
    А теперь я расскажу о том, что мне больше всего понравилось в Zend_Navigation — возможность использовать его в связке с Zend_Acl.
    Добавляю в Bootstrap роли и привилегии для доступа к страницам, а также инициализацию Zend_Acl (читайте комментарии к коду!):
    <?php
     
    class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
    {
     
        /**
         * Инициализируем ACL.
         * Создаем роли и ресурсы. Раздаем права доступа
         *
         * @return Zend_Acl
         */

        protected function _initAcl()
        {
            $auth = Zend_Auth::getInstance();
            // Определяем роль пользователя.
            // Если не авторизирован - значит "гость"
            $role = ($auth->hasIdentity() && !empty($auth->getIdentity()->role))
                ? $auth->getIdentity()->role : 'guest';
     
            $acl = new Zend_Acl();
     
            // Создаем роли
            $acl->addRole(new Zend_Acl_Role('guest'))
                ->addRole(new Zend_Acl_Role('member'), 'guest')
                ->addRole(new Zend_Acl_Role('administrator'), 'member');
     
            // Создаем ресурсы
            // Я использую префиксы для наименования ресурсов
            // "mvc:" - для страниц
            $acl->add(new Zend_Acl_Resource('mvc:index'))
                ->add(new Zend_Acl_Resource('mvc:error'))
                ->add(new Zend_Acl_Resource('mvc:users'));
     
            // Пускаем гостей на "морду" и на страницу ошибок
            $acl->allow('guest', array('mvc:error', 'mvc:index'));
     
            // А также на страницы авторизации и регистрации
            $acl->allow('guest', 'mvc:users', array('login', 'registration'));
            // А мемберам уже облом :)
            $acl->deny('member', 'mvc:users', array('login', 'registration'));
            // Ну и т.д.
            $acl->allow('member', 'mvc:users', array('index', 'logout'));
            $acl->allow('administrator', 'mvc:users', array('new'));
     
            // Цепляем ACL к Zend_Navigation
            Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl);
            Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole($role);
     
            return $acl;
        }
     
        /*
         * Инициализируем объект навигатора и передаем его в View
         *
         * @return Zend_Navigation
         */

        public function _initNavigation()
        {
            $this->bootstrapView();
            $view = $this->getResource('view');
     
            $pages = array(
                array(
                    'controller' => 'index',
                    'label'         => _('Главная страница'),
                ),
                array(
                    'controller' => 'users',
                    'action'        => 'index',
                    // Ресурс для проверки прав доступа
                    'resource'      => 'mvc:users',
                    // И привилегия
                    'privilege'     => 'index',
                    'label'         => _('Пользователи'),
                    'pages' => array (
                        array (
                            'controller' => 'users',
                            'action'        => 'new',
                            'resource'      => 'mvc:users',
                            'privilege'     => 'new',
                            'label'         => _('Добавить пользователя'),
                        ),
                    )
                ),
                array (
                    'controller' => 'users',
                    'action'        => 'registration',
                    'resource'      => 'mvc:users',
                    'privilege'     => 'registration',
                    'label'         => _('Регистрация'),
                ),
                array (
                    'controller' => 'users',
                    'action'        => 'login',
                    'resource'      => 'mvc:users',
                    'privilege'     => 'login',
                    'label'         => _('Авторизация'),
                ),
                array (
                    'controller' => 'users',
                    'action'        => 'logout',
                    'resource'      => 'mvc:users',
                    'privilege'     => 'logout',
                    'label'         => _('Выход'),
                )
            );
     
            $container = new Zend_Navigation($pages);
            $view->menu = $container;
     
            return $container;
        }
     
    }
    ?>

    Запускаю:
    Меню для гостей
    Ну и вижу меню для гостей. А хлебных крошек нету потому что, нужно задать минимальную глубину равной нулю.

    Итоги
    Вобще Zend_Navigation очень удобный и гибкий компонент. Ещё я забыл сказать, что стандартные вью хелперы поддерживают Zend_Translate, что очень удобно при создании мультиязычных сайтов.
    Кажется все. Надеюсь эта статья вам пригодится. И заходите к нам на форум zendframework.ru (а также на самом сайте много полезного и интересно).

    UPD. Тут (точнее уже там :)) san подсказывает, что вот так делать
    $view->menu = $container;
    не очень удобно, потому что можно случайно затереть эту переменную в контроллере.
    Если на сайте только одно меню, тогда можно делать проще:
    $view->navigation($container);

    И выводить его в вьюшках таким образом:
        <?php echo $this->navigation()->menu(); ?>
        <?php echo $this->navigation()->breadcrumbs(); ?>


    UPD2.
    Хабрапользователь jarool сделал дельное замечание:
    Для большинства приложений строится один объект навигации и один ACL список. Поэтому можно менюшку положить в Zend_Registry::set('Zend_Navigation', $AppNavigation) — хелперы сами найдут и не надо в лейоут пихать и указывать при вызове хелпера.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 57

      +2
      А что делать если нужно больше менюшек? :)
        +1
        нужно делать больше менюшек :)
          +3
          Дело в том что хэлпер Navigation может хранить только один экземпляр меню
            +1
            вы внимательно читали?
            на протяжении всей статьи мы работали с контейнером который хранился в вью. у себя я таким образом вывожу две менюшки.
            и только в конце статьи я показал пример с прямой передачей контейнера в хелпер из бутстрапа.
              +3
              Не, я не совсем о том. Трудно объяснить :((
              Ну по любому я уже свой велосипед написал.

          0
          а как подключить несколько лейаутов? или отключить например для front-end части, админки, ajax запросов
          +1
          сам компонент имхо удобен исключительно для создания простых меню без определенной обратной связи. При более серьезном подходе с моделями дерева, сохранением/удалением элементов Zend_Navigation остается только для того что бы обеспечить совместимость с помошниками вида для рендера
            0
            ну дерево я через него выводил. тут у него все ок. + есть поиск елементов и их можно удалять/добавлять.
            так в чём проблема :)?
              0
              проблема в том что сами ноды навигации не удовлетворяют критериям модели и сохраняемых данных. Т.е. что бы сохранить изменения и прочее придется долго мучится либо над наследованием классов Zend_Navigation_Page_ либо создавать отдельные модели и после этого линковать импорт/экспорт на Zend_Navigation. Также есть определенная доля проблем если использовать не одно, а 2-3 разных меню на одной странице (типа head/footer и user меню)
              0
              abstract class Zend_Navigation_Container implements _RecursiveIterator_
              0
              интересно, почему zend_navigation упорно обзывает корневую страницу как /, а не через /controller/action? где то у него есть настройка такая?
                0
                все претензии к Zend_Route :)
                а чем вас такое положение вещей не устраивает? если очень нужно, тогда можете свой роутер написать — наследовать его от Zend_Controller_Router_Route и переопределить метод assemble (он возвращает урл).
                // Assembles user submitted parameters forming a URL path defined by this route
                0
                Нуу инициализировать acl в бутстрапе так явно наверное не следует, иначе бутстрап разрастется неприлично. Лучше плагином.
                  +1
                  А вообще за статья зачет, приятно что люди пишут о zf
                    0
                    спасибо :)
                    рад, что понравилась ^_~
                    0
                    согласен, что не следует. это просто для примера. статья ведь про zend_navigation :).

                    я список ролей, прав и ресурсов в конфиг выношу. имхо выносить в плагин их не очень верно.
                    0
                    Пардон за ламерский вопос не по теме…
                    Не смог пройти…

                    _('Пользователи')

                    Как Вам удается геттекстом кириллицу вытягивать? Что юзаете?
                      0
                      Я может тоже что-то путаю, но если мне память не изменяет геттекст это система поддержки мультиязычности для сайтов, есть соотв. библиотека в зенде, и она хранит и работает с данными в бинарном виде. Вроде не должно быть проблем с кирилицей. Геттекст автоматически определяет лейблы и по настройкам словаря вытягивает перевод.
                        0
                        В зенде есть либа Zend_Translate он работает с готовыми mo файлами.

                        Для этого их надо сначала сгенерить. Вот ключевый вопрос чем сканить кириллицу. Мне приходится ломая язык давать ключи на англ, и потом переводить на русский. Что есть извращение, особенно если англ версии ваще нету на сайте :)

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

                        :)))
                          0
                          хм, кажется к геттекстовому редактору идет и сканилка, она кирилицу не ест?
                            0
                            poeditor юзает сканер геттекста, который идет сместе с либой…
                            По моему я этот ставил (под винду) gnuwin32.sourceforge.net/packages/gettext.htm

                            И ключи на кириллице не распознает… :(((
                            Вероятно, афтор владеет каким-то ноу-хау :))))
                              0
                              Вру… отсюда качал исходники для работы с геттекстом
                              sourceforge.net/projects/gettext/
                                0
                                решительно не ест. :)
                                weltkind.habrahabr.ru/blog/64177/
                                  0
                                  Вот что-то мне сильно подсказівает что броблема в кодировке. Мне мирещится что если подобрать и установить усё в правильной кодировке — должно заработать
                                    +1
                                    И шото очень даже возможно, ибо

                                    2.3.1 Locale Names

                                    A locale name usually has the form ‘ll_CC’. Here ‘ll’ is an ISO 639 two-letter language code, and ‘CC’ is an ISO 3166 two-letter country code. For example, for German in Germany, ll is de, and CC is DE. You find a list of the language codes in appendix Language Codes and a list of the country codes in appendix Country Codes.

                                    You might think that the country code specification is redundant. But in fact, some languages have dialects in different countries. For example, ‘de_AT’ is used for Austria, and ‘pt_BR’ for Brazil. The country code serves to distinguish the dialects.
                                      0
                                      Иными словами, в случае использования Utf-8 задача не решаема?..
                              0
                              Zend_Translate
                                0
                                poedit использую. он xgettext помомему для сканирования использует. проблем не было ни под виндой, ни под линуксом. все файлы в кодировке UTF-8
                                  0
                                  Вот в том то и дело что при сканировании кириллицы poeditor-ом (xgettext) у меня ошибки вылетают.
                                  Мне вот и интересно чем он ее сканирует…
                                    0
                                    ну я вам ответил чем я её сканирую (я автор статьи).
                                      0
                                      А можете пример командной строки привести?
                                        +1
                                        из настроек poedit для php
                                        xgettext --language=PHP --force-po -o %o %C %K %F
                                          0
                                          Причину нашел. Спасибо за наводку.
                                          xgettext.exe --force-po --language=PHP -o %o --from-code=utf-8 %K %F
                                0
                                // Структура простого меню (можно вынести в XML)


                                досада что нет(или я её не могу найти просто) прослойки между Zend_Db_Table_Abstract и Zend_Navigation_Container
                                чтобы деревья напрямую из ДБ выбирать
                                  0
                                  можно самому написать. на проекте я для меню категорий так делал. просто формируете массив с деревом и передаете его в контейнер.
                                    0
                                    <?php echo $this->navigation()->menu(); ?>
                                    <?php echo $this->navigation()->breadcrumbs(); ?>


                                    он в ul и li обрамляет элементы, можно ли применить декораторы?
                                      0
                                      нет. поменять можно только наследовав хелпер и переопределив соотвествующие методы.

                                      а зачем вам это нужно?
                                        0
                                        чтобы исключить проблемы с верстальщиком
                                          +1
                                          если у верстальщика проблема со стилями для списка, тогда лучше исключить такого верстальщика…
                                        +1
                                        Можно попробовать воспользоваться {get|set}Partial()
                                        http://framework.zend.com/manual/ru/zend.view.helpers.html#zend.view.helpers.initial.navigation.menu
                                          +2
                                          Пишите <?php $this->navigation(ваш контейнер)->menu()->setPartial(myshablon.phtml); ?>

                                          А в файле myshablon.phtml делайте вашу разметку и через foreach выводите элементы меню, типа:

                                          foreach ($this->container as $page){
                                          echo $this->menu()->htmlfy($page);
                                          }

                                          ну и уже обрамляете элементы как вам нужно.

                                            0
                                            отличное решение. на знал такого. спасибо ;)
                                              0
                                              спасибо, это лучшее решение
                                                0
                                                Блин, google монстр, нашел ваш топик :)
                                                Спасибо, а то я уже хотел отказываться от Zend_Navigation
                                          +2
                                          Для большинства приложений строится один объект навигации и один ACL список. Поэтому можно менюшку положить в Zend_Registry::set('Zend_Navigation', $AppNavigation) — хелперы сами найдут и не надо в лейоут пихать и указывать при вызове хелпера.

                                          Используя скажем в бутстрапе
                                          Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl);
                                          Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole($RoleName);

                                          можно задать и acl сразу.
                                            0
                                            а я и просмотрел такую возможность. спасибо. сейчас добавлю в статью ;)
                                            0
                                            Спасибо за статью. По ней и пытаюсь изучать ZF.

                                            У меня совсем, наверно, ламерский вопрос…
                                            Вот есть, контроллеры и экшены. Например, у меня вся менюшка — это список из того же контроллера и того же экшена, но я хочу еще передавать айдишник. Я еще пока что не очень разобралась в Роутерах… догадываюсь, что нужно их использовать. Но это получается нужно на каждый пункт меню задавать новый Роутер или можно как-то попроще, по-другому? Как вообще делают в таких случаях?

                                            Приведу пример:
                                            — автобусы (c: category, a: index, id: buses)
                                            — грузовики (c: category, a: index, id: cars)
                                            — тягачи (c: category, a: index, id: tractors)
                                            и т.д.
                                              0
                                              просто как переменную передаешь. в дефолтном роутере переменные идут после экшена
                                              controller/action/parametr/value/parametr/value
                                                0
                                                Спасибо. Оказывается, все правильно передавала, но забирала неправильно.
                                                Вы придали мне уверенности хоть в одном действии, тогда уже нашлась и ошибка в другом :)
                                              0
                                              Спасибо. Оказывается, все правильно передавала, но забирала неправильно.
                                              Вы придали мне уверенности хоть в одном действии, тогда уже нашлась и ошибка в другом :)
                                                0
                                                No Pasaran!
                                                0
                                                Кстати…
                                                Если для рендера используется хелпер ViewRenderer, то ему необходимо передать экземпляр вида, который создается бутстраппером.

                                                class Bootstrap extends Zend_Application_Bootstrap_Bootstrap{
                                                protected function _initView(){
                                                // Initialize view
                                                $view = new Zend_View();

                                                // Do something

                                                // Add it to the ViewRenderer
                                                $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
                                                'ViewRenderer'
                                                );
                                                $viewRenderer->setView($view);

                                                // Return it, so that it can be stored by the bootstrap
                                                return $view;
                                                }
                                                }
                                                  0
                                                  не совсем вас понял.
                                                    0
                                                    Ну вот вы вью бутстрапите: $this->bootstrapView();
                                                    А потом в нем контейнер сохраняете: $view->menu = $container;
                                                    так?
                                                    А если в контроллерах используется инфлексия скриптов вида то хелпер, который рендерит эти скрипты сам создает себе вью и в нем не будет вашего $view->menu.
                                                  0
                                                  Хорошая статья. Попробую реализовать. Спасибо за наглядный пример.
                                                    0
                                                    Попробовал я компонент. Такой вот вопрос остался мне неясен. Пусть будет на примере блога. Что бы отображать хлебные крошки правильно:
                                                    категории -> запись ID -> статистика по записи, нужно сделать следущее — либо добавить в контейнер все записи блога и их подстраницы (что соответсвенно представляет здоровое полотно) либо динамически подгружать нужные странцы в контейнер в зависимости от открытой на данный момент страницы.
                                                    Вопрос в том, как правильнее нужно использовать компонент в этом случае.

                                                      Only users with full accounts can post comments. Log in, please.