注意:這篇文章是 ZF2 官方教程以外的一篇,若是想直接看教程,請看下一章節。 javascript
在結束上一章節(數據庫和模式)以後,ZF2 官方教程還剩最後三個章節,分別是: php
是否是以爲少了些什麼?是的,少了MVC模式中的「V」,也就是少了有關視圖(View)的介紹,在整個 ZF2 教程中對視圖(View)的介紹不多,只是簡單的要求咱們在 view 目錄下創建四個空白的 .phtml 的文件,而後複製代碼,而後運行......並且對於 ZF2 中與視圖(View)有關的另外一個概念:佈局(Layout)也說的不多,基本上沒有提到。可是我認爲先了解視圖(View)和佈局(Layout)是有必要的,因此有了下文。 html
下面這篇文章來自於 ZF2 參考(Zend Framework 2 Reference)原文在此。如下的文字來自於我對原文的意譯不是很是準確和通順,對於鳥語好的同窗最好閱讀原文。 java
Zend\View 爲 ZF2 系統提供了「視圖(View)」級別的支持,它是一個多層機制系統(multi-tiered system),容許多種多樣的擴展機制,多種多樣的替代以及更多。 git
視圖(View)級別的組件以下: github
此外,ZF2 提供在 Zend\Mvc\View 命名空間下的許多 MVC 事件監聽器集成 數據庫
手冊的這部分向你展現當使用 ZF2 MVC 時視圖(view)的經典使用模式。假設你正在使用依賴注入(Dependency Injection)和默認的 MVC 視圖(view)策略。 json
默認的配置直接拿來就可使用,然而你依然須要選擇解析策略(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', ), );
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; } }
在許多項目中,你極可能有一個基於模塊命名空間(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)而且手動指定模板,就像第一個例子同樣。
另外一種使用狀況是當你須要嵌套視圖模型(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)的請求/響應生命週期期間對渲染的細節保持獨立性。
大多數網站採用一個有凝聚力的外觀和感受,咱們一般稱之爲「佈局(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>
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; } }
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; } }
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; } }
//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; } }
一般來講,你可能須要在當前的模型上改變佈局。這個需求(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'); } }
Zend\View\View 確實很是少,它的工做流程本質上來講就是 ViewEvent,它觸發了兩個事件「renderer」和 「response」。你能夠分別地使用 addRenderingStrategy() 和 addResponseStrategy() 方法,在這些事件上附加「策略」。一個渲染策略考察請求對象(或者任何其它條件)爲了選擇一個渲染器(或者不選擇)。一個響應策略肯定如何填充基於渲染返回的響應。
ZF2 提供了三種渲染和響應策略,你能夠在你的應用程序中使用他們。
默認狀況下,只有 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); } }
若是你要在指定的模塊(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); } }
若是你想使用自定義的渲染器該怎麼作呢?或者若是你的應用容許 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); } }