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

时间:2012-01-03 12:27:19

标签: php model-view-controller zend-framework

我在Zend Framework应用程序中创建模块化可重用组件时遇到了问题。在这种情况下,我不是指Zend Framework模块,而是能够拥有可重用的MVC小部件,如果你愿意的话。我遇到的问题可能对我的实施非常特别,但如果有人可以指出我正确的方向,我很乐意把它扔出去重新开始。无论如何,细节和代码将有希望更好地解释事情,即使我正在做的不是最好的方式它应该显示我想要实现的目标:

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

  1. 使用表单处理和扩展(错误)
  2. 创建一个基本控制器
  3. 相关控制器中的重复表单处理代码(更糟糕!)
  4. 干净的方式让我觉得创建一个新的Controller来处理邮件列表表单数据,使用View Helper轻松地将表单和相关标记输出到所需的页面,然后重定向回发生注册的页面表格处理完毕后。但是,我想使用Zend_Form提供的表单验证,这意味着如果验证失败但是在同一个请求中,我需要以某种方式将表单对象传递回视图助手。我目前正在通过将其设置为视图上的变量然后转发回上一页而不是重定向来执行此操作,这是正常的(ish)。如果验证没问题,那么我宁愿使用重定向回原始页面。我无法做到这一点,因为我想将消息传递回组件有关注册状态。通常我会使用FlashMessenger Action Helper,在这种情况下我可以命名它,因此消息不会与其他页面数据冲突,但我无法从View Helper中访问它。所以目前我也在这种情况下转发。如果用户刷新页面并保持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-&gt; 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的实例并不理想,但我更愿意尽可能地封装。

    如果我有一个表单对象,其中验证失败,我可以将其传递回帮助程序以输出错误消息。如果我有一些要呈现的消息,我也可以将它们传递给帮助者。

    在我的视图脚本中,我正在使用这样的帮助:

    <?=$this->mailingList($this->mailingListForm, $this->mailingListMessages);?>
    

    如果MailingListController在视图上都没有设置mailingListForm或mailingListMessages,它将输出一个没有消息的新表单。

    非常感谢任何帮助!

3 个答案:

答案 0 :(得分:2)

使用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.phtml

<?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档案 mailing-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;
    });
});

答案 1 :(得分:0)

我认为你这样做的方式与我的做法非常接近。如果您不想在页面中显示Zend_Form错误消息,那么您所做的就是:

  • 视图助手只显示表单(不需要将表单对象或消息作为参数)
  • 表单现在提交给您的其他控制器
  • 邮件列表控制器在成功
  • 时重定向(而不是转发)回到返回URL
  • 邮件列表控制器会自行重新显示表单,以及失败时的错误

这使得一切变得更加简单,唯一的问题是如果存在任何验证错误,那么用户就会丢失其上下文并获得一个包含表单的普通旧页面而不是它们所在的位置。然后,您可以通过更改要提交的表单来解决此问题(现在或以后)。而是Ajax,并通过渲染错误。 JS。但这将是相当多的工作。

答案 2 :(得分:0)

好的,我想出了一个让我感到更快乐的解决方案并解决了我遇到的一些问题。希望这可以帮助那些面临类似问题的人。现在唯一的缺点是我在View Helper中引用了Model。我不知道松散耦合,但我之前已经多次看过这种情况,甚至在ZF docs中建议避免使用'action'视图助手(这将创建一个新的MVC分派循环)。总的来说,我认为DRYness和封装是值得的,也可能还有一些其他合适的术语。

为了能够从我的MailingListController使用重定向但是保留来自我的模型的消息以及任何表单验证错误,我需要将它们存储在会话中。对于消息,我通常使用FlashMessenger动作帮助器,但是在View Helper中获取它并不是最好的做法,它不会处理我的表单错误,所有它真正做的就是将内容保存到会话中,无论如何它都是不必要的。我可以在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-&gt; view-&gt; 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();?>

我意识到我遇到的很多问题都归结为一系列非常具体的情况,但希望这可以帮助其他人以某种方式解决问题!

干杯, 亚历