我一直有问题在我的Zend框架应用程序创建模块化可重用组件。在这种情况下,我不是指Zend框架模块,而是有一个可重用的MVC小部件的东西,如果你喜欢的能力。我所遇到的问题可能是我的实现所特有的,但如果有人能给我指出正确的方向,我很乐意把它扔掉,重新开始。无论如何,细节和代码将有望更好地解释事情,即使我所做的不是最好的方式,它也应该显示我想要实现的目标:
一个简单的例子是邮件列表注册表单。我想将此包含在使用不同控制器的网站的几个页面上,这在如何处理数据和返回相关消息方面提出了一些问题。我不想做以下任何一种,因为它们真的很难闻:
- 创建一个包含表单处理的基本控制器,并扩展(Bad)
- 在相关控制器中重复表单处理代码(更糟糕!)
我觉得最干净的方法是创建一个新的Controller来处理邮件列表表单数据,使用View Helper轻松地将表单和相关标记输出到所需的页面,然后在处理完表单后重定向到发生注册的页面。然而,我想使用Zend_Form提供的表单验证,这意味着如果验证失败,我需要将表单对象以某种方式传递回视图助手,但在相同的请求中。我目前正在通过将其设置为视图上的变量,然后转发回上一页,而不是重定向,这是ok的。如果验证是ok的,那么我更愿意使用重定向回原始页面。我有麻烦这样做,虽然我想传递消息回到有关注册状态的组件。通常我会使用FlashMessenger动作助手,我可以命名它在这种情况下,所以消息不会与其他页面数据冲突,但我不能从视图助手内访问它。所以现在我在这个例子中也转发了。我更喜欢重定向,以防止表单重新提交,如果用户刷新页面,并保持URL干净。我意识到我本质上想要在页面中有一个迷你MVC分派过程我想这就是动作堆栈的作用?我真的不太了解这一点,任何指示将非常感激。下面是我当前的代码:
控制器:
<?php
class MailingListController extends Zend_Controller_Action {
public function insertAction() {
$request = $this->getRequest();
$returnTo = $request->getParam('return_to');
if(!$request->isPost() || (!isset($returnTo) || empty($returnTo))) {
$this->_redirect('/');
}
$mailingList = new Model_MailingList();
$form = new Form_MailingList();
$returnTo = explode('/', $returnTo);
if($form->isValid($_POST)) {
$emailAddress = $form->getValue('email_address');
$mailingList->addEmailAddress($emailAddress);
$this->view->mailingListMessages = $mailingList->getMessages();
$this->view->mailingListForm = "";
}
else {
$this->view->mailingListForm = $form;
}
$this->_forward($returnTo[2], $returnTo[1], $returnTo[0]);
}
}
return_to是一个包含当前URI(模块/控制器/操作)的字符串,它是在View Helper中生成的。我更喜欢在$form->isValid($_POST)块中重定向。
视图助手:
<?php
class Zend_View_Helper_MailingList extends Zend_View_Helper_Abstract {
public function mailingList($form, $messages = "") {
if(!isset($form)) {
$request = Zend_Controller_Front::getInstance()->getRequest();
$currentPage = $request->getModuleName() . '/' . $request->getControllerName() . '/' . $request->getActionName();
$form = new Form_MailingList();
$form->setAction('/mailing-list/insert');
$form->setCurrentPage($currentPage);
}
$html = '<div class="mailingList"><h2>Join Our Mailing List</h2>' . $form;
$html .= $messages;
$html .= '</div>';
return $html;
}
}
在View Helper中获取Front Controller的实例并不理想,但我更喜欢尽可能地封装。
如果我有一个验证失败的表单对象,我可以将它传递回helper以输出错误消息。如果我有一些消息要渲染,我也可以把它们传递给帮助器。
在我的视图脚本中,我使用这样的帮助:
<?=$this->mailingList($this->mailingListForm, $this->mailingListMessages);?>
如果MailingListController在视图上既没有设置mailingListForm也没有设置mailingListMessages,它将输出一个没有消息的新表单。
任何帮助都非常感谢!
使用ajax似乎是一种最佳方式。View Action Helper仅用于首次加载邮件表单。
控制器class MailingListController extends Zend_Controller_Action {
public function insertAction() {
$request = $this->getRequest();
$form = new Form_MailingList();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
$mailingList = new Model_MailingList();
$emailAddress = $form->getValue('email_address');
$mailingList->addEmailAddress($emailAddress);
$form = $mailingList->getMessages();
}
}
$this->view->form = $form;
}
}
查看脚本 insert. php
<?php echo $this->form; ?>
形式类
class Form_MailingList extends Zend_Form {
public function init() {
//among other things
$this->setAttrib('id', 'mailing-list-form');
$this->setAction('/mailing-list/insert');
}
}
视图助手
class Zend_View_Helper_MailingList extends Zend_View_Helper_Abstract {
public function mailingList() {
$this->view->headScript()->appendFile('/js/mailing-list.js');
return '<div id="mailing-list-wrap">' . $this->view->action('insert', 'mailing-list') . '</div>';
}
}
JS文件 mail-list . JS
$(document).ready(function() {
$('#mailing-list-form').submit(function() {
var formAction = $(this).attr('action');
var formData = $(this).serialize();
$.post(formAction, formData, function(data) {
//response going in form's parent container
$(this).parent().html(data);
});
return false;
});
});
我认为你做这件事的方法和我做的很接近。如果您不考虑在页面中显示Zend_Form错误消息的需求,那么您应该做的是:
- 视图助手只显示表单(它不需要将表单对象或消息作为参数)
- 表单像现在一样提交给另一个控制器
- 邮件列表控制器在成功时重定向(而不是转发)回返回URL
- 邮件列表控制器自己重新显示表单,并在失败时显示错误
这使一切变得简单得多,唯一的问题是,如果有任何验证错误,那么用户就会失去他们的上下文,并得到一个普通的旧页面,其中包含表单,而不是他们原来的位置。然后,您可以通过更改要通过提交的表单来解决这个问题(现在或以后)。而不是Ajax,并通过。JS。但是这将是相当多的工作。
好了,我想到了一个让我更开心的解决方案,并且解决了我面临的一些问题。希望这能帮助那些面临类似问题的人。现在唯一的缺点是我在视图助手中引用模型。我知道不是松耦合,但我以前见过几次这样做,甚至在ZF文档中推荐它作为避免使用"action"视图助手的一种方式(这将创建一个新的MVC调度循环)。总的来说,我认为dry和封装是值得的,可能还有一些其他合适的术语。
为了能够从我的MailingListController使用重定向返回,但维护来自我的模型和任何表单验证错误的消息,我需要将它们存储在会话中。对于消息,我通常使用FlashMessenger动作助手,但作为在视图助手中获得这个不是最佳实践,它不会处理我的表单错误,所有它真正做的是将东西保存到会话,无论如何,这是不必要的。我可以在Model_MailingList中实现我自己的会话存储,我也可以使用它来处理表单错误。然后,我可以在重定向之后用错误重新填充表单,并打印出任何相关消息。总之,下面是代码:
控制器:
<?php
class MailingListController extends Zend_Controller_Action {
public function insertAction() {
$request = $this->getRequest();
$returnTo = $request->getParam('return_to');
if(!$request->isPost() || (!isset($returnTo) || empty($returnTo))) {
$this->_redirect('/');
}
$mailingList = new Model_MailingList();
$form = new Form_MailingList();
if($form->isValid($_POST)) {
$emailAddress = $form->getValue('email_address');
$mailingList->addEmailAddress($emailAddress);
}
else {
$mailingList->setFormErrors($form->getMessages());
}
$redirect = rtrim($request->getBaseUrl(), '/') . $returnTo;
$this->_redirect($redirect);
}
}
我已经添加了一个方法到我的Model_MailingList类;setFormErrors($errors),如果验证失败,我从表单传递错误消息。这将错误数组保存到会话中。
我通常使用具有addMessage和getMessages方法的基模型类。它们只是访问受保护的消息数组。在我的Model_MailingList中,我重写了这些方法,将消息存储在会话中。在addEmailAddress($emailAddress)方法中,我已经调用addMessage来说明是否将电子邮件地址插入到数据库中已经成功。
模型:
<?php
class Model_MailingList extends Thinkjam_Model_DbAbstract {
private $_session;
public function __construct() {
$this->_session = new Zend_Session_Namespace(__CLASS__);
}
public function setFormErrors($errors) {
$this->_session->formErrors = $errors;
}
public function getFormErrors() {
$errors = array();
if(isset($this->_session->formErrors)) {
$errors = $this->_session->formErrors;
unset($this->_session->formErrors);
}
return $errors;
}
// override addMessage and getMessages
protected function addMessage($message) {
if(!isset($this->_session->messages)) {
$this->_session->messages = array();
}
$this->_session->messages[] = $message;
}
public function getMessages() {
if(isset($this->_session->messages)) {
$this->_messages = $this->_session->messages;
unset($this->_session->messages);
}
return $this->_messages;
}
…
public function addEmailAddress($emailAddress) {
...
// I call this if db insert was successful:
$this->addMessage("Thank you. You have been successfully added to the mailing list.")
}
}
我现在不需要传递任何参数给视图助手,因为它可以直接从模型查询它的状态。$this->view->messenger只是另一个视图助手,它将数组转换为无序列表。
视图助手:
<?php
class Zend_View_Helper_MailingList extends Zend_View_Helper_Abstract {
private $_mailingList;
public function MailingList() {
$this->_mailingList = new Model_MailingList();
return $this;
}
public function getForm() {
$request = Zend_Controller_Front::getInstance()->getRequest();
$currentPage = '/' . $request->getModuleName() . '/' . $request->getControllerName() . '/' . $request->getActionName();
$form = new Form_MailingList();
$form->setAction('/mailing-list/insert');
$form->setCurrentPage($currentPage);
$form->setErrors($this->_mailingList->getFormErrors());
$html = '<div class="mailingList"><h2>Join Our Mailing List</h2>' . $form;
$html .= $this->view->messenger($this->_mailingList->getMessages());
$html .= '</div>';
return $html;
}
}
然后在Form_MailingList类中,我只需要添加一个额外的方法来重新填充错误消息。虽然getMessages()是Zend_Form的一个方法,但似乎没有任何相应的setMessages()。但是,您可以在Zend_Form_Element上执行此操作,因此我向Form_MailingList类添加了以下函数:
形式:
<?php
class Form_MailingList extends Thinkjam_Form_Abstract {
...
public function setErrors(array $errors) {
foreach($errors as $key => $value) {
$this->getElement($key)->setErrors($value);
}
}
}
我现在可以使用MailingList视图帮助器在网站的任何页面上添加注册表单:
<?=$this->MailingList()->getForm();?>
我意识到我所面临的很多问题都归结于一组非常具体的情况,但希望这可以在某种程度上帮助其他人!
欢呼,亚历克斯