菜菜鳥Zend Framework 2 不徹底學習塗鴉(七)-- 視圖和佈局

注意:這篇文章是 ZF2 官方教程以外的一篇,若是想直接看教程,請看下一章節 javascript

在結束上一章節(數據庫和模式)以後,ZF2 官方教程還剩最後三個章節,分別是: php

  • 樣式和轉換
  • 表單和 action
  • 總結

是否是以爲少了些什麼?是的,少了MVC模式中的「V」,也就是少了有關視圖(View)的介紹,在整個 ZF2 教程中對視圖(View)的介紹不多,只是簡單的要求咱們在 view 目錄下創建四個空白的 .phtml 的文件,而後複製代碼,而後運行......並且對於 ZF2 中與視圖(View)有關的另外一個概念:佈局(Layout)也說的不多,基本上沒有提到。可是我認爲先了解視圖(View)和佈局(Layout)是有必要的,因此有了下文。 html

下面這篇文章來自於 ZF2 參考(Zend Framework 2 Reference原文在此。如下的文字來自於我對原文的意譯不是很是準確和通順,對於鳥語好的同窗最好閱讀原文。 java

Zend\View 快速教學

1、概覽

Zend\View 爲 ZF2 系統提供了「視圖(View)」級別的支持,它是一個多層機制系統(multi-tiered system),容許多種多樣的擴展機制,多種多樣的替代以及更多。 git

視圖(View)級別的組件以下: github

  • 變量容器(Variables Containers這個組件保存變量而且經過回調函數返回到你在視圖中但願被它代替的值。一個變量容器(Variables Containers)經常也提供針對上下文特定的轉義變量以及其它機制。
  • 視圖模型(View Models)這個組件保存變量容器(Variables Containers),若是有的話就指定使用的模板(template)而且提供可選的渲染的參數(下文有更多說明)。爲了表示複雜的結構,視圖模型(View Models)能夠嵌套。
  • 渲染器(Renderers)獲取視圖模型(View Models)並提供一個表示表示它們(視圖模型)的返回值。ZF2 默認使用三種渲染器(Renderers):一個是 PhpRenderer,利用PHP模板產生標記,一個是 JsonRenderer,另外一個是 FeedRenderer,用來產生 RSS 和訂閱。
  • 解析器(Resolvers)利用解析器策略解決將一個模板名稱解析爲一個可能會佔用資源的渲染器,也就是說解析器利用策略將一個模板名稱解析爲一個渲染器,這個渲染器可能會佔用必定的資源。例如,解析器能夠將一個名爲「blog/entry」的模板解析成一個 PHP 視圖(view)代碼。
  • 視圖(View)包含將當前請求映射到一個渲染器的策略,也包含注入響應結果進行渲染的策略
  • 渲染策略(Rendering Strategies)監聽 Zend\View\ViewEvent::EVENT_RENDERER 的視圖事件而且決定哪些基於請求或其它標準的渲染器將被選擇
  • 響應策略(Response Strategies)用於注入渲染結果的響應對象,也可能包括使用 action,例如設定 Content-Type 頭

此外,ZF2 提供在 Zend\Mvc\View 命名空間下的許多 MVC 事件監聽器集成 數據庫

2、用法

手冊的這部分向你展現當使用 ZF2 MVC 時視圖(view)的經典使用模式。假設你正在使用依賴注入(Dependency Injection)和默認的 MVC 視圖(view)策略。 json

3、配置

默認的配置直接拿來就可使用,然而你依然須要選擇解析策略(Resolver Strategies)而且對選擇的策略進行配置。就像可能代表交替模板名稱的東西同樣,象站點佈局,404(沒有找到)頁面和錯誤頁面。爲了實現這個功能,如下代碼片斷能夠添加到你的配置中。咱們建議將代碼添加到一個站點特定的模塊(site-specific module)中,例如放入 Zend 應用程序骨架(ZendSkeletonApplication)中的「Application」模塊中或者放入一個你在 config/autoload/ 目錄中自動調用的配置文件中。 數組

return array(
    'view_manager' => array(
        // 模板映射解析器容許你直接地映射模板名稱到一個特殊的模板。
        // 如下映射將提供首頁模板("application/index/index")位置
        // 以及佈局("layout/layout"),錯誤頁面("error/index")和
        // 404 page ("error/404"), 決定他們對應的視圖(view)代碼
        'template_map' => array(
            'application/index/index' => __DIR__ .  '/../view/application/index/index.phtml',
            'site/layout'             => __DIR__ . '/../view/layout/layout.phtml',
            'error/index'             => __DIR__ . '/../view/error/index.phtml',
            'error/404'               => __DIR__ . '/../view/error/404.phtml',
        ),

        // 模板路徑堆棧(TemplatePathStack)是一個目錄數組。
        // 這些目錄根據請求的視圖(view)代碼以 LIFO 方式(它是一個堆棧)進行搜索
        // 這是一個進行快速開發應用程序很是好的解決方案,可是因爲在產品中有不少必要的靜態調用而致使潛在的聲明影響了性能。
        //
        // 下面添加了一個當前模塊的視圖(view)目錄的入口
        // 確保你的關鍵字在各個模塊中是不一樣的,確保關鍵字不會相互覆蓋 -- 或者簡單的忽略關鍵字!
        'template_path_stack' => array(
            'application' => __DIR__ . '/../view',
        ),

        // 這是定義默認模板文件的後綴,默認是"phtml"。
        'default_template_suffix' => 'php',

        // 設定站點佈局模板名稱
        //
        // 默認狀況下,MVC 默認的渲染策略使用 "layout/layout" 來做爲站點佈局模板名稱。
        // 這裏,咱們使用 "site/layout"。
        // 咱們經過上面提到的模板映射解析器(TemplateMapResolver)來映射。
        'layout' => 'site/layout',

        // 默認狀況下,MVC 註冊一個 "異常策略"
        // 當一個請求的 action 引起一個異常時觸發
        // 它建立一個自定義的視圖模型(view model)來包裹住異常而且選擇一個模板
        // 咱們將設定它到 "error/index" 目錄下
        //
        // 此外,咱們告訴它咱們要顯示一個異常跟蹤
        // 你頗有可能默認想要關閉這個工能
        'display_exceptions' => true,
        'exception_template' => 'error/index',

       // 另外一個 MVC 默認的策略是"路由沒有找到"(route not found)
       // 基本上要觸發這個策略(a)沒有路由可以匹配當前的請求,(b)控制器(controller)指定的路由在服務定位器(service locator)中沒法找到,(c)控制器(controller)指定的路由在 DispatchableInterface 接口中無效,(d)從控制器(controller)返回的響應狀態是 404
       //
       // 在這種狀況下,默認使用的是 "error" 模板,就像異常策略。
       // 這裏,咱們將使用 "error/404" 模板(咱們經過上面提到的模板映射解析器(TemplateMapResolver)來映射。)
       //
       // 你能夠有選擇性的注入 404 情況的緣由;具體見各類各樣的 `Application\:\:ERROR_*`_ 常量列表。
       // 此外,許多 404 情況來自於路由和調度過程當中的異常。
       // 你能夠有選擇性的設定打開或關閉
       'display_not_found_reason' => true,
       'not_found_template'       => 'error/404',
    ),
);


4、控制器(Controller)和視圖模型(View Models)


Zend\View\View 消耗 ViewModel,傳送它們給選擇的渲染器。但是在哪裏建立它們呢? mvc

最直接的方式是在你的控制器(Controllers)中建立並返回它們。

namespace Foo\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class BazBatController extends AbstractActionController
{
    public function doSomethingCrazyAction()
    {
        $view = new ViewModel(array(
            'message' => 'Hello world',
        ));
        $view->setTemplate('foo/baz-bat/do-something-crazy');
        return $view;
    }
}

以上代碼在視圖模型(View Model)中設定了一個「message」變量,而且設定了「foo/baz-bat/do-something-crazy」的模板名字。視圖模型(View Model)而後返回了它們。

在許多項目中,你極可能有一個基於模塊命名空間(Module Namespace),控制器(Controller)和 action 的模板名稱。考慮一下,若是你簡單的傳送一些變量,是否能夠變得簡單一點?絕對。

MVC 爲控制器(Controller)註冊幾個監聽器(listeners)來自動化實現這個。首先注意查看你是否從你的控制器(Controller)中返回了一個關聯數組,若是返回了,監聽器(listener)將建立一個視圖模型(View Model)而且使這個關聯數組成爲變量容器(Variables Container);這個視圖模型(View Model)而後替換 MvcEvent 的結果。一樣也會注意查看你是否沒有任何返回值或者返回 null;若是返回了,監聽器(listener)將建立一個沒有任何關聯數組的視圖模型(View Model),這個視圖模型(View Model)也會替換 MvcEvent 的結果。

第二個監聽器(Listener)檢查 MvcEvent 結果是不是一個視圖模型(View Model),若是是一個視圖模型(View Model)並且這個模型中含有關聯的模板信息,若是沒有,監聽器(Listener)將檢查在路由過程當中匹配的控制器(Controller)來決定模塊的命名空間(Module namespace)和控制器類名稱。若是有效,它爲了建立一個模塊名字的「action」參數。這將是「module/controller/action」,標準化的小寫字母和破折號分隔的字符。

舉個例子,控制器 Foo\Controller\BazBatController 和 action 「doSomethingCrazyAction」將被映射到 foo/baz-bat/do-something-crazy,正如你所看到的,「Controller」 和 「Action」被省略了。

在實踐中,咱們先前的例子能夠重寫爲如下代碼

namespace Foo\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class BazBatController extends AbstractActionController
{
    public function doSomethingCrazyAction()
    {
        return array(
            'message' => 'Hello world',
        );
    }
}

以上的方法(Method)可能能夠在咱們多數項目中工做。當你須要指定一個不一樣的模板,明確的建立和返回一個視圖模型(View Model)而且手動指定模板,就像第一個例子同樣。

5、嵌套視圖模型(View Model)

另外一種使用狀況是當你須要嵌套視圖模型(view model)時你須要明確的設定視圖模型(view model)。換句話說,你能夠在你返回的主視圖中包含須要渲染的模板。

例如,你可能想要一個來自於主要片斷中的action的視圖(View),包含全部的「章節」和幾個側邊欄;一樣其中一個側邊欄能夠包含來自於多個視圖(view)的內容。

namespace Content\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class ArticleController extends AbstractActionController
{
    public function viewAction()
    {
        // 從數據持久層(persistence layer)得到文章

        $view = new ViewModel();

        // 這不是必須的,由於它與 "module/controller/action" 匹配
        $view->setTemplate('content/article/view');

        $articleView = new ViewModel(array('article' => $article));
        $articleView->setTemplate('content/article');

        $primarySidebarView = new ViewModel();
        $primarySidebarView->setTemplate('content/main-sidebar');

        $secondarySidebarView = new ViewModel();
        $secondarySidebarView->setTemplate('content/secondary-sidebar');

        $sidebarBlockView = new ViewModel();
        $sidebarBlockView->setTemplate('content/block');

        $secondarySidebarView->addChild($sidebarBlockView, 'block');

        $view->addChild($articleView, 'article')
             ->addChild($primarySidebarView, 'sidebar_primary')
             ->addChild($secondarySidebarView, 'sidebar_secondary');

        return $view;
    }
}


以上代碼將建立並返回一個指定的模板「content/article/view」的視圖模型(view model)。當這個視圖被渲染時,將同時渲染三個子視圖(child views),它們是:$articleView,$primarySidebarView 和 $secondarySidebarView;它們將分別被捕獲到 $view 的「article」, 「sidebar_primary」和「sidebar_secondary」 變量中,這樣,當它們渲染時,你能夠包含它們的內容。此外,$secondarySidebarView 將包含一個額外的視圖模型(view model)$sidebarBlockView,它將被「block」視圖變量捕獲。

爲了更好的想象,讓咱們看看最終的內容可能會是什麼樣子,每一個嵌套都有詳細的註釋。

這裏是一個基於12列網格進行渲染的模板

<?php // "content/article/view" template ?>
<!-- This is from the $view View Model, and the "content/article/view" template -->
<div class="row 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="span8">
        <?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="span2 sidebar">
        sidebar content...
    </div>
<?php // "content/secondary-sidebar template ?>
    <!-- This is from the $secondarySidebarView View Model, and the
         "content/secondary-sidebar" template -->
    <div class="span2 sidebar pull-right">
        <?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 "content/article/view" template -->
<div class="row content">
    <!-- This is from the $articleView View Model, and the "content/article"
         template -->
    <article class="span8">
        Lorem ipsum ....
    </article>

    <!-- This is from the $primarySidebarView View Model, and the
         "content/main-sidebar" template -->
    <div class="span2 sidebar">
        sidebar content...
    </div>

    <!-- This is from the $secondarySidebarView View Model, and the
         "content/secondary-sidebar" template -->
    <div class="span2 sidebar pull-right">
        <!-- This is from the $sidebarBlockView View Model, and the
            "content/block" template -->
        <div class="block">
            block content...
        </div>
    </div>
</div>

正如你所看到的,你可使用嵌套視圖(nested Views)來實現很是複雜的結構,同時在控制器(Controller)的請求/響應生命週期期間對渲染的細節保持獨立性。

6、處理佈局(Layouts)

大多數網站採用一個有凝聚力的外觀和感受,咱們一般稱之爲「佈局(Layout)」。它包括默認的層疊樣式表(stylesheets)和必要的 javascript,若是有的話,就像其它基本結構同樣站點的內容都須要包含它。

在 ZF2 裏,佈局(layouts)經過視圖模型(view model)的嵌套來使用(參見上面的視圖模型嵌套的例子)。Zend\Mvc\View\Http\ViewManager 構成一個視圖模型來擔當視圖模型嵌套的「root」。一樣的,它將包含站點的骨架(skeleton)或者佈局(layout)模板。而後全部其它內容將被這個根視圖模型中的視圖變量所捕獲和渲染。

ViewManager 設置「layout/layout」默認爲佈局的模板。要修改這個配置,你能夠在你的配置文件中的「view_manager」區域裏添加一些配置。

在控制器中的監聽器 Zend\Mvc\View\Http\InjectViewModelListener,將一個從控制器中返回的視圖模型(view model),並將它象一個子視圖模式同樣注入到根(佈局)視圖模型。默認狀況下,視圖模型將捕獲名爲「content」的根視圖模型變量。意思是說你能夠在你的佈局視圖代碼中使用下面的代碼

<html>
    <head>
        <title><?php echo $this->headTitle() ?></title>
    </head>
    <body>
        <?php echo $this->content; ?>
    </body>
</html>

若是你要指定一個不一樣的須要捕獲的視圖變量,明確的在你的控制器建立一個視圖模型而且設定「capture to」值
namespace Foo\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class BazBatController extends AbstractActionController
{
    public function doSomethingCrazyAction()
    {
        $view = new ViewModel(array(
            'message' => 'Hello world',
        ));

        // Capture to the layout view's "article" variable
        $view->setCaptureTo('article');

        return $view;
    }
}

有時你不但願渲染一個佈局。例如,你可能須要應答一個 API 調用來預計 JSON 或 XML 的有效負載,或者你可能須要應答一個 XHR 請求來預計局部 HTML 有效負載。最簡單的方法是在你的控制器中明確的建立並返回一個視圖模型,而且標記爲「terminal」,這會提示 MVC 監聽器正常的注入佈局視圖模型所返回的視圖模型,而不是取代佈局視圖模型
namespace Foo\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class BazBatController extends AbstractActionController
{
    public function doSomethingCrazyAction()
    {
        $view = new ViewModel(array(
            'message' => 'Hello world',
        ));

        // 關閉佈局;`MvcEvent` 將使用這個視圖模型實例
        $view->setTerminal(true);

        return $view;
    }
}

在說明實體模式嵌套時,咱們詳細介紹了一個包含文章和側邊欄的視圖模型嵌套。要是你想提供額外的視圖模型到佈局中,而不是在返回的佈局中嵌套。要實現這個可能要使用 「layout」控制器插件,它返回根視圖模型。而後你能夠象先前的例子同樣調用相同的 addChild() 方法。


namespace Content\Controller;

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 ViewModel();
        $sidebarView->setTemplate('content/sidebar');
        $layout->addChild($sidebarView, 'sidebar');

        // Create and return a view model for the retrieved article
        $view = new ViewModel(array('article' => $article));
        $view->setTemplate('content/article');
        return $view;
    }
}

