通過(guò)控制器動(dòng)作方法和視圖腳本,建立了 Album
模塊,是時(shí)候注意到程序中的模型部分了。記住,模型是解決程序核心目的的一部分(也將模型稱(chēng)之為業(yè)務(wù)規(guī)則),在本例中,也是數(shù)據(jù)處理的一部分。使用 Zend Framework 中的 Zend\Db\TableGateway\TableGateway
類(lèi),這個(gè)類(lèi)是用來(lái)查找,插入,更新和刪除數(shù)據(jù)庫(kù)中表記錄的。
通過(guò) PHP 的 PDO 驅(qū)動(dòng),使用 MySQL,創(chuàng)建名為 zf2tutorial
數(shù)據(jù)庫(kù),然后運(yùn)行 SQL 語(yǔ)句創(chuàng)建一個(gè) album 表,并插入一些數(shù)據(jù)。
CREATE TABLE album (
id int(11) NOT NULL auto_increment,
artist varchar(100) NOT NULL,
title varchar(100) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO album (artist, title)
VALUES ('The Military Wives', 'In My Dreams');
INSERT INTO album (artist, title)
VALUES ('Adele', '21');
INSERT INTO album (artist, title)
VALUES ('Bruce Springsteen', 'Wrecking Ball (Deluxe)');
INSERT INTO album (artist, title)
VALUES ('Lana Del Rey', 'Born To Die');
INSERT INTO album (artist, title)
VALUES ('Gotye', 'Making Mirrors');
(所選擇的測(cè)試數(shù)據(jù)是英國(guó)亞馬遜暢銷(xiāo)書(shū)的相關(guān)信息!)
現(xiàn)在數(shù)據(jù)庫(kù)中有了一些數(shù)據(jù),可以為其編寫(xiě)一個(gè)簡(jiǎn)單的模型。
Zend Framework 沒(méi)有提供 Zend\Model
控件,因?yàn)槟P褪悄愕臉I(yè)務(wù)邏輯,取決于你想讓它如何工作。根據(jù)你的需求,這里仍然有很多控件可以供你使用。一種方法是,在你的程序中模型類(lèi)對(duì)應(yīng)一種實(shí)體,然后使用映射器對(duì)象來(lái)加載和存儲(chǔ)實(shí)體到數(shù)據(jù)庫(kù)。另一種是使用對(duì)象關(guān)系映射計(jì)算,例如 Doctrine 或者 Propel。
在這個(gè)教程中,每一個(gè)專(zhuān)輯就是一個(gè) Album
對(duì)象(或者說(shuō)是實(shí)體),通過(guò)使用 Zend\Db\TableGateway\TableGateway
構(gòu)建一個(gè) AlbumTable
類(lèi),然后使用 AlbumTable
構(gòu)建一個(gè)簡(jiǎn)單的模型。在數(shù)據(jù)庫(kù)的表中,Table Data Gateway 的設(shè)計(jì)模式可以實(shí)現(xiàn)數(shù)據(jù)接口。請(qǐng)注意,雖然 Table Data Gateway 模式在大系統(tǒng)中可能會(huì)被限制。但這個(gè)也是個(gè)誘惑,如果你把數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)代碼放進(jìn)控制器的動(dòng)作方法,Zend\Db\TableGateway\AbstractTableGateway
這個(gè)類(lèi)就會(huì)暴露這些代碼。請(qǐng)不要這么做!
在 module/Album/src/Album/Model
路徑下新建一個(gè) Album.php
:
namespace Album\Model;
class Album
{
public $id;
public $artist;
public $title;
public function exchangeArray($data)
{
$this->id = (!empty($data['id'])) ? $data['id'] : null;
$this->artist = (!empty($data['artist'])) ? $data['artist'] : null;
$this->title = (!empty($data['title'])) ? $data['title'] : null;
}
}
Album
實(shí)體對(duì)象是一個(gè)簡(jiǎn)單的 PHP 類(lèi)。為了與 Zend\Db
的 TableGateway
類(lèi)一起工作。需要實(shí)現(xiàn) exchangeArray()
方法。這個(gè)方法簡(jiǎn)單地將 data 數(shù)組中的數(shù)據(jù)拷貝到對(duì)應(yīng)實(shí)體屬性。
下一步,在 module/Album/src/Album/Model
目錄下新建 AlbumTable.php
,內(nèi)容如下:
namespace Album\Model;
use Zend\Db\TableGateway\TableGateway;
class AlbumTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getAlbum($id)
{
$id = (int) $id;
$rowset = $this->tableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
public function saveAlbum(Album $album)
{
$data = array(
'artist' => $album->artist,
'title' => $album->title,
);
$id = (int) $album->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getAlbum($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Album id does not exist');
}
}
}
public function deleteAlbum($id)
{
$this->tableGateway->delete(array('id' => (int) $id));
}
}
這里做了許多事情。首先,我們?cè)跇?gòu)造函數(shù)中設(shè)置了保護(hù)屬性 $tableGateway
為 TableGateway
的實(shí)例。我們將使用這個(gè)來(lái)屬性操作數(shù)據(jù)庫(kù)的 album 表。
繼續(xù)創(chuàng)建一些幫助方法,程序會(huì)使用 table gateWay 和這個(gè)實(shí)例。fetchAll()
檢索數(shù)據(jù)庫(kù)中 albums 表所有的記錄,然后將結(jié)果返回 ResultSet
,getAlbum()
通過(guò) id 檢索一個(gè) Album
對(duì)象,saveAlbum()
要么創(chuàng)建一個(gè)新的記錄,或更新已經(jīng)存在記錄,deleteAlbum()
移除一條記錄。這些代碼不需要解釋的。
為了總是使用同一個(gè)的 AlbumTable
實(shí)例,我們將使用 ServiceManager
來(lái)定義如何創(chuàng)建。最容易的是,在模塊類(lèi)中,我們創(chuàng)建一個(gè)名為 getServiceConfig()
的方法,它可以被 ModuleManager
自動(dòng)調(diào)用,適用于 ServiceManager
。然后,當(dāng)我們需要它的時(shí)候,就可以取回它。
為了配置 ServiceManager
,在 ServiceManager
需要的時(shí)候,我們提供一個(gè)類(lèi)的名字或者一個(gè)工廠(chǎng)(閉包或者回調(diào)),來(lái)產(chǎn)生實(shí)例化的對(duì)象。我們通過(guò)實(shí)現(xiàn) getServiceConfig()
來(lái)提供一個(gè)工廠(chǎng),這個(gè)工廠(chǎng)用來(lái)創(chuàng)建一個(gè) AlbumTable
。添加這個(gè)方法到 module/Album
目錄下的 Module.php
文件的底部。
namespace Album;
// Add these import statements:
use Album\Model\Album;
use Album\Model\AlbumTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
class Module
{
// getAutoloaderConfig() and getConfig() methods here
// Add this method:
public function getServiceConfig()
{
return array(
'factories' => array(
'Album\Model\AlbumTable' => function($sm) {
$tableGateway = $sm->get('AlbumTableGateway');
$table = new AlbumTable($tableGateway);
return $table;
},
'AlbumTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Album());
return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
}
這個(gè)方法返回 factories
的數(shù)組,在傳遞給 ServiceManager
之前,通過(guò) ModuleManager
進(jìn)行合并。Album\Model\AlbumTable
的工廠(chǎng)使用 ServiceManager
來(lái)創(chuàng)建一個(gè) AlbumTableGateway
對(duì)象,以便傳遞到 AlbumTable
對(duì)象。一般會(huì)告知 ServiceManager
對(duì)象,AlbumTableGateway
對(duì)象的創(chuàng)建是通過(guò)得到一個(gè) Zend\Db\Adapter\Adapter
對(duì)象(也是從 ServiceManager
獲取)來(lái)完成的,然后使用 AlbumTableGateway
對(duì)象來(lái)創(chuàng)建一個(gè) TableGateway
對(duì)象。TableGateway
對(duì)象每當(dāng)他創(chuàng)建一條新記錄時(shí),都會(huì)告知一個(gè) Album
對(duì)象。TableGateway
類(lèi)使用原型模式創(chuàng)建結(jié)果集和實(shí)體。這意味著在請(qǐng)求的時(shí)候不是實(shí)例化,而是系統(tǒng)克隆先前實(shí)例化的對(duì)象。參考 PHP Constructor Best Practices and the Prototype Pattern。
最終,我們需要配置 ServiceManager
,讓其知道如何獲取 Zend\Db\Adapter\Adapter
。這是通過(guò)使用一個(gè)工廠(chǎng)類(lèi) Zend\Db\Adapter\AdapterServiceFactory
,我們能在合并配置系統(tǒng)中配置它。Zend Framework 2 的 ModuleManager
合并每一個(gè)模塊的 module.config.php
文件中所有的配置,然后合并導(dǎo)出到 config/autoload
下的文件(*.global.php
和 *.local.php
文件)。我們將添加我們的數(shù)據(jù)庫(kù)配置信息到 global.php
這個(gè)文件,這個(gè)文件應(yīng)該提交到你的版本控制系統(tǒng)。如果你想的話(huà),現(xiàn)在可以使用 local.php
(版本控制系統(tǒng)之外的)來(lái)存儲(chǔ)數(shù)據(jù)庫(kù)的證書(shū)。修改 config/autoload/global.php
(在 Zend 骨架的 root 中,并不在 Album 模塊中),代碼如下:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2tutorial;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
你應(yīng)該將你的數(shù)據(jù)庫(kù)證書(shū)放到 config/autoload/local.php
,所以他們不是在 Git 存儲(chǔ)庫(kù)(就像 local.php
會(huì)被忽略):
return array(
'db' => array(
'username' => 'YOUR USERNAME HERE',
'password' => 'YOUR PASSWORD HERE',
),
);
現(xiàn)在 ServiceManager
可以創(chuàng)建一個(gè) AlbumTable
實(shí)例了,我們可以添加一個(gè)方法到控制器去見(jiàn)檢索它。添加 getAlbumTable()
到 AlbumController
類(lèi)中:
// module/Album/src/Album/Controller/AlbumController.php:
public function getAlbumTable()
{
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('Album\Model\AlbumTable');
}
return $this->albumTable;
}
你也應(yīng)該添加下面這句到類(lèi)首部:
protected $albumTable;
每當(dāng)我們需要與模型進(jìn)行交互的時(shí)候,就可以在控制器中調(diào)用 getAlbumTable()
。
如果服務(wù)定位器在 Module.php
中正確配置,那么在調(diào)用 getAlbumTable()
時(shí)將會(huì)拿到一個(gè) Album\Model\AlbumTable
實(shí)例。
為了列舉專(zhuān)輯,我們需要從模型中檢索他們?nèi)缓髠鬟f到視圖。為此,我們?cè)?AlbumController
填寫(xiě) indexAction()
。更新 AlbumController
的 indexAction()
方式如下:
// module/Album/src/Album/Controller/AlbumController.php:
// ...
public function indexAction()
{
return new ViewModel(array(
'albums' => $this->getAlbumTable()->fetchAll(),
));
}
// ...
在 Zend Framework 2 中,為了在視圖中設(shè)置變量,我們返回一個(gè) ViewModel
實(shí)例,構(gòu)造函數(shù)的第一個(gè)參數(shù)是一個(gè)來(lái)自動(dòng)作的數(shù)組,包含我們需要的數(shù)據(jù)。然后會(huì)自動(dòng)傳遞到視圖腳本。ViewModel
對(duì)象還允許我們改變視圖腳本,但是默認(rèn)是使用{controller name}/{action name}
。我們現(xiàn)在可以填寫(xiě)視圖腳本 index.phtml
。
<?php
// module/Album/view/album/album/index.phtml:
$title = 'My albums';
$this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1>
<p>
<a href="<?php echo $this->url('album', array('action'=>'add'));?>">Add new album</a>
</p>
<table class="table">
<tr>
<th>Title</th>
<th>Artist</th>
<th> </th>
</tr>
<?php foreach ($albums as $album) : ?>
<tr>
<td><?php echo $this->escapeHtml($album->title);?></td>
<td><?php echo $this->escapeHtml($album->artist);?></td>
<td>
<a href="<?php echo $this->url('album',
array('action'=>'edit', 'id' => $album->id));?>">Edit</a>
<a href="<?php echo $this->url('album',
array('action'=>'delete', 'id' => $album->id));?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</table>
第一件事是我們要為頁(yè)面設(shè)置標(biāo)題,標(biāo)題可以在瀏覽器的標(biāo)題欄顯示,可以在 <head>
部分使用視圖幫助方法 headTitle()
來(lái)進(jìn)行設(shè)置標(biāo)題。然后我們添加一個(gè)創(chuàng)建新 album 的鏈接。
Zend Framework 2 提供了視圖幫助方法 url()
,用來(lái)創(chuàng)建 URL。url()
的第一個(gè)參數(shù)是 route,用來(lái)創(chuàng)建 URL,第二個(gè)參數(shù)是所有變量的數(shù)組,用來(lái)填充字段的。在本例中,我們使用 ‘a(chǎn)lbum’ route 以及設(shè)置兩個(gè)字段變量 action
和 id
。
我們遍歷了 $albums
中從控制器分配的動(dòng)作。Zend Framework 2 視圖系統(tǒng)自動(dòng)確認(rèn)了填充到視圖腳本域的變量,所以我們不用擔(dān)心還要像舊框架 Zend Framework 1 中那樣使用前綴 $this->
來(lái)區(qū)別變量,當(dāng)然你仍然可以在這么做。
然后創(chuàng)建一個(gè)表格來(lái)展示每一個(gè) album 的標(biāo)題和藝術(shù)家,提供一些鏈接,用以修改和刪除記錄。一個(gè)標(biāo)準(zhǔn)的變量是:使用迭代器循環(huán)遍歷 albums 的列表,我們交替使用冒號(hào)和 endforeach 的形式;相對(duì)于嘗試匹配括號(hào),這就可以十分簡(jiǎn)單訪(fǎng)問(wèn)元素了。再一次使用視圖幫助方法 url()
來(lái)創(chuàng)建修改和刪除鏈接。
我們可以使用視圖幫助方法
escapeHtml()
來(lái)輔助保護(hù)我們自己的跨站腳本的缺陷。(詳情查看 http://en.wikipedia.org/wiki/Cross-site_scripting)
如果你打開(kāi) http://zf2-tutorial.localhost/album
,就能看到如下效果:
更多建議: