我們現(xiàn)在可以添加新專輯代碼的功能。這分為兩個(gè)部分:
展示表單給用戶用來(lái)提供細(xì)節(jié)
我們用 Zend\Form
來(lái)處理這些。Zend\Form
控件管理表單和處理表單驗(yàn)證,添加一個(gè) Zend\InputFilter
到 Album
實(shí)體。開(kāi)始寫我們的新類 Album\Form\AlbumForm
,這個(gè)類繼承自 Zend\Form\Form
。在 module/Album/src/Album/Form
目錄下新建一個(gè) AlbumForm.php
文件,內(nèi)容如下:
namespace Album\Form;
use Zend\Form\Form;
class AlbumForm extends Form
{
public function __construct($name = null)
{
// we want to ignore the name passed
parent::__construct('album');
$this->add(array(
'name' => 'id',
'type' => 'Hidden',
));
$this->add(array(
'name' => 'title',
'type' => 'Text',
'options' => array(
'label' => 'Title',
),
));
$this->add(array(
'name' => 'artist',
'type' => 'Text',
'options' => array(
'label' => 'Artist',
),
));
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Go',
'id' => 'submitbutton',
),
));
}
}
在 AlbumForm
的構(gòu)造函數(shù)中,我們需要做一些事情。首先我們要設(shè)置表單的名字,調(diào)用父類構(gòu)造函數(shù)。接著我們創(chuàng)建四個(gè)表單元素:id,title,artist,以及提交按鈕。對(duì)每一項(xiàng),我們都要設(shè)置各種各樣的屬性和設(shè)置,包括要顯示的標(biāo)簽。
HTML-Forms 可以使用
POST
和GET
來(lái)發(fā)送。ZF2s 默認(rèn)使用POST
,因此你不必顯式的設(shè)置這個(gè)選項(xiàng)。如果你想改成GET
,你所做的就是需要在構(gòu)造函數(shù)中指定。
$this->setAttribute('method', 'GET');
我們需要為表單設(shè)置驗(yàn)證。在 Zend Framework 2,驗(yàn)證通過(guò)使用輸入過(guò)濾器處理,這個(gè)過(guò)濾器可以是獨(dú)立的或者可以在類中定義。它繼承自 InputFilterAwareInterface
接口類,就像一個(gè)模型實(shí)體。在本例中,將輸入過(guò)濾器添加到 Album 類,module/Album/src/Album/Model
路徑下的 Album.php
文件修改如下:
namespace Album\Model;
// Add these import statements
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Album implements InputFilterAwareInterface
{
public $id;
public $artist;
public $title;
protected $inputFilter; // <-- Add this variable
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
// Add content to these methods:
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$inputFilter->add(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
$inputFilter->add(array(
'name' => 'artist',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
));
$inputFilter->add(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
InputFilterAwareInterface
定義了兩方法:setInputFilter()
和 getInputFilter()
。我們需要實(shí)現(xiàn) getInputFilter()
方法,而 setInputFilter()
只要簡(jiǎn)單的拋一個(gè)異常就行了。
在 getInputFilter()
中,實(shí)例化一個(gè) InputFilter
,然后添加我們想要的輸入框。為每個(gè)屬性對(duì)應(yīng)添加過(guò)濾和驗(yàn)證。例如為 id
字段添加整型過(guò)濾器,為文本元素添加兩個(gè)過(guò)濾器,StripTags
和 StringTrim
,用來(lái)移除不想要的 HTML 和不必要的空白字符。還要為這些屬性添加 StringLength
,確保不會(huì)輸入太多的字符,以便存入數(shù)據(jù)庫(kù)。
現(xiàn)在需要獲取表單進(jìn)行顯示,然后在提交時(shí)進(jìn)行處理。在 AlbumController
的 addAction()
:
// module/Album/src/Album/Controller/AlbumController.php:
//...
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Album\Model\Album; // <-- Add this import
use Album\Form\AlbumForm; // <-- Add this import
//...
// Add content to this method:
public function addAction()
{
$form = new AlbumForm();
$form->get('submit')->setValue('Add');
$request = $this->getRequest();
if ($request->isPost()) {
$album = new Album();
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$album->exchangeArray($form->getData());
$this->getAlbumTable()->saveAlbum($album);
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
}
return array('form' => $form);
}
//...
添加 AlbumForm
到使用列表之后,我們實(shí)現(xiàn) addAction()
??匆豢?addAction()
的內(nèi)部細(xì)節(jié)吧:
$form = new AlbumForm();
$form->get('submit')->setValue('Add');
實(shí)例化 AlbumForm
然后設(shè)置提交按鈕的標(biāo)簽為 Add。在編輯專輯會(huì)使用到不同的標(biāo)簽,就可以復(fù)用代碼。
$request = $this->getRequest();
if ($request->isPost()) {
$album = new Album();
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
如果 Request
對(duì)象的 isPost()
方法返回 true,表明表單已經(jīng)被提交了。從專輯實(shí)例設(shè)置表單的輸入過(guò)濾器,然后我們將報(bào)文數(shù)據(jù)設(shè)置到表單中,使用表單對(duì)象的 isValid()
成員函數(shù)來(lái)檢查數(shù)據(jù)是否有效。
$album->exchangeArray($form->getData());
$this->getAlbumTable()->saveAlbum($album);
如果表單是有效的,就從表單中獲取數(shù)據(jù),使用 saveAlbum()
存儲(chǔ)到模型中。
// Redirect to list of albums
return $this->redirect()->toRoute('album');
在保存新記錄之后,使用重定向控制器插件重定向到專輯的列表。
return array('form' => $form);
最終,返回我們想指定給視圖的變量。在本例中,僅僅是表單對(duì)象。注意 Zend Framework 2 也運(yùn)行返回變量的數(shù)組,然后指定給視圖,這將會(huì)在場(chǎng)景后邊創(chuàng)建一個(gè) ViewModel
??梢陨佥斎朦c(diǎn)字。
現(xiàn)在我們需要在 add.phtml
視圖腳本中渲染表單。
<?php
// module/Album/view/album/album/add.phtml:
$title = 'Add new album';
$this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1>
<?php
$form->setAttribute('action', $this->url('album', array('action' => 'add')));
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formHidden($form->get('id'));
echo $this->formRow($form->get('title'));
echo $this->formRow($form->get('artist'));
echo $this->formSubmit($form->get('submit'));
echo $this->form()->closeTag();
我們先展示一個(gè)標(biāo)題,然后再渲染表單。Zend 框架提供一些視圖輔助函數(shù),可以十分簡(jiǎn)單地完成上訴要求。form()
輔助函數(shù)有一個(gè) openTag()
和 closeTag()
方法,用來(lái)控制表單的打開(kāi)和關(guān)閉。對(duì)每一個(gè)元素的標(biāo)簽,可以使用 formRow()
,但是兩個(gè)元素太單一了,還要使用 formHidden()
和 formSubmit()
。
或者,渲染表單的過(guò)程可以綁定到視圖輔助方法 formCollection
上。例如,在上面的視圖腳本替代所有的表單渲染的輸出語(yǔ)句是:
echo $this->formCollection($form);
注意:你仍然需要使用 openTag
和 closeTag
方法來(lái)控制表單。上面的代碼,你可以替代其他輸入語(yǔ)句,調(diào)用 formCollection
。
這將會(huì)對(duì)表單結(jié)構(gòu)進(jìn)行遍歷,對(duì)每個(gè)元素調(diào)用合適的標(biāo)簽,元素和視圖輔助的錯(cuò)誤提示,你通過(guò)打開(kāi)和關(guān)閉表單標(biāo)簽包裝 formCollection($form)。
現(xiàn)有應(yīng)該使用程序主頁(yè)上的 Add new album 鏈接來(lái)增加一條新的 album 記錄。
編輯專輯和添加一個(gè)專輯的代碼幾乎是相同,所以代碼都很簡(jiǎn)單。這次在 AlbumController
中使用 editAction()
:
// module/Album/src/Album/Controller/AlbumController.php:
//...
// Add content to this method:
public function editAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('album', array(
'action' => 'add'
));
}
// Get the Album with the specified id. An exception is thrown
// if it cannot be found, in which case go to the index page.
try {
$album = $this->getAlbumTable()->getAlbum($id);
}
catch (\Exception $ex) {
return $this->redirect()->toRoute('album', array(
'action' => 'index'
));
}
$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getAlbumTable()->saveAlbum($album);
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
//...
代碼看地來(lái)很簡(jiǎn)單。讓我們看看與添加 album 之間的不同。首先查找配置 route 中 id
,然后加載對(duì)應(yīng)的專輯,代碼如下:
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('album', array(
'action' => 'add'
));
}
// Get the album with the specified id. An exception is thrown
// if it cannot be found, in which case go to the index page.
try {
$album = $this->getAlbumTable()->getAlbum($id);
}
catch (\Exception $ex) {
return $this->redirect()->toRoute('album', array(
'action' => 'index'
));
}
params
是一個(gè)控制器插件,提供一個(gè)簡(jiǎn)便的方式來(lái)檢索匹配的路由。在 module.config.php
,我們創(chuàng)建在模塊中的 route,使用它來(lái)進(jìn)行檢索 id
。如果 id
是零,就會(huì)重定向到添加動(dòng)作,否則,我們繼續(xù)從數(shù)據(jù)庫(kù)中獲取專輯實(shí)體。
必須檢查,確保指定 id
的專輯可以被找到。如果不行,數(shù)據(jù)訪問(wèn)方法將會(huì)拋出異常。捕獲該異常并重新輸入用戶索引頁(yè)面。
$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit');
表單的 bind()
方法附著于模型。有如下兩個(gè)方式:
當(dāng)顯示表單時(shí),每個(gè)元素的初始值都從模型中提取。
isValid()
成功驗(yàn)證后,表單中的數(shù)據(jù)推送回模型中。這些操作通過(guò)復(fù)合對(duì)象完成的。有許多的復(fù)合對(duì)象,但是只會(huì)使用 Zend\Stdlib\Hydrator\ArraySerializable 作為默認(rèn)復(fù)合對(duì)象,這個(gè)復(fù)合對(duì)象在模型指定了兩個(gè)方法:getArrayCopy()
和 exchangeArray()
。我們?cè)缫言?Album 實(shí)體中寫好了 exchangeArray()
,所以只要寫好 getArrayCopy()
:
// module/Album/src/Album/Model/Album.php:
// ...
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
// Add the following method:
public function getArrayCopy()
{
return get_object_vars($this);
}
// ...
復(fù)合對(duì)象使用 bind()
的結(jié)果是,我們不用往 $album
填充表單的數(shù)據(jù),因?yàn)橐呀?jīng)自動(dòng)填充好了,只要調(diào)用 mappers 的 saveAlbum()
來(lái)保存修改到數(shù)據(jù)庫(kù)。
視圖模板,edit.phtml
,添加一個(gè)專輯的如下所示:
<?php
// module/Album/view/album/album/edit.phtml:
$title = 'Edit album';
$this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1>
<?php
$form = $this->form;
$form->setAttribute('action', $this->url(
'album',
array(
'action' => 'edit',
'id' => $this->id,
)
));
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formHidden($form->get('id'));
echo $this->formRow($form->get('title'));
echo $this->formRow($form->get('artist'));
echo $this->formSubmit($form->get('submit'));
echo $this->form()->closeTag();
唯一的變化是使用 Edit Album 的標(biāo)題和設(shè)置表單的動(dòng)作到 edit 的動(dòng)作。
現(xiàn)在可以編輯專輯了。
為完善我們的程序,我們需要添加刪除操作。列表中每一個(gè)專輯都有一個(gè)刪除鏈接,使用最原始點(diǎn)擊方式來(lái)對(duì)應(yīng)刪除記錄。這或許很糟糕,記住使用 HTTP 的規(guī)范,執(zhí)行一個(gè)不可撤銷的動(dòng)作,應(yīng)該使用 POST 而不是使用 GET。
在用戶點(diǎn)擊刪除時(shí),我們要顯示一個(gè)確認(rèn)窗口,在用戶點(diǎn)擊 yes 后,就會(huì)進(jìn)行刪除。如果表單并不重要,就將代碼直接寫入視圖腳本中(畢竟,Zend\Form
是可選?。?/p>
在 AlbumController::deleteAction()
寫下如下代碼:
// module/Album/src/Album/Controller/AlbumController.php:
//...
// Add content to the following method:
public function deleteAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('album');
}
$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->getPost('del', 'No');
if ($del == 'Yes') {
$id = (int) $request->getPost('id');
$this->getAlbumTable()->deleteAlbum($id);
}
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
return array(
'id' => $id,
'album' => $this->getAlbumTable()->getAlbum($id)
);
}
//...
在獲取匹配專輯的表單 id,使用請(qǐng)求對(duì)象的 isPost()
來(lái)決定顯示確認(rèn)頁(yè)面或者直接刪除專輯。使用表對(duì)象的 deleteAlbum()
方法刪除記錄,然后重定向回到專輯列表。如果不是 POST
請(qǐng)求,我們就會(huì)取回正確的數(shù)據(jù)庫(kù)記錄,然后連同 id
返回給視圖。
視圖腳本的簡(jiǎn)單表單:
<?
// module/Album/view/album/album/delete.phtml:
$title = 'Delete album';
$this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1>
<p>Are you sure that you want to delete
'<?php echo $this->escapeHtml($album->title); ?>' by
'<?php echo $this->escapeHtml($album->artist); ?>'?
</p>
<?php
$url = $this->url('album', array(
'action' => 'delete',
'id' => $this->id,
));
?>
<form action="<?php echo $url; ?>" method="post">
<div>
<input type="hidden" name="id" value="<?php echo (int) $album->id; ?>" />
<input type="submit" name="del" value="Yes" />
<input type="submit" name="del" value="No" />
</div>
</form>
在這個(gè)腳本中,我們展示一個(gè)帶有 Yes 和 No 按鈕的確認(rèn)信息。如果用戶點(diǎn)擊 Yes 我們就會(huì)執(zhí)行刪除操作。
最后一點(diǎn)。此刻,主頁(yè) http://zf2-tutorial.localhost/
并沒(méi)有顯示專輯列表。
這是由于在 Application
模塊中的 module.config.php
route 的設(shè)置。為了改變?cè)O(shè)置,打開(kāi) module/Application/config/module.config.php
找到主頁(yè)的 route。
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'index',
),
),
),
將控制器由 Application\Controller\Index
改為 Album\Controller\Album
。
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Album\Controller\Album', // <-- change here
'action' => 'index',
),
),
),
就這些了,現(xiàn)在你有一個(gè)可以運(yùn)行的程序了。
更多建議: