# ViewRenderer

## Введение

Помощник `ViewRenderer` предназначен для решения следующих задач:

- Устранение необходимости инстанцирования объектов вида внутри контроллеров; объекты вида будут автоматически регистрироваться вместе с контроллером.

- Автоматическая установка путей к скриптам вида, помощникам и фильтрам, основанная на текущем модуле, и автоматическое присоединение имени текущего модуля в качестве префикса имен классов помощников и фильтров.

- Создание глобально доступного объекта вида для всех запускаемых контроллеров и действий.

- Возможность устанавливать используемые по умолчанию опции рендеринга для всех контроллеров.

- Возможность автоматического рендеринга скрипта вида, не требующего от разработчика каких-либо действий.

- Возможность создавать собственные спецификации базового пути вида и путей к скриптам видов.

Если вы вручную производите `_forward()`, перенаправление или `render`, то авторендеринг не будет произведен, поскольку выполнение любых этих операций говорит помощнику `ViewRenderer`, что вы определили свой собственный вывод.

`ViewRenderer` включен по умолчанию. Вы можете отключить его через параметр фронт-контроллера `noViewRenderer` (`$front->setParam('noViewRenderer', true)`) или посредством удаления помощника из стека брокера помощников (`Zend_Controller_Action_HelperBroker::removeHelper('viewRenderer')`).

Если вы хотите изменить настройки `ViewRenderer` до начала диспетчеризации, то можете сделать это одним из двух способов:

- Инстанцировать и зарегистрировать свой объект `ViewRenderer`, а затем передать его брокеру помощников:

      $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
      $viewRenderer->setView($view)
                   ->setViewSuffix('php');
      Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

- Инициализировать и/или извлечь по запросу объект `ViewRenderer` через брокер помощников:

      $viewRenderer =
          Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
      $viewRenderer->setView($view)
                   ->setViewSuffix('php');

## API

В простейшем варианте использования вы просто инстанцируете `ViewRenderer` и передаете его брокеру помощников. Наиболее легким способом его инстанцирования и регистрации является использование метода `getStaticHelper()` брокера помощников:

    Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');

Во время инстанцирования контроллера действий производится вызов `ViewRenderer` для инстанцирования объекта вида. Каждый раз, когда инстанцируется контроллер, вызывается метод `init()` помощника `ViewRenderer`, что приводит к установке свойства `$view` данного контроллера действий и вызову метода `addScriptPath()` с путем относительно текущего модуля; он будет вызван с префиксом класса, соответствующим имени текущего модуля, что эффективно разделяет пространства имен всех классов помощников и фильтров, определенных для этого модуля.

Каждый раз, когда вызывается `postDispatch()`, он будет вызывать `render()` для текущего действия.

В качестве примера рассмотрим следующий класс:

    // Класс контроллера, модуль foo:
    class Foo_BarController extends Zend_Controller_Action
    {
        // Рендеринг bar/index.phtml по умолчанию;
        // не требуется производить какие-либо операции
        public function indexAction()
        {
        }

        // Рендеринг bar/populate.phtml с переменной 'foo', установленной в 'bar'.
        // Поскольку объект вида определен в preDispatch(), то он всегда доступен.
        public function populateAction()
        {
            $this->view->foo = 'bar';
        }
    }

    ...

    // в одном из ваших скриптов вида:
    $this->foo(); // вызывает Foo_View_Helper_Foo::foo()

`ViewRenderer` также определяет несколько аксессоров для того, чтобы можно было устанавливать и извлекать опции видов:

- `setView($view)` позволяет установить объект вида для `ViewRenderer`. Объект сохраняется в открытом свойстве `$view` класса.

- `setNeverRender($flag = true)` может использоваться для отключения или включения авторендеринга глобально, т.е. для всех контроллеров. Если установлен в `TRUE`, то `postDispatch()` не будет автоматически вызывать `render()` в текущем контроллере. `getNeverRender()` возвращает текущее значение.

- `setNoRender($flag = true)` может использоваться для отключения или включения авторендеринга. Если установлен в `TRUE`, то `postDispatch()` не будет автоматически вызывать `render()` в текущем контроллере. Эта установка сбрасывается каждый раз во время вызова `preDispatch()` (т.е. нужно устанавливать этот флаг для каждого контроллера, для которого вы не хотите производить авторендеринг). `getNoRender()` возвращает текущее значение.

- `setNoController($flag = true)` может использоваться для того, чтобы указать методу `render()`, чтобы он не искал скрипт вида в поддиректории с именем контроллера (что является поведением по умолчанию). `getNoController()` возвращает текущее значение.

- `setNeverController($flag = true)` является аналогом `setNoController()`, но работает на глобальном уровне - т.е. он не будет сбрасываться с каждым обработанным действием. `getNeverController()` возвращает текущее значение.

- `setScriptAction($name)` может использоваться для того, чтобы указать скрипт действия для рендеринга. `$name` должен быть именем скрипта без суффикса (и без поддиректории контроллера, за исключением того случая, когда включен `noController`). Если не задан, то ищется скрипт вида с именем, аналогичным имени действия в объекте запроса. `getScriptAction()` возвращает текущее значение.

- `setResponseSegment($name)` может использоваться для указания того, в какой именованный сегмент объекта ответа следует сохранить результат рендеринга. Если не указан, то выходные данные сохраняются в сегменте, используемом по умолчанию. `getResponseSegment()` возвращает текущее значение.

- `initView($path, $prefix, $options)` может вызываться для указания базового пути вида, префикса классов помощников и фильтров, опций помощника `ViewRenderer`. Вы можете передавать любые из следующих флагов: `neverRender`, `noRender`, `noController`, `scriptAction` и `responseSegment`.

- `setRender($action = null, $name = null, $noController = false)` позволяет установить `scriptAction`, `responseSegment`, или `noController` за один проход. `direct()` является псевдонимом для этого метода, что дает возможность легко вызывать этот метод из вашего контроллера.

      // Рендеринг 'foo' вместо текущего скрипта вида
      $this->_helper->viewRenderer('foo');

      // Рендеринг form.phtml в сегмент ответа 'html' в обход
      // поддиректории:
      $this->_helper->viewRenderer('form', 'html', true);

  `setRender()` и `direct()` в действительности не производят рендеринг скрипта вида, а устанавливают закрытые свойства помощника, которые `postDispatch()` и `render()` будут использовать при рендеринге скрипта вида.

Конструктор позволяет опционально передать объект вида и опции `ViewRenderer`. Он использует те же флаги, что и `initView()`:

    $view    = new Zend_View(array('encoding' => 'UTF-8'));
    $options = array('noController' => true, 'neverRender' => true);
    $viewRenderer =
        new Zend_Controller_Action_Helper_ViewRenderer($view, $options);

`ViewRenderer` имеет несколько дополнительных методов для создания пользовательских спецификаций пути, используемых для определения базового пути вида, добавляемого в объект вида, и пути к определенному скрипту вида, используемого при автоматическом определении скрипта вида для рендеринга. Все эти методы принимают одну или более меток заполнения:

- `:moduleDir` ссылается на текущую базовую директорию модуля (по соглашению это директория, родительская по отношению к директории контроллеров модуля).

- `:module` ссылается на имя текущего модуля.

- `:controller` ссылается на имя текущего контроллера.

- `:action` ссылается на имя текущего действия.

- `:suffix` ссылается на суффикс скрипта вида (который может быть установлен через `setViewSuffix()`).

Методы для управления спецификациями пути:

- `setViewBasePathSpec($spec)` позволяет изменить спецификацию пути, используемую для определения базового пути, добавляемого в объект вида. По умолчанию используется спецификация `:moduleDir/views`. Вы можете в любое время получить текущую спецификацицию, используя метод `getViewBasePathSpec()`.

- `setViewScriptPathSpec($spec)` позволяет изменить спецификацию пути, используемую для определения пути к отдельному скрипту вида (без базового пути скрипта вида). По умолчанию используется спецификация `:controller/:action.:suffix`. Вы можете в любое время получить текущую спецификацию, используя метод `getViewScriptPathSpec()`.

- `setViewScriptPathNoControllerSpec($spec)` позволяет изменить спецификацию пути, используемую для определения пути к отдельному скрипту вида, когда действует `noController` (без базового пути скрипта вида). По умолчанию используется спецификация `:action.:suffix`. Вы можете в любое время получить текущую спецификацию, используя метод `getViewScriptPathNoControllerSpec()`.

