現(xiàn)在我們已經(jīng)對 Zend Framework 2 骨架應(yīng)用程序有所了解,讓我們繼續(xù)來創(chuàng)建我們自己的模塊。我們會創(chuàng)建一個名為“博客”的模塊。這個模塊會顯示一個代表每個博客帖子的數(shù)據(jù)庫條目清單。每個帖子都有三個屬性:id
,text
和 title
。我們會創(chuàng)建用于提交新帖子到數(shù)據(jù)庫中的表單,和修改現(xiàn)有帖子的表單。另外在我們整個快速開始指南中都會使用最佳實踐。
首先我們在 /module
目錄下創(chuàng)建一個新文件夾名為 blog
。
為了能讓其被 ModuleManager 認作是模塊,我們只需要在目標module 的名稱空間(Blog
)中創(chuàng)建一個名為 Module
的 PHP 類。創(chuàng)建文件 /module/Blog/Module.php
。
<?php
// 文件名: /module/Blog/Module.php
namespace Blog;
class Module
{
}
現(xiàn)在我們擁有一個可以被 ZF2 的 ModuleManager 偵測到的 module 了。讓我們將這個 module 添加到我們應(yīng)用程序中。雖然這個 module 目前還不能干任何事情,但僅僅擁有 Module.php
類已經(jīng)能讓其被 ZF2 的 ModuleManager 載入。要實現(xiàn)這點,在主應(yīng)用程序配置文件 /config/application.config.php
中內(nèi)的 module 數(shù)組中為 Blog
添加一個條目:
<?php
// 文件名: /config/application.config.php
return array(
'modules' => array(
'Application',
'Blog'
),
// ...
);
如果你刷新你的應(yīng)用程序你應(yīng)該看不見任何改變(也沒有任何錯誤)。
這個時候,我們有必要回頭討論一下 module 到底是做什么的。簡而言之,一個 module 是您的應(yīng)用程序的一個被封裝的功能集合。一個 module 可以為您的應(yīng)用程序添加可見功能,像我們的博客 module;一個 module 也可以為應(yīng)用程序內(nèi)的其他 module 提供背景功能,例如和第三方 API 進行互動。
將您的代碼組織成 module 形式有利于讓您輕松地重用其他應(yīng)用程序中的功能,或者使用社區(qū)提供的 module。
我們要做的下一件事情就是為我們的應(yīng)用程序添加一個路徑,這樣就能通過 URL localhost:8080/blog
來訪問我們的 module。要實現(xiàn)這點,需要為我們的 module 添加路由配置,但首先我們需要讓 ModuleManager
知道我們的 module 具有這些需要載入的配置表。
添加一個 getConfig()
函數(shù)到 Module
類中,并讓其返回配置。(這個函數(shù)是在 ConfigProviderInterface
中聲明的,盡管實際上是否要實現(xiàn)這個接口是可選的) 這個函數(shù)應(yīng)該能返回一個 array
或者一個 Traversable
對象。繼續(xù)編輯您的/module/Blog/Module.php
:
<?php
// 文件名: /module/Blog/Module.php
namespace Blog;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements ConfigProviderInterface
{
public function getConfig()
{
return array();
}
}
做到這里我們的 Module
就可以被配置了。配置文件可能會變得十分大,此時將所有東西放在 getConfig()
中就不是最佳手段了。為了保持我們的工程的組織清晰,我們會將數(shù)組配置放在單獨的文件中。在此我們創(chuàng)建 /module/Blog/config/module.config.php
:
<?php
// 文件名: /module/Blog/config/module.config.php
return array();
現(xiàn)在我們來重寫 getConfig()
函數(shù)來添加這個剛剛建立的新文件。
<?php
// 文件名: /module/Blog/Module.php
namespace Blog;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements ConfigProviderInterface
{
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
}
重新裝載您的應(yīng)用程序,這是你便會見到所有東西仍然和之前一樣,接下來我們給配置文件添加一個新路徑:
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
// 該行為 RouteManager 打開配置
'router' => array(
// 打開所有可能路徑的配置O
'routes' => array(
// 定義一個新路徑,稱為 "post"
'post' => array(
// 定義一個路徑 "Zend\Mvc\Router\Http\Literal" , 基本上就是一個字符串
'type' => 'literal',
// 配置路徑本身
'options' => array(
// 監(jiān)聽 uri "/blog"
'route' => '/blog',
// 定義默認控制器和當(dāng)這個路徑匹配時需要執(zhí)行的動作
'defaults' => array(
'controller' => 'Blog\Controller\List',
'action' => 'index',
)
)
)
)
)
);
現(xiàn)在我們添加了一個叫做 blog
的路徑,該路徑負責(zé)監(jiān)聽 URL localhost:8080/blog
。每當(dāng)有人訪問這個路徑時,Blog\Controller\List
類中的 indexAction()
函數(shù)就會被執(zhí)行。不過,這個控制器暫時還不存在,所以如果你重新裝載頁面你就會看見這個錯誤信息:
A 404 error occurred
Page not found.
The requested controller could not be mapped to an existing controller class.
Controller:
Blog\Controller\List(resolves to invalid controller class or alias: Blog\Controller\List)
No Exception available
現(xiàn)在我們需要告訴 module 要去哪里尋找這個叫做 Blog\Controller\List
的控制器。要做到這點我們必須將這個 key 添加到 controllers
配置 key,在 /module/Blog/config/module.config.php
內(nèi)。
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
'controllers' => array(
'invokables' => array(
'Blog\Controller\List' => 'Blog\Controller\ListController'
)
),
'router' => array( /** Route Configuration */ )
);
這個配置文件定義了 Blog\Controller\List
是在名稱空間 Blog\Controller
下的 ListController
的別名。重新裝載頁面應(yīng)該會看見以下信息:
( ! ) Fatal error: Class 'Blog\Controller\ListController' not found in {libPath}/Zend/ServiceManager/AbstractPluginManager.php on line {lineNumber}
這個錯誤告訴我們應(yīng)用程序知道要載入哪個類,但是并不知道在哪里能找到它。要修正這個問題,我們需要為我們的 Module
配置 autoloading。 Autoloading 是一個過程,讓 PHP 能自動根據(jù)需求載入類。至于我們的 Module,我們通過添加 getAutoloaderConfig()
函數(shù)到我們的 Module 類來實現(xiàn)這點。(這個函數(shù)是在 AutoloaderProviderInterface 中定義的,盡管實際上類是否要實現(xiàn)這個接口是可選的)
<?php
// 文件名: /module/Blog/Module.php
namespace Blog;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements
AutoloaderProviderInterface,
ConfigProviderInterface
{
/**
* 返回一個 array 傳給 Zend\Loader\AutoloaderFactory.
*
* @return array
*/
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
// Autoload 所有類從名稱空間 'Blog' 來自于 '/module/Blog/src/Blog'
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
)
)
);
}
/**
* 返回配置來和應(yīng)用程序配置合并
*
* @return array|\Traversable
*/
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
}
這看上去好像很多東西被改變了,不過不用害怕。我們已經(jīng)添加了一個 getAutoloaderConfig()
函數(shù)來提供Zend\Loader\StandardAutoloader
的配置。這個配置文件告訴應(yīng)用程序 __NAMESPACE__
(Blog) 中的類都能在 __DIR__.'/src/'.__NAMESPACE__
中找到 (/module/Blog/src/Blog
)。
Zend\Loader\StandardAutoloader
使用了 PHP 社區(qū)領(lǐng)導(dǎo)的標準,稱為PSR-0(https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)。在其他東西之中,這個標準為 PHP 定義了一個方法來映射類名稱到文件系統(tǒng)。所以有了這個配置后,應(yīng)用程序就知道我們的 Blog\Controller\ListController
類應(yīng)該在 /module/Blog/src/Blog/Controller/ListController.php
中存在。
如果你現(xiàn)在刷新瀏覽器,你還是會見到同樣的錯誤,即使我們已經(jīng)配置了 autoloader,因為我們還需要創(chuàng)建控制器類。我們立刻創(chuàng)建這個文件:
<?php
// 文件名: /module/Blog/src/Blog/Controller/ListController.php
namespace Blog\Controller;
class ListController
{
}
再次重新載入頁面,現(xiàn)在我們終于看見不一樣的內(nèi)容了。新的錯誤信息(類似下文):
A 404 error occurred
Page not found.
The requested controller was not dispatchable.
Controller:
Blog\Controller\List(resolves to invalid controller class or alias: Blog\Controller\List)
Additional information:
Zend\Mvc\Exception\InvalidControllerException
File:
{libraryPath}/Zend/Mvc/Controller/ControllerManager.php:{lineNumber}
Message:
Controller of type Blog\Controller\ListController is invalid; must implement Zend\Stdlib\DispatchableInterface
這是因為我們的控制器必須實現(xiàn) ZendStdlibDispatchableInterface 接口才能被 ZendFramework 的 MVC 層運行。ZendFramework 提供了一些該接口的基礎(chǔ)控制器實現(xiàn),叫做 AbstractActionController,我們稍后會用。讓我們先修改我們的控制器:
<?php
// 文件名: /module/Blog/src/Blog/Controller/ListController.php
namespace Blog\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class ListController extends AbstractActionController
{
}
是時候再次刷新站點的頁面了,您應(yīng)該會看見有一個新的錯誤消息:
An error occurred
An error occurred during execution; please try again later.
Additional information:
Zend\View\Exception\RuntimeException
File:
{libraryPath}/library/Zend/View/Renderer/PhpRenderer.php:{lineNumber}
Message:
Zend\View\Renderer\PhpRenderer::render: Unable to render template "blog/list/index"; resolver could not resolve to a file
現(xiàn)在應(yīng)用程序告訴你一個視圖模板文件沒法被渲染,這也是可預(yù)見的,畢竟我們還沒有將其創(chuàng)建。應(yīng)用程序期望它在 /module/Blog/view/blog/list/index.phtml
這個位置。創(chuàng)建這個文件并且放置一些假內(nèi)容在上面:
<!-- Filename: /module/Blog/view/blog/list/index.phtml -->
<h1>Blog\ListController::indexAction()</h1>
在我們繼續(xù)之前,先讓我們快速的看一下我們將該文件放置在何處。請注意視圖文件是放置在 /view
子目錄下的,并不是在 /src
子目錄下,因為他們不是 PHP 類文件,而是用于渲染 HTML 的模板文件。下面的路徑需要一些解釋,不過也非常簡單。首先我們有全小寫的名稱空間,跟著一個小寫的控制器名稱(沒有后綴‘controller’),最后跟上我們正在訪問的動作的名稱(同樣地,沒有后綴‘a(chǎn)ction’)??偟膩砜聪襁@樣:/view/{namespace}/{controller}/{action}.phtml
。這已經(jīng)成為了社區(qū)標準,不過也有潛在的隨時被你改變的可能。不過光是創(chuàng)建這個文件是不夠的,這就帶出了這次快速開始指南的最后主題:我們需要讓應(yīng)用程序知道上哪去尋找視圖文件。通過修改我們的 module 配置文件 module.config.php
來實現(xiàn):
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
'controllers' => array( /** Controller Configuration */),
'router' => array( /** Route Configuration */ )
);
上面的配置告訴應(yīng)用程序目錄 /module/Blog/view
中擁有視圖文件來匹配上述默認樣式。重要的是,你需要記住你不單能將視圖文件指定給您的 module,同時也能用于覆蓋其他 module 的視圖文件。
現(xiàn)在刷新您的站點。終于我們可以看見錯誤消息之外的內(nèi)容了。
恭喜!你不單止創(chuàng)建了一個簡單的“Hello World”風(fēng)格 module,還學(xué)會了許多錯誤消息和他們的起因。如果到現(xiàn)在你還沒被磨死,請繼續(xù)我們的快速開始指南,一起來創(chuàng)建一個能真正干點事情的 module。
更多建議: