Содержание


View


View, MVC, View Models, layer, RSS, Variables containers, Resolvers, Renderer,Strategy



Обзор

ZendViewреализует слой «View» в MVC системе Zend Framework 2. Этот слой представляет собой многоуровневую систему для расширений, замен и много других полезностей.

Компоненты «View» состоят из:

1) Переменные контейнера(Variables containers), которые содержат переменные и функции  для передачи в шаблон вида. Также этот контейнер обладает такими возможностями, как экранирование переменных и многими другими.

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

3) Рендеры(Renderers), которые взаимодействуют с моделями вида и предоставляют различные возможности для их вывода. По умолчанию Zend Framework 2 поддерживает три вида рендеринга: “PHP” – использующий стандартные PHP шаблоны для создания разметки страницы, JSON и Feed – позволяющий создавать RSS ленты и Atom feeds.

 4) Resolvers, которые связывают имена шаблона вида с источниками для рендеринга. Например: в Resolvers приходит имя “blog/entry” и он определяет какой шаблон нужно подключить.

5) View, состоящий из стратегий(strategies), отображающих текущий запрос и редеринг, и стратегии инъекцирования Response с результатами рендеринга.

6)  Renderer and Response Strategies. Стратегия отображения(RendererStrategies) слушает события “renderer”(рендеринга) View и определяет, какой именно рендеринг нужно применить в данном случае, основываясь на запросе(Request)  или других критериях. Response strategies используются для связи объекта ответа(Response object) с результатами рендеринга -  может выполнять различные действия, например, такие как установка типа заголовка(Content-Type headers) и другие.

Использование

В этом разделе мы покажем стандартные примеры использования view layer в связке с MVC Zend Framework 2. Предполагается, что Вы будете использовать инъекции зависимостей и стандартные настройки MVC.

Настройка

Все стандартные настройки фреймворка уже сделаны и работают «из коробки». Однако Вам все равно нужно выбрать стратегию resolver и настроить её, а так же указать имена для некоторых шаблонов и страниц ошибок. Код ниже поможет Вам это сделать. Добавьте его в конфиг модуля(ей) или конфиг приложения в config/autoload/.

