我們現(xiàn)在可以添加新專輯代碼的功能。這分為兩個部分:
展示表單給用戶用來提供細節(jié)
我們用 Zend\Form
來處理這些。Zend\Form
控件管理表單和處理表單驗證,添加一個 Zend\InputFilter
到 Album
實體。開始寫我們的新類 Album\Form\AlbumForm
,這個類繼承自 Zend\Form\Form
。在 module/Album/src/Album/Form
目錄下新建一個 AlbumForm.php
文件,內容如下:
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
的構造函數(shù)中,我們需要做一些事情。首先我們要設置表單的名字,調用父類構造函數(shù)。接著我們創(chuàng)建四個表單元素:id,title,artist,以及提交按鈕。對每一項,我們都要設置各種各樣的屬性和設置,包括要顯示的標簽。
HTML-Forms 可以使用
POST
和GET
來發(fā)送。ZF2s 默認使用POST
,因此你不必顯式的設置這個選項。如果你想改成GET
,你所做的就是需要在構造函數(shù)中指定。
$this->setAttribute('method', 'GET');
我們需要為表單設置驗證。在 Zend Framework 2,驗證通過使用輸入過濾器處理,這個過濾器可以是獨立的或者可以在類中定義。它繼承自 InputFilterAwareInterface
接口類,就像一個模型實體。在本例中,將輸入過濾器添加到 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()
。我們需要實現(xiàn) getInputFilter()
方法,而 setInputFilter()
只要簡單的拋一個異常就行了。
在 getInputFilter()
中,實例化一個 InputFilter
,然后添加我們想要的輸入框。為每個屬性對應添加過濾和驗證。例如為 id
字段添加整型過濾器,為文本元素添加兩個過濾器,StripTags
和 StringTrim
,用來移除不想要的 HTML 和不必要的空白字符。還要為這些屬性添加 StringLength
,確保不會輸入太多的字符,以便存入數(shù)據(jù)庫。
現(xià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
到使用列表之后,我們實現(xiàn) addAction()
??匆豢?addAction()
的內部細節(jié)吧:
$form = new AlbumForm();
$form->get('submit')->setValue('Add');
實例化 AlbumForm
然后設置提交按鈕的標簽為 Add。在編輯專輯會使用到不同的標簽,就可以復用代碼。
$request = $this->getRequest();
if ($request->isPost()) {
$album = new Album();
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
如果 Request
對象的 isPost()
方法返回 true,表明表單已經(jīng)被提交了。從專輯實例設置表單的輸入過濾器,然后我們將報文數(shù)據(jù)設置到表單中,使用表單對象的 isValid()
成員函數(shù)來檢查數(shù)據(jù)是否有效。
$album->exchangeArray($form->getData());
$this->getAlbumTable()->saveAlbum($album);
如果表單是有效的,就從表單中獲取數(shù)據(jù),使用 saveAlbum()
存儲到模型中。
// Redirect to list of albums
return $this->redirect()->toRoute('album');
在保存新記錄之后,使用重定向控制器插件重定向到專輯的列表。
return array('form' => $form);
最終,返回我們想指定給視圖的變量。在本例中,僅僅是表單對象。注意 Zend Framework 2 也運行返回變量的數(shù)組,然后指定給視圖,這將會在場景后邊創(chuàng)建一個 ViewModel
。可以少輸入點字。
現(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();
我們先展示一個標題,然后再渲染表單。Zend 框架提供一些視圖輔助函數(shù),可以十分簡單地完成上訴要求。form()
輔助函數(shù)有一個 openTag()
和 closeTag()
方法,用來控制表單的打開和關閉。對每一個元素的標簽,可以使用 formRow()
,但是兩個元素太單一了,還要使用 formHidden()
和 formSubmit()
。
或者,渲染表單的過程可以綁定到視圖輔助方法 formCollection
上。例如,在上面的視圖腳本替代所有的表單渲染的輸出語句是:
echo $this->formCollection($form);
注意:你仍然需要使用 openTag
和 closeTag
方法來控制表單。上面的代碼,你可以替代其他輸入語句,調用 formCollection
。
這將會對表單結構進行遍歷,對每個元素調用合適的標簽,元素和視圖輔助的錯誤提示,你通過打開和關閉表單標簽包裝 formCollection($form)。
現(xiàn)有應該使用程序主頁上的 Add new album 鏈接來增加一條新的 album 記錄。
編輯專輯和添加一個專輯的代碼幾乎是相同,所以代碼都很簡單。這次在 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,
);
}
//...
代碼看地來很簡單。讓我們看看與添加 album 之間的不同。首先查找配置 route 中 id
,然后加載對應的專輯,代碼如下:
$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
是一個控制器插件,提供一個簡便的方式來檢索匹配的路由。在 module.config.php
,我們創(chuàng)建在模塊中的 route,使用它來進行檢索 id
。如果 id
是零,就會重定向到添加動作,否則,我們繼續(xù)從數(shù)據(jù)庫中獲取專輯實體。
必須檢查,確保指定 id
的專輯可以被找到。如果不行,數(shù)據(jù)訪問方法將會拋出異常。捕獲該異常并重新輸入用戶索引頁面。
$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit');
表單的 bind()
方法附著于模型。有如下兩個方式:
當顯示表單時,每個元素的初始值都從模型中提取。
isValid()
成功驗證后,表單中的數(shù)據(jù)推送回模型中。這些操作通過復合對象完成的。有許多的復合對象,但是只會使用 Zend\Stdlib\Hydrator\ArraySerializable 作為默認復合對象,這個復合對象在模型指定了兩個方法:getArrayCopy()
和 exchangeArray()
。我們早已在 Album 實體中寫好了 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);
}
// ...
復合對象使用 bind()
的結果是,我們不用往 $album
填充表單的數(shù)據(jù),因為已經(jīng)自動填充好了,只要調用 mappers 的 saveAlbum()
來保存修改到數(shù)據(jù)庫。
視圖模板,edit.phtml
,添加一個專輯的如下所示:
<?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 的標題和設置表單的動作到 edit 的動作。
現(xiàn)在可以編輯專輯了。
為完善我們的程序,我們需要添加刪除操作。列表中每一個專輯都有一個刪除鏈接,使用最原始點擊方式來對應刪除記錄。這或許很糟糕,記住使用 HTTP 的規(guī)范,執(zhí)行一個不可撤銷的動作,應該使用 POST 而不是使用 GET。
在用戶點擊刪除時,我們要顯示一個確認窗口,在用戶點擊 yes 后,就會進行刪除。如果表單并不重要,就將代碼直接寫入視圖腳本中(畢竟,Zend\Form
是可選?。?。
在 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,使用請求對象的 isPost()
來決定顯示確認頁面或者直接刪除專輯。使用表對象的 deleteAlbum()
方法刪除記錄,然后重定向回到專輯列表。如果不是 POST
請求,我們就會取回正確的數(shù)據(jù)庫記錄,然后連同 id
返回給視圖。
視圖腳本的簡單表單:
<?
// 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>
在這個腳本中,我們展示一個帶有 Yes 和 No 按鈕的確認信息。如果用戶點擊 Yes 我們就會執(zhí)行刪除操作。
最后一點。此刻,主頁 http://zf2-tutorial.localhost/
并沒有顯示專輯列表。
這是由于在 Application
模塊中的 module.config.php
route 的設置。為了改變設置,打開 module/Application/config/module.config.php
找到主頁的 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)在你有一個可以運行的程序了。
更多建議: