Zend Framework的表单、装饰器和验证:我是否应该回归纯HTML?

20

我目前正在开发一个非常大的应用程序,其中包含很多表单。

到目前为止,我一直在手写表单并编写自己的验证逻辑,但我决定使用Zend_Form及其内置的验证例程。

然而,我不断遇到越来越多关于Zend_Form_Decorator缺乏灵活性的问题。像向单个输入元素添加额外按钮这样的简单任务变得难以完成。

现在,我已经达到了一个严重考虑完全放弃Zend_Form_Element + Zend_Form_Decorator方法的地步,但我不想失去出色的验证选项。

基本上,我想要两全其美:

  • 以最终用户看到的方式编写表单:使用普通HTML
  • 轻松添加服务器端验证以验证表单字段,而不会破坏太多ZF标准行为

我考虑的一个可能的解决方案是在服务器端和视图中都编写表单。这将使我能够轻松验证自己的表单,但(在我看来相当大的)缺点是每个表单都必须定义两次,这感觉很不对。

有没有关于这个的指南?你们中有没有人经历过同样的问题,如果有,你们是怎么解决这些问题的?

我非常想听听你们的观点。

10个回答

21

我也认为默认的装饰器非常麻烦。我理解为什么它们会是这样,但我认为“不便因素”被严重低估了。

无论如何,我建议您在表单中使用ViewScripts。请注意,这些不同于视图(Views)-- 相反,ViewScripts在您的表单类中显式引用,起到了某种“子视图”的作用,使您能够控制每个元素的布局。以前很难找到如何使用ViewScripts的示例,但我会尝试提供一些有用的东西。

首先,在您的表单类中覆盖loadDefaultDecorators:

public function loadDefaultDecorators() {
     $this->setDecorators(
         array(
             array('ViewScript', 
                 array('viewScript' => 'foo/bar.phtml')
             )
          )
      );        
} 

这将引用名为bar.phtml的ViewScript,位于/views/scripts/foo。 请注意上面“ViewScript”和“viewScript”的大小写差异。

接下来,您需要调整应用于每个元素的修饰器,以确保其显示,但不带有烦人的dt/dd包装器。例如:

$baz = new Zend_Form_Element_Text('bazInput');
$baz->setDecorators(array('ViewHelper','Errors')); 

最后,您需要构建您的ViewScript,例如:

<form method="post" action="<?php echo $this-element->getAction() ?>">
    <table>
        <tr>
            <td><label for="bazInput">Baz:</label></td>
            <td><?php echo $this->element->bazInput ?></td>
        </tr>
    </table>
    <input type="submit" value="Submit Form" />
</form>

显然,这只是一个非常简单的例子,但它说明了如何引用表单元素和表单操作。

然后在您的视图中,像往常一样引用并输出您的表单。这样您就可以更好地控制表单布局--包括轻松添加Javascript。

我认为这种方法解决了您的两个要求:您可以使用纯HTML构建表单,并仍然利用Zend Form验证机制。


感谢您的回答;尽管我已经知道viewScripts,但这似乎是最适合满足我所有需求的最佳解决方案。 - Aron Rotteveel

9
我在过去的10个月中尽可能使用了许多Zend组件,在一个大型项目中,但Zend_Form是最大的痛点。表单渲染速度慢,难以美化。更不用说子表单了。我看到了一篇有趣的文章 "scaling zend_form",但似乎并没有对渲染速度有太大帮助 :(
我正在考虑在视图中使用纯HTML制作所有表单,并仅使用Zend_Form进行验证和过滤(不进行渲染)。要么就完全使用Zend_Validate和Zend_Filter,而不涉及表单方面。
工具只有在帮助你的时候才是工具,否则它只会成为一个阻碍。

3
这恰好是我的经历。谢谢你确认这可能不仅仅是因为我的愚蠢才让这个工具很难使用 :) - Aron Rotteveel
1
自从那个回答以来已经过去了3年,你仍然持有这个想法吗?如果是的话,我和你有相同的想法。 - jonathancardoso
是的,我仍然有这种感觉。我听说 ZF 2.0(即将推出)将重新设计表单,所以我很期待尝试一下。与此同时,我仍在使用 Zend_Form,并尝试只使用 CSS 进行样式设计,或者我只使用 Zend_Form 进行验证,但实际上我手动构建 HTML。 - lo_fye
同意,我完全讨厌Zend Framework。它臃肿而且试图做所有事情。在我看来,它是PHP框架中的Drupal。它是由一家公司制作的,而不是由热情的个人社区制作的。目前,我的最爱是Kohana。 - wesside

