通過控制器動作方法和視圖腳本,建立了 Album
模塊,是時候注意到程序中的模型部分了。記住,模型是解決程序核心目的的一部分(也將模型稱之為業(yè)務(wù)規(guī)則),在本例中,也是數(shù)據(jù)處理的一部分。使用 Zend Framework 中的 Zend\Db\TableGateway\TableGateway
類,這個類是用來查找,插入,更新和刪除數(shù)據(jù)庫中表記錄的。
通過 PHP 的 PDO 驅(qū)動,使用 MySQL,創(chuàng)建名為 zf2tutorial
數(shù)據(jù)庫,然后運行 SQL 語句創(chuàng)建一個 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');
(所選擇的測試數(shù)據(jù)是英國亞馬遜暢銷書的相關(guān)信息!)
現(xiàn)在數(shù)據(jù)庫中有了一些數(shù)據(jù),可以為其編寫一個簡單的模型。
Zend Framework 沒有提供 Zend\Model
控件,因為模型是你的業(yè)務(wù)邏輯,取決于你想讓它如何工作。根據(jù)你的需求,這里仍然有很多控件可以供你使用。一種方法是,在你的程序中模型類對應(yīng)一種實體,然后使用映射器對象來加載和存儲實體到數(shù)據(jù)庫。另一種是使用對象關(guān)系映射計算,例如 Doctrine 或者 Propel。
在這個教程中,每一個專輯就是一個 Album
對象(或者說是實體),通過使用 Zend\Db\TableGateway\TableGateway
構(gòu)建一個 AlbumTable
類,然后使用 AlbumTable
構(gòu)建一個簡單的模型。在數(shù)據(jù)庫的表中,Table Data Gateway 的設(shè)計模式可以實現(xiàn)數(shù)據(jù)接口。請注意,雖然 Table Data Gateway 模式在大系統(tǒng)中可能會被限制。但這個也是個誘惑,如果你把數(shù)據(jù)庫訪問代碼放進控制器的動作方法,Zend\Db\TableGateway\AbstractTableGateway
這個類就會暴露這些代碼。請不要這么做!
在 module/Album/src/Album/Model
路徑下新建一個 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
實體對象是一個簡單的 PHP 類。為了與 Zend\Db
的 TableGateway
類一起工作。需要實現(xiàn) exchangeArray()
方法。這個方法簡單地將 data 數(shù)組中的數(shù)據(jù)拷貝到對應(yīng)實體屬性。
下一步,在 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));
}
}
這里做了許多事情。首先,我們在構(gòu)造函數(shù)中設(shè)置了保護屬性 $tableGateway
為 TableGateway
的實例。我們將使用這個來屬性操作數(shù)據(jù)庫的 album 表。
繼續(xù)創(chuàng)建一些幫助方法,程序會使用 table gateWay 和這個實例。fetchAll()
檢索數(shù)據(jù)庫中 albums 表所有的記錄,然后將結(jié)果返回 ResultSet
,getAlbum()
通過 id 檢索一個 Album
對象,saveAlbum()
要么創(chuàng)建一個新的記錄,或更新已經(jīng)存在記錄,deleteAlbum()
移除一條記錄。這些代碼不需要解釋的。
為了總是使用同一個的 AlbumTable
實例,我們將使用 ServiceManager
來定義如何創(chuàng)建。最容易的是,在模塊類中,我們創(chuàng)建一個名為 getServiceConfig()
的方法,它可以被 ModuleManager
自動調(diào)用,適用于 ServiceManager
。然后,當我們需要它的時候,就可以取回它。
為了配置 ServiceManager
,在 ServiceManager
需要的時候,我們提供一個類的名字或者一個工廠(閉包或者回調(diào)),來產(chǎn)生實例化的對象。我們通過實現(xiàn) getServiceConfig()
來提供一個工廠,這個工廠用來創(chuàng)建一個 AlbumTable
。添加這個方法到 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);
},
),
);
}
}
這個方法返回 factories
的數(shù)組,在傳遞給 ServiceManager
之前,通過 ModuleManager
進行合并。Album\Model\AlbumTable
的工廠使用 ServiceManager
來創(chuàng)建一個 AlbumTableGateway
對象,以便傳遞到 AlbumTable
對象。一般會告知 ServiceManager
對象,AlbumTableGateway
對象的創(chuàng)建是通過得到一個 Zend\Db\Adapter\Adapter
對象(也是從 ServiceManager
獲?。﹣硗瓿傻模缓笫褂?AlbumTableGateway
對象來創(chuàng)建一個 TableGateway
對象。TableGateway
對象每當他創(chuàng)建一條新記錄時,都會告知一個 Album
對象。TableGateway
類使用原型模式創(chuàng)建結(jié)果集和實體。這意味著在請求的時候不是實例化,而是系統(tǒng)克隆先前實例化的對象。參考 PHP Constructor Best Practices and the Prototype Pattern。
最終,我們需要配置 ServiceManager
,讓其知道如何獲取 Zend\Db\Adapter\Adapter
。這是通過使用一個工廠類 Zend\Db\Adapter\AdapterServiceFactory
,我們能在合并配置系統(tǒng)中配置它。Zend Framework 2 的 ModuleManager
合并每一個模塊的 module.config.php
文件中所有的配置,然后合并導(dǎo)出到 config/autoload
下的文件(*.global.php
和 *.local.php
文件)。我們將添加我們的數(shù)據(jù)庫配置信息到 global.php
這個文件,這個文件應(yīng)該提交到你的版本控制系統(tǒng)。如果你想的話,現(xiàn)在可以使用 local.php
(版本控制系統(tǒng)之外的)來存儲數(shù)據(jù)庫的證書。修改 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ù)庫證書放到 config/autoload/local.php
,所以他們不是在 Git 存儲庫(就像 local.php
會被忽略):
return array(
'db' => array(
'username' => 'YOUR USERNAME HERE',
'password' => 'YOUR PASSWORD HERE',
),
);
現(xiàn)在 ServiceManager
可以創(chuàng)建一個 AlbumTable
實例了,我們可以添加一個方法到控制器去見檢索它。添加 getAlbumTable()
到 AlbumController
類中:
// 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)該添加下面這句到類首部:
protected $albumTable;
每當我們需要與模型進行交互的時候,就可以在控制器中調(diào)用 getAlbumTable()
。
如果服務(wù)定位器在 Module.php
中正確配置,那么在調(diào)用 getAlbumTable()
時將會拿到一個 Album\Model\AlbumTable
實例。
為了列舉專輯,我們需要從模型中檢索他們?nèi)缓髠鬟f到視圖。為此,我們在 AlbumController
填寫 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è)置變量,我們返回一個 ViewModel
實例,構(gòu)造函數(shù)的第一個參數(shù)是一個來自動作的數(shù)組,包含我們需要的數(shù)據(jù)。然后會自動傳遞到視圖腳本。ViewModel
對象還允許我們改變視圖腳本,但是默認是使用{controller name}/{action name}
。我們現(xiàn)在可以填寫視圖腳本 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>
第一件事是我們要為頁面設(shè)置標題,標題可以在瀏覽器的標題欄顯示,可以在 <head>
部分使用視圖幫助方法 headTitle()
來進行設(shè)置標題。然后我們添加一個創(chuàng)建新 album 的鏈接。
Zend Framework 2 提供了視圖幫助方法 url()
,用來創(chuàng)建 URL。url()
的第一個參數(shù)是 route,用來創(chuàng)建 URL,第二個參數(shù)是所有變量的數(shù)組,用來填充字段的。在本例中,我們使用 ‘a(chǎn)lbum’ route 以及設(shè)置兩個字段變量 action
和 id
。
我們遍歷了 $albums
中從控制器分配的動作。Zend Framework 2 視圖系統(tǒng)自動確認了填充到視圖腳本域的變量,所以我們不用擔心還要像舊框架 Zend Framework 1 中那樣使用前綴 $this->
來區(qū)別變量,當然你仍然可以在這么做。
然后創(chuàng)建一個表格來展示每一個 album 的標題和藝術(shù)家,提供一些鏈接,用以修改和刪除記錄。一個標準的變量是:使用迭代器循環(huán)遍歷 albums 的列表,我們交替使用冒號和 endforeach 的形式;相對于嘗試匹配括號,這就可以十分簡單訪問元素了。再一次使用視圖幫助方法 url()
來創(chuàng)建修改和刪除鏈接。
我們可以使用視圖幫助方法
escapeHtml()
來輔助保護我們自己的跨站腳本的缺陷。(詳情查看 http://en.wikipedia.org/wiki/Cross-site_scripting)
如果你打開 http://zf2-tutorial.localhost/album
,就能看到如下效果:
http://wiki.jikexueyuan.com/project/zend2-user-guide/images/databaseandmodels.png" alt="image" />