相对“厚”的控制器与Zend_Form一样正常吗? (如果不是,那么如何将它们变薄?)

时间:2011-10-17 03:46:53

标签: php zend-framework zend-form

我有一个看起来像这样的表格:

class Cas_Form_Company extends Zend_Form
{
    /**
     * @param Cas_Model_Company|null $company
     */
    public function __construct(Cas_Model_Company $company = null)
    {
        parent::__construct();

        $id = new Zend_Form_Element_Hidden('id');

        $name = new Zend_Form_Element_Text('name');
        $name->addValidator('stringLength', false, array(2,45));
        $name->addValidator(new Cas_Model_Validate_CompanyUnique());
        $name->setLabel('Name');

        $submit = new Zend_Form_Element_Submit('Submit');

        if ($company)
        {
            $id->setValue($company->GetId());
            $name->setValue($company->GetName());
        }

        $this->addElements(array($id, $name, $submit));
        $this->setMethod('post');
        $this->setAction(Zend_Controller_Front::getInstance()->getBaseUrl() . '/Asset/company');
    }

    public function Commit()
    {
        if (!$this->valid())
        {
            throw new Exception('Company form is not valid.');
        }

        $data = $this->getValues();
        if (empty($data['id']))
        {
            Cas_Model_Gateway_Company::Create($data['name']);
        }
        else
        {
            $company = Cas_Model_Gateway_Company::FindById((int)$data['id']);
            $company->SetName($data['name']);
            Cas_Model_Gateway_Company::Commit($company);
        }
    }
}

现在,这种形式依赖于一个控制器,它看起来像这样:

public function companyAction()
{
    if ($this->getRequest()->isPost())
    {
        if ($this->getRequest()->getParam('submit') == 'Delete')
        {
            Cas_Model_Gateway_Company::Delete(Cas_Model_Gateway_Company::FindById((int)$this->getRequest()->getParam('id')));
            $this->_helper->redirector->setCode(303)->gotoSimple('companies');
        }

        $form = new Cas_Form_Company();
        if ($form->isValid($this->getRequest()->getParams()))
        {
            $form->Commit();
            $this->_helper->redirector->setCode(303)->gotoSimple('index');
        }
        $this->view->form = $form;
    }
    else if ($id = $this->getRequest()->getParam('id'))
    {
        $form = new Cas_Form_Company(Cas_Model_Gateway_Company::FindById($id));
        $this->view->form = $form;
    }
    else
    {
        $this->view->form = new Cas_Form_Company();
    }
    $this->_helper->viewRenderer->setScriptAction('formrender');
}

看起来控制器动作在这里“做得太多”,但我看不到解决这个问题的简单方法。一般来说,我认为表单应该是担心它是添加,编辑还是删除操作的人。但我似乎无法找到一个很好的方法去做。

对于使用Zend_Form的人或我做错了什么,这是正常模式吗?

2 个答案:

答案 0 :(得分:3)

这非常好,您所做的就是向表单发送值并处理响应,重定向并处理视图。

这些东西都很合适,如果你把它们放在别处,那将是一件很麻烦的事情。我不会想到“胖控制器”,因为“需要比其他控制器/操作更多的LOC”,而是“包含业务逻辑”。如果您觉得圈复杂度变得太大,请尝试将您的行动分成更小的行动。

编辑:实际上,我会将$form->Commit();部分重构为类似$repository->create($form->getValues()的内容,因为a)Cas_Form_Company::Commit()中没有使用内部内容,并且; b)您希望将与存储相关的功能与验证&显示你的表格。考虑调试/改变数据的存储方式,现在你可以查看所有f.ex.包含查询的类正在执行此操作,只执行该操作 - 处理DAL。

答案 1 :(得分:2)

似乎你在表单,模型和控制器中混淆了一些责任。通常,当我根据表单数据与控制器中的模型进行一些交互时,我会引入一个服务层。

另外,我看到你在一个动作中处理多个功能,我将在不同的动作之间进行分割。如果您使用的是Company模型,我建议您创建一个CompanyController:

class CompanyController extends Zend_Controller_Action {}

接下来,您可能希望在一个页面上查看公司详细信息,在另一个页面上修改这些详细信息并在第三页上创建新实例。我通常使用viewAction()editAction()newAction()

执行此操作
public function indexAction ()
{
    $service   = new Cas_Service_Company;
    $companies = $service->getAllCompanies();

    if (!count($companies)) {
        throw new Zend_Controller_Action_Exception('No companies found');
    }

    $this->view->companies = $companies;
}

public function viewAction ()
{
    $service = new Cas_Service_Company;
    $company = $service->getCompany($this->getRequest()->getQuery('id'));

    if (false === $company) {
        throw new Zend_Controller_Action_Exception('Company not found');
    }

    $this->view->company = $company;
}

public function editAction ()
{
    $request = $this->getRequest();
    $service = new Cas_Service_Company;
    $company = $service->getCompany($request->getQuery('id'));

    if (false === $company) {
        throw new Zend_Controller_Action_Exception('Company not found');
    }

    $form = new Cas_Form_Company(array('company' => $company));
    if ($request->isPost() && $form->isValid($request->getPost())) {
        $service->updateCompany($company, $form);
        // redirect here to company view for example
    }

    $this->view->form    = $form;
    $this->view->company = $company;
}

public function newAction ()
{
    $request = $this->getRequest();
    $form    = new Cas_Form_Company;
    if ($request->isPost() && $form->isValid($request->getPost())) {
        $company = $service->createCompany($form);
        // redirect here to company view for example
    }

    $this->view->form    = $form;
}

public function deleteAction ()
{
    $request = $this->getRequest();
    $form    = new Cas_Form_DeleteCompany;
    if ($request->isPost() && $form->isValid($request->getPost())) {
        $service->deleteCompany($form);
        // redirect here to index for example
    }

    $this->view->form    = $form;
}

现在您对所有功能都有单独的操作,为Company设置表单非常简单:

class Cas_Form_Company extends Zend_Form
{
    protected $_company;

    public function init ()
    {
        $this->addElement('text', 'name', array(
            'label' => 'Name'
        ));

        // More elements here

        if (null !== $this->_company) {
            $this->populate($this->_company->toArray());
        }
    }

    public function setCompany (Cas_Model_Company $company)
    {
        $this->_company = $company;
    }
}

在表单中我使用了几个简洁的功能:

  1. 使用init()代替__construct(),无需致电parent::__construct()并调用setOptions()。这对#2有益:
  2. 使用Cas_Model_Company的setter。当您需要更多依赖项时,只需在构造表单时将它们放入数组中(请参阅editAction()作为控制器中的示例)
  3. 如果您设置了$this->_company,只需通过检查即可注入表单数据。在我的情况下,我使用Doctrine,所以toArray()已经存在。否则,您需要自己创建此方法。
  4. 将所有内容放在一起的最后一块是服务层。 Martin Fowler也在他的EAA书和他的网站http://martinfowler.com/eaaCatalog/serviceLayer.html

    中描述了这一点

    典型的服务类在我看来总是这样:

    class Cas_Service_Company
    {
        public function getCompany ($id)
        {
            // Get a Cas_Model_Company and load data from database
            // Example for Doctrine:
    
            $company = new Cas_Model_Company;
            $company = $company->find($id);
            return $company;
        }
    
        public function getAllCompanies ()
        {
            // Get a Cas_Model_Company and load all data from database
            // Example for Doctrine:
    
            $company   = new Cas_Model_Company;
            $companies = $company->findAll();
            return $companies;
        }
    
        public function updateCompany (Cas_Model_Company $company, Cas_Form_Company $form)
        {
            // Update model with new form data
            // Example for Doctrine:
    
            $company->fromArray($form->toArray());
            $company->save();
    
            return $company;
        }
    
        public function createCompany (Cas_Form_Company $form)
        {
            // Create model with form data
            // Example for Doctrine:
    
            $company   = new Cas_Model_Company;
            $company->fromArray($form->toArray());
            $company->save();
    
            return $company;
        }
    
        public function deleteCompany (Cas_Form_DeleteCompany $form)
        {
            // Get a Cas_Model_Company and load data from database
            // Example for Doctrine:
    
            $company = new Cas_Model_Company;
            $company = $company->find($form->getValue('id'));
    
            // Remove this instance
            if (false !== $company) {
                $company->delete();
                return true;
            } else {
                return false;
            }
        }
    }
    

    使用这种服务层,您可以保留所有代码以在一个位置访问和修改模型。当您需要另一个查询(根据特定条件查找公司)时,您只需将该方法添加到服务器类并在(例如)控制器中调用该方法。它会使您的代码非常保持清洁。

    总结:使用服务层并只使用控制器作为信息代理:从源A获取数据并将其带到B点。为表单创建一个setter以接受模型并将其用作装饰器的一种图案