你也可使用這種技術來選擇一個不一樣的佈局,在佈局視圖模型中簡單的調用 setTemplate() 方法


//In a controller
namespace Content\Controller;

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 ViewModel(array('article' => $article));
        $view->setTemplate('content/article');
        return $view;
    }
}

有時,當你使用 PhpRenderer 時你想從你的實際視圖代碼中訪問佈局。緣由是可能包含想要改變佈局模板或想要訪問或者注入佈局視圖變量。相似的使用「layout」控制器插件,你可使用 「layout」視圖輔助函數。若是爲它提供一個字符串參數,你能夠改變這個模板;若是你不提供參數,將返回一個根佈局視圖模型

一般來講,你可能須要在當前的模型上改變佈局。這個需求(a)檢查在路由中匹配到的控制器是否屬於這個模塊,(b)改變視圖模型的模板。

在一個監聽器裏作這些操做。它應該在低(負)的優先級上監聽「route」事件,或在任何優先級上監聽「dispatch」事件。

namespace Content;

class Module
{
    /**
     * @param  \Zend\Mvc\MvcEvent $e The MvcEvent instance
     * @return void
     */
    public function onBootstrap($e)
    {
        // Register a dispatch event
        $app = $e->getParam('application');
        $app->getEventManager()->attach('dispatch', array($this, 'setLayout'));
    }

    /**
     * @param  \Zend\Mvc\MvcEvent $e The MvcEvent instance
     * @return void
     */
    public function setLayout($e)
    {
        $matches    = $e->getRouteMatch();
        $controller = $matches->getParam('controller');
        if (false === strpos($controller, __NAMESPACE__)) {
            // not a controller from this module
            return;
        }

        // Set the layout template
        $viewModel = $e->getViewModel();
        $viewModel->setTemplate('content/layout');
    }
}

7、建立和註冊替代渲染和響應策略


Zend\View\View 確實很是少,它的工做流程本質上來講就是 ViewEvent,它觸發了兩個事件「renderer」和 「response」。你能夠分別地使用 addRenderingStrategy() 和 addResponseStrategy() 方法,在這些事件上附加「策略」。一個渲染策略考察請求對象(或者任何其它條件)爲了選擇一個渲染器(或者不選擇)。一個響應策略肯定如何填充基於渲染返回的響應。

ZF2 提供了三種渲染和響應策略,你能夠在你的應用程序中使用他們。

  • Zend\View\Strategy\PhpRendererStrategy。這個策略是全方位的,它始終返回 Zend\View\Renderer\PhpRenderer 而且填充渲染返回的響應體。
  • Zend\View\Strategy\JsonStrategy。這個策略檢查並接受 HTTP 頭,若是存在而且肯定客戶端是否已經代表接受一個「application/json」的響應。若是是這樣的話,它將返回 Zend\View\Renderer\JsonRenderer 而且填充 JSON 返回值的響應體,同時爲「application/json」值設定一個內容類型(Content-Type)頭。
  • Zend\View\Strategy\FeedStrategy。這個策略檢查並接受 HTTP 頭,若是存在而且肯定客戶端是否已經代表接受一個「application/rss+xml」或「application/atom+xml」的響應。若是是這樣的話,它將返回 Zend\View\Renderer\FeedRenderer,基於匹配到的結果,設定操做方式(feed type)爲「rss」或者「atom」中的任何一個。響應策略將填充產生的 feed 的響應體,同時基於操做方式(feed type)設定適當的內容類型(Content-Type)頭。