4

以下是我在使用Zend Form的项目中所使用的一些装饰器。我认为这些装饰器非常简单易懂。

$stdRowDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td', 'width' => '200')), array('Label', array('escape' => false, 'class' => 'zfFormLabel', 'tag' => 'td')), array(array('row'=>'HtmlTag'), array('tag'=>'tr')));
$startRowDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td')), array('Label', array('escape' => false, 'tag' => 'td', 'class' => 'zfFormLabel')), array(array('row'=>'HtmlTag'), array('tag'=>'tr', 'openOnly'=>true)));
$startRowOpenOnlyDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td', 'openOnly'=>true)), array('Label', array('escape' => false, 'class' => 'zfFormLabel', 'tag' => 'td')), array(array('row'=>'HtmlTag'), array('tag'=>'tr', 'openOnly'=>true)));
$midRowDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td')),array('Label', array('escape' => false, 'class' => 'zfFormLabel', 'tag' => 'td')));
$midRowCloseOnlyDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td', 'closeOnly'=>'true')),array('Label', array('escape' => false, 'class' => 'zfFormLabel', 'tag' => 'td')));
$midRowCloseOnlyNoLabelDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td', 'closeOnly'=>'true')));
$endRowDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td')), array('Label', array('escape' => false, 'class' => 'zfFormLabel', 'tag' => 'td')), array(array('row'=>'HtmlTag'), array('tag'=>'tr', 'closeOnly'=>'true')));
$endRowCloseOnlyNoLabelDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td', 'closeOnly'=>'true')), array(array('row'=>'HtmlTag'), array('tag'=>'tr', 'closeOnly' => 'true')));
$buttonEndRowDec = array('ViewHelper', 'Description', 'Errors', array(array('data'=>'HtmlTag'), array('tag' => 'td', 'colspan'=>'2', 'align'=>'center')), array(array('row'=>'HtmlTag'), array('tag'=>'tr', 'closeOnly'=>'true')));
$buttonDecorators = array('ViewHelper', array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')), array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')), array(array('row' => 'HtmlTag'), array('tag' => 'tr')), );

4
这是我在Zend_Form方面学到的内容:
让它自己做它的事情,长远来看,它会为你节省大量代码。
代价是你最终需要编写更多的CSS来使它们以你想要的方式显示。记住,几乎任何HTML元素都可以被样式化成任何东西。默认情况下,Zend_Form提供了足够的CSS选择器,使您能够获得所需的特定或广泛的效果。我还没有看到过无法将默认装饰器完全按照我的意愿工作的情况。
当然,我有一个又大又丑的CSS文件,但根据我的经验,无论如何最终都可能会有一个又大又丑的CSS文件。我不用担心应用程序特定的表单编码/验证/过滤/处理等问题,而是通过CSS文件处理一些特别样式化的元素。
如果您决定采用这种方法,请注意确保使用CSS样式重置脚本

2
我已使用Zend Framework一年,但只在我的第一个项目中使用了Zend_Form。尝试花费15分钟来定位我的“或取消”链接后,我放弃了使用Zend_Form。不过,我确实喜欢它的集成功能。
现在我使用普通的HTML表单,并在模型中使用Zend_Filter_Input(大多数情况下是Zend_Db_Table,但在我的最后一个项目中必须添加服务层)。
以下是控制器代码示例,使用ZFI在模型中。错误处理和常见验证方法在Zend_Db_Table的子类中,我的类扩展它。
视图助手格式化错误消息数组。
if ($this->_request->isPost()) {
    $data = $this->_request->getPost();
    $event = new Default_Model_DbTable_Event();         
    $event->createRow($data)->save();

    if ($event->hasErrors()) {
        $this->view->errors = $event->getErrorMessages();
        $this->view->event = $data;
    } else {
        $this->_redirect('events');
    }
}