return array(
    'di' => array(
        'instance' => array(
        // The above lines will likely already be present; it's the following
        // definitions that you will want to ensure are present within the DI
        // instance configuration.
 
            // Setup the View layer
            // This sets up an "AggregateResolver", which allows you to have
            // multiple template resolution strategies. We recommend using the
            // TemplateMapResolver as the primary solution, with the
            // TemplatePathStack as a backup.
            'ZendViewResolverAggregateResolver' => array(
                'injections' => array(
                    'Zend\View\Resolver\TemplateMapResolver',
                    'Zend\View\Resolver\TemplatePathStack',
                ),
            ),
 
            // The TemplateMapResolver allows you to directly map template names
            // to specific templates. The following map would provide locations
            // for a "home" template, as well as for the "site/layout",
            // "site/error", and "site/404" templates, resolving them to view
            // scripts in this module.
            'Zend\View\Resolver\TemplateMapResolver' => array(
                'parameters' => array(
                    'map'  => array(
                        'home'        => __DIR__ . '/../view/home.phtml',
                        'site/layout' => __DIR__ . '/../view/site/layout.phtml',
                        'site/error'  => __DIR__ . '/../view/site/error.phtml',
                        'site/404'    => __DIR__ . '/../view/site/404.phtml',
                    ),
                ),
            ),
 
            // The TemplatePathStack takes an array of directories. Directories
            // are then searched in LIFO order (it's a stack) for the requested
            // view script. This is a nice solution for rapid application
            // development, but potentially introduces performance expense in
            // production due to the number of stat calls necessary.
            //
            // The following maps adds an entry pointing to the view directory
            // of the current module. Make sure your keys differ between modules
            // to ensure that they are not overwritten!
            'Zend\View\Resolver\TemplatePathStack' => array(
                'parameters' => array(
                    'paths'  => array(
                        'application' => __DIR__ . '/../view',
                    ),
                ),
            ),
 
            // We'll now define the PhpRenderer, and inject it with the
            // AggregateResolver we defined earlier. By default, the MVC layer
            // registers a rendering strategy that uses the PhpRenderer.
            'Zend\View\Renderer\PhpRenderer' => array(
                'parameters' => array(
                    'resolver' => 'Zend\View\Resolver\AggregateResolver',
                ),
            ),
 
            // By default, the MVC's default rendering strategy uses the
            // template name "layout" for the site layout. Let's tell it to use
            // "site/layout" (which we mapped via the TemplateMapResolver,
            // above).
            'Zend\Mvc\View\DefaultRenderingStrategy' => array(
                'parameters' => array(
                    'layoutTemplate' => 'site/layout',
                ),
            ),
 
            // By default, the MVC registers an "exception strategy", which is
            // triggered when a requested action raises an exception; it creates
            // a custom view model that wraps the exception, and selects a
            // template. This template is "error" by default; let's change it to
            // "site/error" (which we mapped via the TemplateMapResolver,
            // above).
            //
            // Additionally, we'll tell it that we want to display an exception
            // stack trace; you'll likely want to disable this by default.
            'Zend\Mvc\View\ExceptionStrategy' => array(
                'parameters' => array(
                    'displayExceptions' => true,
                    'exceptionTemplate' => 'site/error',
                ),
            ),
 
            // Another strategy the MVC registers by default is a "route not
            // found" strategy. Basically, this gets triggered if (a) no route
            // matches the current request, (b) the controller specified in the
            // route match cannot be found in the locator, (c) the controller
            // specified in the route match does not implement the DispatchableInterface
            // interface, or (d) if a response from a controller sets the
            // response status to 404.
            //
            // The default template used in such situations is "error", just
            // like the exception strategy. Let's tell it to use the "site/404"
            // template, (which we mapped via the TemplateMapResolver, above).
            //
            // You can opt in to inject the reason for a 404 situation; see the
            // various Application::ERROR_* constants for a list of values.
            // Additionally, a number of 404 situations derive from exceptions
            // raised during routing or dispatching. You can opt-in to display
            // these.
            'Zend\Mvc\View\RouteNotFoundStrategy' => array(
                'parameters' => array(
                    'displayExceptions'     => true,
                    'displayNotFoundReason' => true,
                    'notFoundTemplate'      => 'site/404',
                ),
            ),
        ),
    ),
);

Контроллеры и модели вида

Zend\View\View предоставляет объект ViewModels , передавая его в нужный шаблон вида для рендеринга. Где и как его создать и использовать?

Самый простой способ -  создание ViewModels в контроллере и передать его в шаблон вида:

namespace FooController;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
 
class BarController extends AbstractActionController
{
    public function doSomethingAction()
    {
        $view = new View\Model(array(
            'message' => 'Hello world',
        ));
        $view->setTemplate('bar/do-something');
        return $view;
    }
}
Этот код определяет переменную “message” в шаблоне вида с содержимым 'Hello world' и передает ее в шаблон вида с именем “bar/do-something”.

В большинстве случаев имена шаблонов видов создаются на основе имени контроллеров и действий в нем. Поэтому логично было бы упростить передачу переменных в шаблон вида.

MVC создает «слушателей»  для контроллеров для упрощения выше озвученной задачи.  Сначала смотриться, возвращат ли контроллер массив данных. Если да, то создается модель вида (view model) и в неё инъекцируется ассоциативный массив, как переменная контейнера вида, и подменяет событие MVC. Если же вы ничего не передали в массиве или передали NULL произойдет тоже самое, просто е в шаблоне вида не будет переменной, но событие MVC будет изменено.

Другой слушаетель проверяет, появились ли в событии MVC модель вида. Если да, то проверяет, есть ли нужный шаблон вида. Если нет, то начнется проверка контроллера и связанной с ним маршрутизации(routing). И если обнаруживается действие(action), то он пытается найти шаблон вида с соответствующим именем:“controller/action” будут переведены в нижний регистр и слова будут разделены дефисом.

Пример: контроллер BarControllerBazBatControllerс действием “doSomethingCrazy” будет автоматически обработан, и в итоге будет искаться такой шаблон вида: baz-bat/do-something-crazy.

Теперь можем переписать код более просто:

namespace FooController;
 
use Zend\Mvc\Controller\AbstractActionController;
 
