如何在Zend框架中创建模块化MVC组件


How to create modular MVC components in Zend Framework

我一直有问题在我的Zend框架应用程序创建模块化可重用组件。在这种情况下,我不是指Zend框架模块,而是有一个可重用的MVC小部件的东西,如果你喜欢的能力。我所遇到的问题可能是我的实现所特有的,但如果有人能给我指出正确的方向,我很乐意把它扔掉,重新开始。无论如何,细节和代码将有望更好地解释事情,即使我所做的不是最好的方式,它也应该显示我想要实现的目标:

一个简单的例子是邮件列表注册表单。我想将此包含在使用不同控制器的网站的几个页面上,这在如何处理数据和返回相关消息方面提出了一些问题。我不想做以下任何一种,因为它们真的很难闻:

  1. 创建一个包含表单处理的基本控制器,并扩展(Bad)
  2. 在相关控制器中重复表单处理代码(更糟糕!)

我觉得最干净的方法是创建一个新的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();?>

我意识到我所面临的很多问题都归结于一组非常具体的情况,但希望这可以在某种程度上帮助其他人!

欢呼,亚历克斯