谢谢您的回答。您能详细说明一下如何验证表单并向客户显示错误信息吗? - Aron Rotteveel
我在模型中创建了一个ZFI实例,并且还有一个属性来保存验证失败时的错误。下面我将发布控制器代码片段。 - Ekerete
我已经合并了我的两篇帖子,所以我的代码片段在上面。将删除重复条目。 - Ekerete

2
我赞同lo_fye提到的内容。在我的经验中,Zend Forms很笨重,没有很好地考虑过。
我发现最简单和最灵活的解决方案是创建两个表单文件:1. 表单类,用于初始化表单元素,然后是一个视图脚本来显示表单元素。在我的表单类中,我关闭了所有装饰器,除了实际的表单元素。例如: ->removeDecorator('HtmlTag') ->removeDecorator('DtDdWrapper') ->removeDecorator('Label') ->removeDecorator('Errors');
然后,在初始化表单元素的构造函数末尾,传递视图脚本:$this->setDecorators(array(array('ViewScript', array('viewScript' => 'path/to/script_form.phtml'))));
在视图脚本中,我按照自己的喜好格式化表单,然后在输入字段(例如)所在的位置只显示该元素:$this->element->form_element_id。请注意,我删除了错误装饰器,只获取错误堆栈并按照自己的想法显示它。
缺点是您需要为每个表单创建一个视图脚本或者自己构建某种可重复使用的系统,所以需要额外的工作。但最终,我认为这样更容易设计符合您布局的表单。

2
如果您想要使用验证器和过滤器而不使用表单:Zend_Filter_Input

22.5. Zend_Filter_Input

Zend_Filter_Input 提供了一种声明式接口,用于关联多个过滤器和验证器,将它们应用于数据集合,并在通过过滤器和验证器处理后检索输入值。默认情况下,返回的值以转义格式呈现,以确保HTML输出的安全性。

虽然 - 我个人建议学习制作自定义装饰器和视图助手。Zend_Form非常强大,我从来没有遇到过无法定位/装饰事物的问题。即使是使用jQuery构建复杂的权限表格并自动生成列和行 - 我也发现Zend_Form界面可以节省时间。如果您有关于如何处理装饰某些东西的具体问题,我很乐意帮忙。开一个新问题并在这里进行评论或其他操作...

1
你可以使用Zend Form并自己生成HTML :) 你只需要跟踪更改并在HTML和ZF中保持表单元素相同 :)

跟踪这些变化并在两个地方进行维护是Zend表单试图消除的问题!这值得学习(希望我还在学习曲线上)。 - Matt
完全同意!我只是在回答问题! :) - Tomáš Fejfar

1

简短回答:仅使用Zend_Form进行验证和过滤,使用您的普通视图脚本以您想要的形式渲染表单。

长篇回答:我得出结论,不值得使用Zend_Form来生成表单的HTML。至少在我工作的公司内如此。但是为什么?

很简单,我不想创建一个类(或使用一个小技巧)只是为了在我的表单内部的div中“轻松”添加链接。我不想创建多个修饰器来添加可以轻松添加简单HTML的功能,最重要的是,与我们合作的设计师不想编辑一个(或两个或更多)类来完成他们的工作。

我们继续使用Zend_Form,但仅用于验证和过滤我们的表单。而不是用来生成它们。

我知道许多人不会同意我的观点,但这只是一个曾因Zend_Form而失眠的人的意见。


JCM,我认为在这种情况下,有人可以使用Zend_Filter_Input来定义验证/过滤器。如果表单元素只绑定来自HTML,则仅对验证使用zend_form没有意义。 - Rakesh Lamp Stack

0

编辑/评论@Cal Jacobson的顶部答案。

无法编辑,因为更改的字符不足,无法评论,因为声望不足,但是...

$this-element->getAction()

应该是

$this->element->getAction()

我相信原帖的作者知道这一点,但进行纠正可以避免在直接使用答案中的代码时出现错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接