class BarController extends AbstractActionController
{
    public function doSomethingCrazyAction()
    {
        return array(
            'message' => 'Hello world',
        );
    }
}
 В большинстве случаев такой реализации будет достаточно , особенно если у Вас нет необходимости указывать различные шаблоны видов, явно создавать модели видов или указывать основной шаблон вручную.

Возможна ситуация, когда необходимо указать несколько моделей вида(view models) и отобразить их в одном шаблоне.

Как пример: у Вас есть основная модель вида, которая включает в себя несколько других для отображения.

namespace ContentController;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
 
class ArticleController extends AbstractActionController
{
    public function viewAction()
    {
        // get the article from the persistence layer, etc...
 
        $view = new View\Model();
 
        $articleView = new View\Model(array('article' => $article));
        $articleView->setTemplate('content/article');
 
        $primarySidebarView = new View\Model();
        $primarySidebarView->setTemplate('content/main-sidebar');
 
        $secondarySidebarView = new View\Model();
        $secondarySidebarView->setTemplate('content/secondary-sidebar');
 
        $sidebarBlockView = new View\Model();
        $sidebarBlockView->setTemplate('content/block');
 
        $secondarySidebarView->addChild($sidebarBlockView, 'block');
 
        $view->addChild($articleView, 'article')
             ->addChild($primarySidebarView, 'sidebar_primary')
             ->addChild($secondarySidebarView, 'sidebar_secondary');
 
        return $view;
    }
}

В коде выше будет создана модель вида и передана в шаблон вида “content/article”. Когда начнется рендеринг основного шаблона, рендеринг будет еще 3 потомков: $articleView, $primarySidebarView, $secondarySidebarView. Для лучшего представления давайте рассмотрим основной шаблон вида, куда все передается:  

<?php // "article/view" template ?>
<div class="sixteen columns content">
    <?php echo $this->article ?>
 
    <?php echo $this->sidebar_primary ?>
 
    <?php echo $this->sidebar_secondary ?>
</div>
 
<?php // "content/article" template ?>
    <!-- This is from the $articleView view model, and the "content/article"
         template -->
    <article class="twelve columns">
        <?php echo $this->escapeHtml('article') ?>
    </article>
 
<?php // "content/main-sidebar template ?>
    <!-- This is from the $primarySidebarView view model, and the
         "content/main-sidebar template -->
    <div class="two columns sidebar">
        sidebar content...
    </div>
 
<?php // "content/secondary-sidebar template ?>
    <!-- This is from the $secondarySidebarView view model, and the
         "content/secondary-sidebar template -->
    <div class="two columns sidebar">
        <?php echo $this->block ?>
    </div>
 
<?php // "content/block template ?>
        <!-- This is from the $sidebarBlockView view model, and the
            "content/block template -->
        <div class="block">
            block content...
        </div>

А так будет уже после создания разметки страницы:

<!-- This is from the $view view model, and the "article/view" template -->
<div class="sixteen columns content">
    <!-- This is from the $articleView view model, and the "content/article"
         template -->
    <article class="twelve columns">
        Lorem ipsum ....
    </article>
 
    <!-- This is from the $primarySidebarView view model, and the
         "content/main-sidebar template -->
    <div class="two columns sidebar">
        sidebar content...
    </div>
 
    <!-- This is from the $secondarySidebarView view model, and the
         "content/secondary-sidebar template -->
    <div class="two columns sidebar">
        <!-- This is from the $sidebarBlockView view model, and the
            "content/block template -->
        <div class="block">
            block content...
        </div>
    </div>
</div>

Как Вы видите, можно легко добиться сложной разметки страниц с множеством вложенных видов, одновременно изолируя рендеринг от request/reponse контроллера.

Работа с Layouts

В большинстве сайтов на всех страницах или группах страниц используется общий внешний вид, навигация и тд… Это и называется “layout” или макет, главный шаблон. В макете подключаются основные таблицы стилей, JavaScript, структура разметки, внутри которой будет выведена динамическая(изменяющаяся) информация . Тоесть шаблоны видов будут  вставлены в макет(главный шаблон) в определенные места, обозначенные разработчиком.

Стратегия рендеринга (rendering strategy) по умолчанию устанавливает как макет “layout”. Однако Вы можете его изменить внеся небольшие изменения в Di или с контроллера.