默認狀況下,只有 PhpRendererStrategy 是註冊的,意思是說若是你須要使用其它的策略你須要本身註冊。此外,這意味着你有可能會想在高優先級的狀況下注冊它們,以確保它們在 PhpRendererStrategy 以前獲得匹配。如下是一個例子,讓咱們註冊一個 JsonStrategy

namespace Application;

class Module
{
    /**
     * @param  \Zend\Mvc\MvcEvent $e MvcEvent 實例
     * @return void
     */
    public function onBootstrap($e)
    {
        // 在高優先級(在視圖嘗試渲染以前執行)註冊一個渲染事件
        $app = $e->getApplication();
        $app->getEventManager()->attach('render', array($this, 'registerJsonStrategy'), 100);
    }

    /**
     * @param  \Zend\Mvc\MvcEvent $e MvcEvent 實例
     * @return void
     */
    public function registerJsonStrategy($e)
    {
        $app          = $e->getTarget();
        $locator      = $app->getServiceManager();
        $view         = $locator->get('Zend\View\View');
        $jsonStrategy = $locator->get('ViewJsonStrategy');

        // 附加策略,在高優先級中哪一個是監聽器集合
        $view->getEventManager()->attach($jsonStrategy, 100);
    }
}

以上代碼將隨着 「render」事件 註冊  JsonStrategy,這樣它在  PhpRendererStrategy 以前執行,從而確保當請求時 JSON 負載能夠被建立。


