菜菜鸟Zend Framework 2 不完全学习涂鸦(九)-- 表单和actions

原创
2013/08/05 22:57
阅读数 3K

表单和actions

一、添加新的唱片(Albums


我们现在可以为添加新唱片编写代码了,这里有两个功能

为用户提供一个表单输入详细内容

处理提交的表单并存储到数据库

我们使用 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',
            ),
        ));
    }
}

在 AlbumForm 表单内部结构,我们做了几件事情。首先,通过调用父类的构造函数来设定了表单的名称。然后我们在这个例子中设定了表单的方法:post。最后我们创建了四个表单元素:编号,标题,专辑名称和提交按钮。对于每个元素我们设定了属性和选项,包括用于显示的标签。


我们也需要对于这个表单的校验。在 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;
    }
}

InputFilterAwareInterface 定义了两个方法:setInputFilter() 和 getInputFilter() 。我们只需要实现 getInputFilter() 所以我们就简单的在 setInputFilter() 中抛出一个异常。


在 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);
    }
//...

在添加唱片表单(AlbumForm)到用户列表之后,我们实现了 addAction()。让我们稍微的看一下 addAction() 代码


$form = new AlbumForm();
$form->get('submit')->setValue('Add');

我们实例化唱片表单(AlbumForm)并设定提交按钮的标签为“Add”。这里之所以这么做是为了在“编辑唱片”时我们需要重用表单(re-use)使用不同的标签。


$request = $this->getRequest();
if ($request->isPost()) {
    $album = new Album();
    $form->setInputFilter($album->getInputFilter());
    $form->setData($request->getPost());
    if ($form->isValid()) {

如果请求对象(Request object)的 isPost() 方法为真,那么就是说表单已经提交了,我们从唱片实例(album instance)中获得表单的输入过滤。然后我们使用来自于表单的 isValid() 成员函数,来检测表单传过来的数据是否有效。


$album->exchangeArray($form->getData());
$this->getAlbumTable()->saveAlbum($album);

如果表单数据有效,我们抓取表单数据并使用 saveAlbum() 方法保存至模型。


// Redirect to list of albums
return $this->redirect()->toRoute('album');

在我们保存了新唱片的数据后,我们使用 Redirect 控制器(Controller)插件重定向回唱片列表页面。


return array('form' => $form);

最后,我们返回了我们想传递给视图(view )的变量。在这里案例中,就只有表单对象。注意,ZF2 也允许你简单的返回一个包含需要传递给视图(view)的变量数组,ZF2 会为你在幕后创建 ViewModel,这样可以少打一些字。


现在我们需要在 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);

注意:你依然需要调用表单的 openTag 和 closeTag 方法。你用上面的 formCollection 来替换其它的输出语句(echo statements)

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),这个 id 存在于匹配到的路由并且使用这个 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 是一个控制器(Controller)插件,提供了方便的方法来检索匹配的路由中的参数。我们使用 params 来检索我们在 module.config.php 文件中创建的路由中的 id。如果 id 是 0,我们就重定向到添加唱片页面,否则我们继续从数据库中获得唱片实体 信息。

我们必须检查确认指定 id 的唱片实际上可以被找到。如果找不到,数据访问方法抛出一个异常。我们捕获这个异常并重新将用户路由(re-route)到首页。

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

表单的 bind() 方法,在表单上附加了模型。有两种使用方法

当现实表单时,提取来自模型的值并初始化每个元素

在成功使用 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);
    }
// ...

伴随 hydrator 使用 bind() 的结果,我们不需要填充表单数据返回给 $album 因为那已经做好了。所以我们只需要调用映射的 saveAlbum() 将修改后的信息储存回数据库。

视图(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();

唯一的区别是使用了“Edit Album”的标题和指定表单的action指向“edit”。

你现在可以编辑唱片了。

三、删除唱片

为了完善我们的应用程序,我们需要添加删除功能。我们在唱片列表中的每个唱片旁边都有一个删除的链接,当我们要删除一个唱片时可以点击它。这将会报错。回忆一下我们的 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)
        );
    }
//...

老样子,我们从匹配到的路由中获得编号(id)并且检查请求对象(request object)的 isPost() 来确定是显示确认表单也还是删除唱片。我们使用 deleteAlbum() 方法来删除数据表对象中的数据行,然后重定向回唱片列表页面。如果请求不是 POST,我们检索适当的数据库记录并且连同 id 一起分配给视图(view)。

视图(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',
        ),
    ),
),

修改 controller,将其从 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',
        ),
    ),
),

好了,你现在拥有了一个完全可以工作的应用程序了!
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
4 收藏
1
分享
返回顶部
顶部