return array(
    'di' => array(
        'instance' => array(
        // The above lines will likely already be present; it's the following
        // definitions that you will want to ensure are present within the DI
        // instance configuration.
 
            // By default, the MVC's default rendering strategy uses the
            // template name "layout" for the site layout. Let's tell it to use
            // "site/layout" (which we mapped via the TemplateMapResolver,
            // above).
            'Zend\Mvc\View\DefaultRenderingStrategy' => array(
                'parameters' => array(
                    'baseTemplate' => 'site/layout',
                ),
            ),
        ),
    ),
);
Слушатель контроллера ZendMvcViewInjectViewModelListener берет модель вида, которую возвращает контроллер и инъекцирует её как дочернюю в главную(layout) модель вида. По – умолчанию, эта «дочерняя модель вида» будет передана в переменную “content” главной модели вида (layout). Смотрим:
<html>
    <head>
        <title><?php echo $this->headTitle() ?></title>
    </head>
    <body>
        <?php echo $this->content; ?>
    </body>
</html>
Если же есть необходимость назначить различные переменные в layout то используйте метод setCaptureTo:
namespace FooController;
 
use ZendMvcControllerAbstractActionController;
use ZendViewModelViewModel;
 
class BarController extends AbstractActionController
{
    public function doSomethingAction()
    {
        $view = new ViewModel(array(
            'message' => 'Hello world',
        ));
 
        // Capture to the layout view's "article" variable
        $view->setCaptureTo('article');
 
        return $view;
    }
}

Бывают случаи, когда не нужно подключать макет для вывода на экран. Например, вы работаете с  API, в который должны передать только часть HTML кода или данные в JSON, XML, или ответ на XHR запрос. Самый простой выход в таком случае это создать модель вида и вернуть её из контроллера, пометив как  “terminal”. Тогда MVC при рендеринге заменит макет(layout)  вашим шаблоном вида.

namespace FooController;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
 
class BarController extends AbstractActionController
{
    public function doSomethingAction()
    {
        $view = new View\Model(array(
            'message' => 'Hello world',
        ));
 
        // Disable layouts; use this view model in the MVC event instead
        $view->setTerminal(true);
 
        return $view;
    }
}

Мы уже обсуждали вложенные модели видов. Однако иногда возникает потребность в использовании дополнительных моделей видов в макете вместо вложений их друг в друга. Это возможно с использованием контроллера плагинов макета (layout), который возвращает главную модель вида. А дальше вызвать в нем метод addChild() как уже делали раньше:

namespace ContentController;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
 
class ArticleController extends AbstractActionController
{
    public function viewAction()
    {
        // get the article from the persistence layer, etc...
 
        // Get the "layout" view model and inject a sidebar
        $layout = $this->layout();
        $sidebarView = new View\Model();
        $sidebarView->setTemplate('content/sidebar');
        $layout->addChild($sidebarView, 'sidebar');
 
        // Create and return a view model for the retrieved article
        $view = new View\Model(array('article' => $article));
        $view->setTemplate('content/article');
        return $view;
    }
}

Если нужно выбрать другой макет(layout) просто используйте метод setTemplate():

namespace ContentController;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
 
class ArticleController extends AbstractActionController
{
    public function viewAction()
    {
        // get the article from the persistence layer, etc...
 
        // Get the "layout" view model and set an alternate template
        $layout = $this->layout();
        $layout->setTemplate('article/layout');
 
        // Create and return a view model for the retrieved article
        $view = new View\Model(array('article' => $article));
        $view->setTemplate('content/article');
        return $view;
    }
}

Бывают так же случаи, кода нужно получить доступ к макету непосредственно из скрипта вида(шаблона вида) при использовании PhpRender. Так же , как и в контроллере, Вы можете использовать помощники/пдагины вида layout. Если Вы что то передаете(аргументы) то макет измениться, иначе вернется без изменений.

// Change the layout:
$this->layout('alternate/layout'); // OR
$this->layout()->setTemplate('alternate/layout');
 
// Access a layout variable.
// Since access to the base view model is relatively easy, it becomes a
// reasonable place to store things such as API keys, which other view scripts
// may need.
$layout       = $this->layout();
$disqusApiKey = false;
if (isset($layout->disqusApiKey)) {
    $disqusApiKey = $layout->disqusApiKey;
}
 
// Set a layout variable
$this->layout()->footer = $this->render('article/footer');

Если же нужно менять макет для конкретного модуля. Для этого понадобиться слушатель. Он должен прослушивать события роутинга(route) или событие dispatch. Но чаще всего это делается  в bootstrap модуля:

namespace Content;
 
class Module
{
    public function onBootstrap($e)
    {
        // Register a dispatch event
        $app = $e->getParam('application');
        $app->getEventManager()->attach('dispatch', array($this, 'setLayout'), -100);
    }
 
    public function setLayout($e)
    {
        $matches    = $e->getRouteMatch();
        $controller = $matches->getParam('controller');
        if (0 !== strpos($controller, __NAMESPACE__, 0)) {
            // not a controller from this module
            return;
        }
 
        // Set the layout template
        $viewModel = $e->getViewModel();
        $viewModel->setTemplate('content/layout');
    }
}

Создание и регистрация Alternate Rendering и Response Strategies

Если вникнуть глубже, то Zend\View\View практически ничего не делает.  Его задача заключается в создании  ViewEvent, а затем вызове двух событий: “renderer” и “response”. Также возможно добавить на эти события стратегии(strategies)  с помощью методов addRendererStrategy() и addResponseStrategy(). Renderer strategy изучает обект Request и определяет какой рендеринг использовать, а “response strategy” как именно его отобразить.

Рассмотри более детально все три типа редеринга:

1) Zend\View\StrategyPhpRendererStrategy. Перехватывает всё и использует Zend\View\Renderer\PhpRenderer для построения каркаса страницы.

2) Zend\View\Strategy\JsonStrategy. Проверяет заголовки HTTPheader, и если они присутствуют и в них указано “application/json”, то использует Zend\View\Renderer\JsonRenderer и в результате получим данные в виде Json. Такжеустановит Content-Type header в “application/json”.

3) Zend\View\Strategy\FeedStrategy. Как и предыдущий, только в заголовке должно быть указано “application/rss+xml” или  “application/atom+xml”. Использует рендер Zend\View\Renderer\FeedRenderer. Content-Type header установит соответствующим образом.

По умолчанию «из коробки» зарегестрирован только PhpRendererStrategy. Если же нужно использовать другую стратегию рндеринга нужно зарегистрировать их вручную, причем с большим приоритетом, что б убедиться, что они первые начнут обрабатывать поступающую информацию!

Для примера зарегистрируем JsonStrategy:

namespace Application;
 
class Module
{
    public function onBootstrap($e)
    {
        // Register a "render" event, at high priority (so it executes prior
        // to the view attempting to render)
        $app = $e->getParam('application');
        $app->getEventManager()->attach('render', array($this, 'registerJsonStrategy'), 100);
    }
 
    public function registerJsonStrategy($e)
    {
        $app          = $e->getTarget();
        $locator      = $app->getServiceManager();
        $view         = $locator->get('Zend\View\View');
        $jsonStrategy = $locator->get('Zend\View\StrategyJsonStrategy');
 
        // Attach strategy, which is a listener aggregate, at high priority
        $view->getEventManager()->attach($jsonStrategy, 100);
    }
}

А если нужно сделать подобное только в конкретном модуле или контроллере? Одним из решений является определить это в Bootstrap модуля:

namespace Content;
 
class Module
{
    public function onBootstrap($e)
    {
        // Register a render event
        $app = $e->getParam('application');
        $app->getEventManager()->attach('render', array($this, 'registerJsonStrategy'), 100);
    }
 
    public function registerJsonStrategy($e)
    {
        $matches    = $e->getRouteMatch();
        $controller = $matches->getParam('controller');
        if (0 !== strpos($controller, __NAMESPACE__, 0)) {
            // not a controller from this module
            return;
        }
 
        // Potentially, you could be even more selective at this point, and test
        // for specific controller classes, and even specific actions or request
        // methods.
 
        // Set the JSON strategy when controllers from this module are selected
        $app          = $e->getTarget();
        $locator      = $app->getServiceManager();
        $view         = $locator->get('Zend\View\View');
        $jsonStrategy = $locator->get('Zend\View\StrategyJsonStrategy');
 
        // Attach strategy, which is a listener aggregate, at high priority
        $view->getEventManager()->attach($jsonStrategy, 100);
    }
}

