鍍金池/ 教程/ PHP/ 表單和動(dòng)作
開始構(gòu)建一個(gè)框架應(yīng)用程序
模塊
路由和控制器
表單和動(dòng)作
結(jié)尾
開始使用 Zend Framework 2
樣式和翻譯
數(shù)據(jù)和模型

表單和動(dòng)作

添加新的專輯

我們現(xiàn)在可以添加新專輯代碼的功能。這分為兩個(gè)部分:

  • 展示表單給用戶用來提供細(xì)節(jié)

  • 處理表單提交并存儲(chǔ)到數(shù)據(jù)庫(kù)

我們用 Zend\Form 來處理這些。Zend\Form 控件管理表單和處理表單驗(yàn)證,添加一個(gè) Zend\InputFilterAlbum 實(shí)體。開始寫我們的新類 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 可以使用 POSTGET 來發(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)證通過使用輸入過濾器處理,這個(gè)過濾器可以是獨(dú)立的或者可以在類中定義。它繼承自 InputFilterAwareInterface 接口類,就像一個(gè)模型實(shí)體。在本例中,將輸入過濾器添加到 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)添加過濾和驗(yàn)證。例如為 id 字段添加整型過濾器,為文本元素添加兩個(gè)過濾器,StripTagsStringTrim,用來移除不想要的 HTML 和不必要的空白字符。還要為這些屬性添加 StringLength,確保不會(huì)輸入太多的字符,以便存入數(shù)據(jù)庫(kù)。

現(xiàn)在需要獲取表單進(jìn)行顯示,然后在提交時(shí)進(jìn)行處理。在 AlbumControlleraddAction()

// 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è)置表單的輸入過濾器,然后我們將報(bào)文數(shù)據(jù)設(shè)置到表單中,使用表單對(duì)象的 isValid() 成員函數(shù)來檢查數(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 。可以少輸入點(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() 方法,用來控制表單的打開和關(guān)閉。對(duì)每一個(gè)元素的標(biāo)簽,可以使用 formRow(),但是兩個(gè)元素太單一了,還要使用 formHidden()formSubmit()

http://wiki.jikexueyuan.com/project/zend2-user-guide/images/stylingandtranslations3.png" alt="image" />

或者,渲染表單的過程可以綁定到視圖輔助方法 formCollection 上。例如,在上面的視圖腳本替代所有的表單渲染的輸出語句是:

 echo $this->formCollection($form);

注意:你仍然需要使用 openTagcloseTag 方法來控制表單。上面的代碼,你可以替代其他輸入語句,調(diào)用 formCollection。

這將會(huì)對(duì)表單結(jié)構(gòu)進(jìn)行遍歷,對(duì)每個(gè)元素調(diào)用合適的標(biāo)簽,元素和視圖輔助的錯(cuò)誤提示,你通過打開和關(guān)閉表單標(biāo)簽包裝 formCollection($form)。

現(xiàn)有應(yīng)該使用程序主頁上的 Add new album 鏈接來增加一條新的 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,
        );
    }
//...

代碼看地來很簡(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)便的方式來檢索匹配的路由。在 module.config.php,我們創(chuàng)建在模塊中的 route,使用它來進(jìn)行檢索 id。如果 id 是零,就會(huì)重定向到添加動(dòng)作,否則,我們繼續(xù)從數(shù)據(jù)庫(kù)中獲取專輯實(shí)體。

必須檢查,確保指定 id 的專輯可以被找到。如果不行,數(shù)據(jù)訪問方法將會(huì)拋出異常。捕獲該異常并重新輸入用戶索引頁面。

 $form = new AlbumForm();
 $form->bind($album);
 $form->get('submit')->setAttribute('value', 'Edit');

表單的 bind() 方法附著于模型。有如下兩個(gè)方式:

  • 當(dāng)顯示表單時(shí),每個(gè)元素的初始值都從模型中提取。

  • isValid() 成功驗(yàn)證后,表單中的數(shù)據(jù)推送回模型中。

這些操作通過復(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() 來保存修改到數(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)擊方式來對(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 是可選?。?。

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() 來決定顯示確認(rèn)頁面或者直接刪除專輯。使用表對(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è)帶有 YesNo 按鈕的確認(rèn)信息。如果用戶點(diǎn)擊 Yes 我們就會(huì)執(zhí)行刪除操作。

確保主頁顯示專輯列表

最后一點(diǎn)。此刻,主頁 http://zf2-tutorial.localhost/ 并沒有顯示專輯列表。

這是由于在 Application 模塊中的 module.config.php route 的設(shè)置。為了改變?cè)O(shè)置,打開 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)在你有一個(gè)可以運(yùn)行的程序了。