若是你要在指定的模塊(Module)或者指定的控制器(Controller)中註冊該怎麼作呢?一種方法是相似於在佈局(Layout)章節中最後一個例子咱們在指定的模塊(Module)中詳細的改變佈局(Layout)代碼:

namespace Content;

class Module
{
    /**
     * @param  \Zend\Mvc\MvcEvent $e MvcEvent 實例
     * @return void
     */
    public function onBootstrap($e)
    {
        // 註冊一個渲染事件
        $app = $e->getParam('application');
        $app->getEventManager()->attach('render', array($this, 'registerJsonStrategy'), 100);
    }

    /**
     * @param  \Zend\Mvc\MvcEvent $e MvcEvent 實例
     * @return void
     */
    public function registerJsonStrategy($e)
    {
        $matches    = $e->getRouteMatch();
        $controller = $matches->getParam('controller');
        if (false === strpos($controller, __NAMESPACE__)) {
            // 不是一個來自於模塊的控制器
            return;
        }

        // 可能地,你能夠在這裏有更多有選擇性的測試指定的控制器類,指定的action或者請求方法

        // 當來自於這個模塊的控制器被選擇時設定JSON策略
        $app          = $e->getTarget();
        $locator      = $app->getServiceManager();
        $view         = $locator->get('Zend\View\View');
        $jsonStrategy = $locator->get('ViewJsonStrategy');

        // 附加策略,在高優先級中哪一個是監聽器集合
        $view->getEventManager()->attach($jsonStrategy, 100);
    }
}

以上代碼詳細使用了  JsonStrategy,一樣的代碼能夠用於  FeedStrategy。


若是你想使用自定義的渲染器該怎麼作呢?或者若是你的應用容許 JSON,Atom 供稿(feed)和 HTML 綜合在一塊兒該怎麼作呢?在這裏,你要建立你本身的自定義的策略。下面是一個經過 HTTP Accept 頭進行適當的循環以及選擇適當的基於第一個匹配的渲染器的例子

namespace Content\View;

use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Feed\Writer\Feed;
use Zend\View\Renderer\FeedRenderer;
use Zend\View\Renderer\JsonRenderer;
use Zend\View\Renderer\PhpRenderer;

class AcceptStrategy implements ListenerAggregateInterface
{
    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(EventManagerInterface $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(EventManagerInterface $events)
    {
        foreach ($this->listeners as $index => $listener) {
            if ($events->detach($listener)) {
                unset($this->listeners[$index]);
            }
        }
    }

    /**
     * @param  \Zend\Mvc\MvcEvent $e MvcEvent 實例
     * @return \Zend\View\Renderer\RendererInterface
     */
    public function selectRenderer($e)
    {
        $request = $e->getRequest();
        $headers = $request->getHeaders();

        // 沒有Accept header? 返回 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;
            }
        }

        // 沒有匹配到任何東西;返回 PhpRenderer。嚴格來講,我可能要返回一個 HTTP 415 不支持響應
        return $this->phpRenderer;
    }

    /**
     * @param  \Zend\Mvc\MvcEvent $e MvcEvent 實例
     * @return void
     */
    public function injectResponse($e)
    {
        $renderer = $e->getRenderer();
        $response = $e->getResponse();
        $result   = $e->getResult();

        if ($renderer === $this->jsonRenderer) {
            // JSON 渲染器; 設定 content-type header
            $headers = $response->getHeaders();
            $headers->addHeaderLine('content-type', 'application/json');
        } elseif ($renderer === $this->feedRenderer) {
            // Feed 渲染器; 設定 content-type header,若是有必要刻意輸出 feed
            $feedType  = $this->feedRenderer->getFeedType();
            $headers   = $response->getHeaders();
            $mediatype = 'application/'
                       . (('rss' == $feedType) ? 'rss' : 'atom')
                       . '+xml';
            $headers->addHeaderLine('content-type', $mediatype);

            // 若是 $result 是一個 feed,就輸出它
            if ($result instanceof Feed) {
                $result = $result->export($feedType);
            }
        } elseif ($renderer !== $this->phpRenderer) {
            // 不是咱們所支持的渲染器,所以沒有咱們的策略,返回
            return;
        }

        // 注入內容
        $response->setContent($result);
    }
}

這個策略就像咱們演示的那樣在  JsonStrategy 以前註冊。 當你從應用程序定位器實例中檢索策略時 你將還要 定義 DI 配置來確保各類渲染器的注入。
相關文章
相關標籤/搜索