Для более детального управления спецификациями путей вы можете использовать Zend_Filter_Inflector. Внутри себя `ViewRenderer` уже использует инфлектор для поиска соответствий. Для взаимодействия с инфлектором - установки своего собственного инфлектора или изменения используемого по умолчанию - могут использоваться следующие методы:

- `getInflector()` возвращает инфлектор. Если в `ViewRenderer` его нет, то метод создает его, используя правила по умолчанию.

  По умолчанию он использует ссылки на статические правила для суффикса и директории модуля, так же, как и статическую цель. Это дает различным свойствам `ViewRenderer` возможность динамически изменять инфлектор.

- `setInflector($inflector, $reference)` позволяет устанавливать свой инфлектор для использования с `ViewRenderer`. Если `$reference` равен true, то суффикс и директория модуля будут установлены как статические ссылки на свойства `ViewRenderer`, так же, как и цель.

### Используемые по умолчанию соглашения по поиску

`ViewRenderer` производит некоторую нормализацию пути для облегчения поиска скрипта вида. Используемые по умолчанию правила:

- `:module`: СловаРазделенныеРегистром (CamelCase) разделяются тире и вся строка приводится к нижнему регистру. Например: "FooBarBaz" преобразуется в "foo-bar-baz".

  Внутри себя инфлектор использует фильтры `Zend_Filter_Word_CamelCaseToDash` и `Zend_Filter_StringToLower`.

- `:controller`: СловаРазделенныеРегистром (CamelCase) разделяются тире, знаки подчеркивания преобразуются в разделители директорий и вся строка приводится к нижнему регистру. Например: "FooBar" преобразуется в "foo-bar"; "FooBar_Admin" преобразуется в "foo-bar/admin".

  Внутри себя инфлектор использует фильтры `Zend_Filter_Word_CamelCaseToDash`, `Zend_Filter_Word_UnderscoreToSeparator` и `Zend_Filter_StringToLower`.

- `:action`: СловаРазделенныеРегистром (CamelCase) разделяются тире, символы, не являющиеся буквенно-цифровыми, переводятся в тире и вся строка приводится к нижнему регистру. Например: "fooBar" преобразуется в "foo-bar"; "foo-barBaz" преобразуется в "foo-bar-baz".

  Внутри себя инфлектор использует фильтры `Zend_Filter_Word_CamelCaseToDash`, `Zend_Filter_PregReplace` и `Zend_Filter_StringToLower`.

Последними рассматриваемыми элементами в API `ViewRenderer`-а являются методы для собственно определения путей к скриптам вида и рендеринга видов. Эти методы включают в себя:

- `renderScript($script, $name)` позволяет производить рендеринг скрипта по указанному пути, в опционально заданный именованный сегмент. Если используется этот метод, то `ViewRenderer` не производит автоматическое определение имени скрипта, вместо этого он напрямую передает аргумент `$script` методу `render()` объекта вида.

  После того, как был произведен рендеринг вида в объект ответа, устанавливается `noRender` для предотвращения случайного повторного рендеринга того же скрипта вида.

  По умолчанию `Zend_Controller_Action::renderScript()` вызывает метод `renderScript()` помощника `ViewRenderer`.

- `getViewScript($action, $vars)` создает путь к скрипту вида, основываясь на переданном действии $action и/или переменных, переданных в $vars. Этот массив может включать в себя ключи спецификаций пути ('moduleDir', 'module', 'controller', 'action' и 'suffix'). Если была передана переменная, то она будет использована, иначе будут использоваться значения из текущего запроса.

  `getViewScript()` будет использовать `viewScriptPathSpec`, либо `viewScriptPathNoControllerSpec`, в зависимости от значения флага `noController`.

  Разделители слов в именах модуля, контроллера или действия будут заменены на тире ('-'). Таким образом, если вы имеете контроллер с именем 'foo.bar' и действие 'baz:bat', то при использовании спецификации по умолчанию результатом будет путь 'foo-bar/baz-bat.phtml' к скрипту вида.

  По умолчанию `Zend_Controller_Action::getViewScript()` вызывает метод `getViewScript()` `ViewRenderer`-а.

- `render($action, $name, $noController)` сначала проверяет, были ли переданы параметры `$name` или `$noController`, и если были переданы, то устанавливает соответствующие флаги (responseSegment и noController соответственно) в ViewRenderer. Затем он передает параметр `$action` (если есть) методу `getViewScript()`. Наконец, он передает полученный путь к скрипту вида методу `renderScript()`.

  Следует помнить о побочных эффектах использования render(): значения, передаваемые для имени сегмента ответа и флага noController, сохраняются в объекте. Кроме этого, по окончании рендеринга будет установлен noRender.

  По умолчанию `Zend_Controller_Action::render()` вызывает метод `render()` помощника `ViewRenderer`.

- `renderBySpec($action, $vars, $name)` позволяет передавать переменные спецификации пути для определения создаваемого пути к скрипту вида. Он передает `$action` и `$vars` методу `getScriptPath()`, затем передает полученный путь и `$name` методу `renderScript()`.

## Примеры базового использования

### Базовое использование

В простейшем случае вы просто инициализируете и регистрируете помощник `ViewRenderer` через брокер помощников в своем файле загрузки и затем устанавливаете переменные в своих методах действий.

    // В вашем файле загрузки:
    Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');

    ...

    // Модуль 'foo', контроллер 'bar':
    class Foo_BarController extends Zend_Controller_Action
    {
        // По умолчанию производится рендеринг bar/index.phtml;
        // дополнительные действия не требуются
        public function indexAction()
        {
        }

        // Рендеринг bar/populate.phtml с переменной 'foo', установленной в 'bar'.
        // Поскольку объект вида был определен в preDispatch(), то он уже
        // доступен для использования.
        public function populateAction()
        {
            $this->view->foo = 'bar';
        }

        // Ничего не рендерится, т.к. производится переход на другое действие;
        // это другое действие может производить рендеринг
        public function bazAction()
        {
            $this->_forward('index');
        }

        // Ничего не рендерится, т.к. производится перенаправление по другому адресу
        public function batAction()
        {
            $this->_redirect('/index');
        }
    }

### Соглашения по именованию: Разделители слов в именах контроллера и действия

Если имена вашего контроллера и действия состоят из нескольких слов, то диспетчер требует, чтобы в URL они были разделены определенными символами-разделителями слов и путей. `ViewRenderer` при создании путей заменяет все найденные в имени контроллера разделители путей действующим разделителем путей ('/') и все разделители слов - чертой ('-'). Таким образом, вызов действия `/foo.bar/baz.bat` должен быть преобразован в вызов метода `FooBarController::bazBatAction()` в `FooBarController.php`, который в свою очередь произведет рендеринг скрипта вида `foo-bar/baz-bat.phtml`. Вызов действия `/bar_baz/baz-bat` должен быть преобразован в вызов `Bar_BazController::bazBatAction()` в `Bar/BazController.php` (обратите внимание на разделение путей), при этом производится рендеринг `bar/baz/baz-bat.phtml`.

Во втором примере обратите внимание на то, что по-прежнему используется модуль по умолчанию, но из-за наличия разделителя путей получается имя контроллера `Bar_BazController` в файле `Bar/BazController.php`. `ViewRenderer` имитирует иерархию директорий контроллеров.

### Отключение авторендеринга

Может потребоваться отключить авторендеринг для некоторых действий или контроллеров - например, если вы хотите производить вывод другого типа (XML, JSON и т.д.), или просто не хотите ничего выводить. Есть два варианта - либо полностью отключить авторендеринг (`setNeverRender()`), либо отключить его для текущего действия (`setNoRender()`).

    // Класс контроллера baz, модуль bar:
    class Bar_BazController extends Zend_Controller_Action
    {
        public function fooAction()
        {
            // Не производить авторендеринг в этом действии
            $this->_helper->viewRenderer->setNoRender();
        }
    }

    // Класс контроллера bat, модуль bar:
    class Bar_BatController extends Zend_Controller_Action
    {
        public function preDispatch()
        {
            // Не производить авторендеринг во всех действиях этого контроллера
            $this->_helper->viewRenderer->setNoRender();
        }
    }

