Являются ли относительно «толстые» контроллеры нормальными с Zend_Form?(А если нет, то как их разбавить?) - PullRequest
1 голос
/ 17 октября 2011

У меня есть форма, которая выглядит следующим образом:

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 ]

3 голосов
/ 17 октября 2011

Это прекрасно, все, что вы делаете, это отправляете значения в вашу форму и обрабатываете ответ, немного перенаправляете и обрабатываете представление.

Каждая из этих вещей подходит действительно хорошо, и было бы непросто найти ее, если вы разместите их в другом месте. Я бы не думал о «жирных контроллерах», поскольку «требует больше LOC, чем другие контроллеры / действия», а скорее «содержит бизнес-логику». Если вы чувствуете, что цикломатическая сложность становится слишком большой, попробуйте разделить ваши действия на более мелкие.

edit: На самом деле, я бы реорганизовал часть $form->Commit(); в нечто вроде $repository->create($form->getValues(), так как a) ничего не используется в Cas_Form_Company::Commit(), и; б) вы хотите, чтобы функции, связанные с хранением, были отделены от проверки и отображения вашей формы. Подумайте об отладке / изменении способа хранения ваших данных, и теперь вы найдете, где искать, если все f.ex. классы, содержащие запросы, делают это и только это - обрабатывают DAL.

2 голосов
/ 17 октября 2011

Кажется, вы перепутали некоторые обязанности в вашей форме, модели и контроллере. Обычно я представляю сервисный уровень, когда у меня есть некоторые взаимодействия с моделями из контроллера, основанные на данных формы.

Кроме того, я вижу, что вы обрабатываете несколько функций в одном действии, которое я бы разделил между различными действиями. Если вы работаете с моделью 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. Когда вам нужно больше зависимостей, нужно просто поместить их в массив при создании формы (см. editAction() в качестве примера в контроллере)
  3. Внедрить данные формы просто путем проверки, если вы установили $this->_company. В моем случае я использую Doctrine, поэтому toArray() уже есть. В противном случае вам нужно создать этот метод самостоятельно.

Последний кусок, который соберет все воедино - это сервисный уровень. Мартин Фаулер также описал это в своей книге P 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. Сделайте установщик для вашей формы, чтобы принять модель и использовать ее как своего рода декоратор рисунок

...