我们现在可以为添加新唱片编写代码了,这里有两个功能
为用户提供一个表单输入详细内容
处理提交的表单并存储到数据库
我们使用 Zend\Form 来制作表单。Zend\Form 组件管理表单和表单验证,我们为唱片实体添加一个 Zend\InputFilter。我们从创建一个新的类 Album\Form\AlbumForm 来定义我们的表单开始,这个类扩展了 Zend\Form\Form 。在 module/Album/src/Album/Form 目录下新建一个 AlbumForm.php 文件
<?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->setAttribute('method', 'post'); $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', ), )); } }
我们也需要对于这个表单的校验。在 ZF2 里要实现校验功能需要使用输入过滤,这个输入过滤要么是独立的,要么在 InputFilterAwareInterface 接口中的任何类中定义,例如,一个模型实体。在我们这个案例中,我们在唱片类中添加输入过滤。以下代码保存在 module/Album/src/Album/Model 目录下的 Album.php 文件内
<?php namespace Album\Model; // Add these import statements use Zend\InputFilter\Factory as InputFactory; 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(); $factory = new InputFactory(); $inputFilter->add($factory->createInput(array( 'name' => 'id', 'required' => true, 'filters' => array( array('name' => 'Int'), ), ))); $inputFilter->add($factory->createInput(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($factory->createInput(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; } }
在 getInputFilter() 内部,我们实例化一个 InputFilter 然后添加我么需要的输入元素。我们为每个我们需要过滤或者校验的输入元素都设定了属性。对于编号(id)域我们添加了 Int 过滤,因为我们只需要整数(Integer)。对于文本元素,我们添加了两个过滤:StripTags 和 StringTrim 来去除不想要的HTML标记和不必的空格。同时我们也设定了文本元素时必须的并且添加了 StringLength 校验来确保用户不会输入过多的字符,从而可以使我们将其保存到数据库中。
我们现在需要显示这个表单并提交它,这是通过在唱片控制器(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); } //...
$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);
现在我们需要在 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 = $this->form; $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();
再说明一下,我们首先显示了标题然后我们渲染了表单。ZF 提供了一些辅助函数来简化一点操作。form() 视图(view)辅助函数有 openTag() 和 closeTag() 方法,我们使用这两个方法来打开和关闭表单。然后标签中的每个元素,我们可以使用 formRow() ,但是对于两个单独的元素,我们使用 formHidden() 和 formSubmit()。
此外,表单的渲染过程可以简化为使用绑定的 formCollection 视图助手。举例,在上面的视图代码(view script)中用以下代码替换所有的表单渲染(form-rendering)echo元素
就是将
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->formCollection($form);
formCollection 会遍历表单的结构,适当的为每个元素调用标签,元素和视图(view)辅助函数,但是不还是需要使用打开(open)和关闭(close)标签来包裹住 formCollection($form)。这个辅助函数会减低你的视图(view)代码的复杂性,在默认 HTML 渲染表单的情况下是可以接受的。
你现在可以使用在应用程序首页上的 “Add new 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, ); } //...
$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' )); }
我们必须检查确认指定 id 的唱片实际上可以被找到。如果找不到,数据访问方法抛出一个异常。我们捕获这个异常并重新将用户路由(re-route)到首页。
$form = new AlbumForm(); $form->bind($album); $form->get('submit')->setAttribute('value', 'Edit');
当现实表单时,提取来自模型的值并初始化每个元素
在成功使用 isValid() 校验后,表单数据回推给模型(put back into the model)
这些操作使用了 hydrator 对象。有许多的 hydrator ,但是默认的一个是 Zend\Stdlib\Hydrator\ArraySerializable 它希望在模型中找到两个方法:getArrayCopy() 和 exchangeArray()。在我们的唱片实体中我们已经写过了 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); } // ...
视图(view)模板,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();
你现在可以编辑唱片了。
为了完善我们的应用程序,我们需要添加删除功能。我们在唱片列表中的每个唱片旁边都有一个删除的链接,当我们要删除一个唱片时可以点击它。这将会报错。回忆一下我们的 HTTP 细则,我们回想起来你不能使用 GET 来进行不可逆的操作,要使用 POST 来代替。
当用户点击删除是我们将显示一个确认表单,如果用户点击“yes”,我们将删除这个唱片。由于这个表单不是重要的,我们就直接在我们的视图(view)中编写代码(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) ); } //...
视图(view)代码是一个简单的表单
<?php // 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” 值。
最后一点,目前,首页 http://zf2-tutorial.localhost/ 没有显示唱片列表
这是由于在 module.config.php 文件中设置的 Application 模块路由所引起的。要修改它,打开 module/Application/config/module.config.php 并且找到首页路由
'home' => array( 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'Application\Controller\Index', 'action' => 'index', ), ), ),
'home' => array( 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'Album\Controller\Album', // <-- change here 'action' => 'index', ), ), ),