В большинстве случаев не имеет смысла глобально отключать авторендеринг (через `setNeverRender()`), поскольку единственная выгода, которую вы получаете в этом случае от использования `ViewRenderer` - автоматическая установка объекта вида.

### Выбор другого скрипта вида

В некоторых случаях требуется, чтобы производился рендеринг скрипта с именем, отличным от имени действия. Например, если у вас есть контроллер, который имеет методы действий для добавления и редактирования, они оба могут отображать один и тот же вид 'форма', хоть и с разным набором значений. Вы легко можете изменить имя скрипта, используя методы `setScriptAction()` и `setRender()`, или вызывая помощника как метод брокера - этим будет произведен вызов метода `setRender()`.

    // Класс контроллера bar, модуль foo:
    class Foo_BarController extends Zend_Controller_Action
    {
        public function addAction()
        {
            // Рендерить 'bar/form.phtml' вместо 'bar/add.phtml'
            $this->_helper->viewRenderer('form');
        }

        public function editAction()
        {
            // Рендерить 'bar/form.phtml' вместо 'bar/edit.phtml'
            $this->_helper->viewRenderer->setScriptAction('form');
        }

        public function processAction()
        {
            // произведение валидации...
            if (!$valid) {
                // Рендерить 'bar/form.phtml' вместо 'bar/process.phtml'
                $this->_helper->viewRenderer->setRender('form');
                return;
            }

            // иначе продолжение обработки...
        }

    }

### Изменение зарегистрированного объекта вида

А что, если нужно модифицировать объект вида - например, изменить пути к помощникам или кодировку? Вы можете делать это как через модификацию объекта вида, установленного в вашем контроллере, так и через извлечение объекта вида из `ViewRenderer`, оба они являются ссылками на один и тот же объект.

    // Класс контроллера bar, модуль foo:
    class Foo_BarController extends Zend_Controller_Action
    {
        public function preDispatch()
        {
            // Изменение кодировки вида
            $this->view->setEncoding('UTF-8');
        }

        public function bazAction()
        {
            // Получение объекта вида и указание 'htmlspecialchars'
            // в качестве функции для экранирования
            $view = $this->_helper->viewRenderer->view;
            $view->setEscape('htmlspecialchars');
        }
    }

## Примеры продвинутого использования

### Изменение спецификаций пути

В некоторых случаях вы можете решить, что спецификации пути, используемые по умолчанию, не соответствуют требованиям вашего сайта. Например, вы можете захотеть иметь одно дерево шаблонов, к которому можно давать доступ дизайнерам (что, например, довольно типично в случае использованя Smarty). В таком случае вы можете захотеть задать жесткую спецификацию базового пути вида и создать альтернативную спецификацию для собственно путей к скриптам вида.

В рамках данного примера предположим, что базовый путь к скриптам вида - '/opt/vendor/templates', и вы хотите, чтобы обращение к скриптам вида производилось по схеме ':moduleDir/:controller/:action.:suffix'. Также предположим, что если флаг noController установлен, то нужно, чтобы использовался верхний уровень вместо поддиректории (':action.:suffix'). И наконец, вы хотите использовать 'tpl' в качестве суффикса имени скрипта вида.

    /**
     * В вашем файле загрузки:
     */

    // Другая реализация вида
    $view = new ZF_Smarty();

    $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view);
    $viewRenderer->setViewBasePathSpec('/opt/vendor/templates')
                 ->setViewScriptPathSpec(':module/:controller/:action.:suffix')
                 ->setViewScriptPathNoControllerSpec(':action.:suffix')
                 ->setViewSuffix('tpl');
    Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

### Рендеринг нескольких скриптов вида из одного действия

Иногда бывает нужно произвести рендеринг нескольких скриптов вида из одного действия. Решение довольно очевидное - просто сделайте несколько вызовов метода `render()`:

    class SearchController extends Zend_Controller_Action
    {
        public function resultsAction()
        {
            // Предполагается, что $this->model - текущая модель
            $this->view->results =
                $this->model->find($this->_getParam('query', '');

            // render() по умолчанию использует ViewRenderer
            // Рендеринг формы поиска и затем результатов поиска
            $this->render('form');
            $this->render('results');
        }

        public function formAction()
        {
            // Ничего не делается. ViewRenderer автоматически производит
            // рендеринг скрипта вида
        }
    }