В этом примере было все продемонстрировано на основе JSON. Аналогично можно сделать и для  FeedStrategy.

Если же есть необходимость использовать пользовательские(самописые) рендеры, или необходима комбинация из всех или части возможных рендеров, то придется создать свою стратегию их использования. Ниже приведен код стратегии, которая в зависимости от заголовка HTTP header определяет какой рендер исполользовать:

namespace ContentView;
 
use Zend\EventManager\EventCollection;
use Zend\EventManager\ListenerAggregate;
use Zend\Feed\WriterFeed;
use Zend\View\RendererFeedRenderer;
use Zend\View\RendererJsonRenderer;
use Zend\View\RendererPhpRenderer;
 
class AcceptStrategy implements ListenerAggregate
{
    protected $feedRenderer;
    protected $jsonRenderer;
    protected $listeners = array();
    protected $phpRenderer;
 
    public function __construct(
        PhpRenderer $phpRenderer,
        JsonRenderer $jsonRenderer,
        FeedRenderer $feedRenderer
    ) {
        $this->phpRenderer  = $phpRenderer;
        $this->jsonRenderer = $jsonRenderer;
        $this->feedRenderer = $feedRenderer;
    }
 
    public function attach(EventCollection $events, $priority = null)
    {
        if (null === $priority) {
            $this->listeners[] = $events->attach('renderer', array($this, 'selectRenderer'));
            $this->listeners[] = $events->attach('response', array($this, 'injectResponse'));
        } else {
            $this->listeners[] = $events->attach('renderer', array($this, 'selectRenderer'), $priority);
            $this->listeners[] = $events->attach('response', array($this, 'injectResponse'), $priority);
        }
    }
 
    public function detach(EventCollection $events)
    {
        foreach ($this->listeners as $index => $listener) {
            if ($events->detach($listener)) {
                unset($this->listeners[$index]);
            }
        }
    }
 
    public function selectRenderer($e)
    {
        $request = $e->getRequest();
        $headers = $request->getHeaders();
 
        // No Accept header? return PhpRenderer
        if (!$headers->has('accept')) {
            return $this->phpRenderer;
        }
 
        $accept = $headers->get('accept');
        foreach ($accept->getPrioritized() as $mediaType) {
            if (0 === strpos($mediaType, 'application/json')) {
                return $this->jsonRenderer;
            }
            if (0 === strpos($mediaType, 'application/rss+xml')) {
                $this->feedRenderer->setFeedType('rss');
                return $this->feedRenderer;
            }
            if (0 === strpos($mediaType, 'application/atom+xml')) {
                $this->feedRenderer->setFeedType('atom');
                return $this->feedRenderer;
            }
        }
 
        // Nothing matched; return PhpRenderer. Technically, we should probably
        // return an HTTP 415 Unsupported response.
        return $this->phpRenderer;
    }
 
    public function injectResponse($e)
    {
        $renderer = $e->getRenderer();
        $response = $e->getResponse();
        $result   = $e->getResult();
 
        if ($renderer === $this->jsonRenderer) {
            // JSON Renderer; set content-type header
            $headers = $response->getHeaders();
            $headers->addHeaderLine('content-type', 'application/json');
        } elseif ($renderer === $this->feedRenderer) {
            // Feed Renderer; set content-type header, and export the feed if
            // necessary
            $feedType  = $this->feedRenderer->getFeedType();
            $headers   = $response->getHeaders();
            $mediatype = 'application/'
                       . (('rss' == $feedType) ? 'rss' : 'atom')
                       . '+xml';
            $headers->addHeaderLine('content-type', $mediatype);
 
            // If the $result is a feed, export it
            if ($result instanceof Feed) {
                $result = $result->export($feedType);
            }
        } elseif ($renderer !== $this->phpRenderer) {
            // Not a renderer we support, therefor not our strategy. Return
            return;
        }
 
        // Inject the content
        $response->setContent($result);
    }
}

Эта стратегия регистрируется так же, как и в примере с JsonStrategy.  Так же необходимо настроить Di, что бы быть уверенным, что различные рендеры будут использованы когда это будет необходимо.


Автор статьи: DuB