Zend Action Controller - стратегия рефакторинга - PullRequest
6 голосов
/ 12 января 2012

Я создал первый запущенный веб-сервис на Zend Framework (1.10), и сейчас я ищу способы реорганизовать некоторую логику в моих контроллерах действий, чтобы было проще для меня и остальных моя команда, чтобы расширить и поддерживать сервис.

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

Базовая структура для наших контроллеров действий:

  1. Извлечение XML-сообщения из тела запроса. Сюда входит проверка по схеме relaxNG для конкретного действия
  2. Подготовьте ответ XML
  3. Проверка данных в сообщении запроса (неверные данные вызывают исключение - в ответ добавляется сообщение, которое отправляется немедленно)
  4. Выполнить действие базы данных (выбрать / вставить / обновить / удалить)
  5. Возвращает успех или неудачу действия, с необходимой информацией

Простой пример - это действие, которое возвращает список поставщиков на основе гибкого набора критериев:

class Api_VendorController extends Lib_Controller_Action
{  
    public function getDetailsAction()
    {
        try {
            $request = new Lib_XML_Request('1.0');
            $request->load($this->getRequest()->getRawBody(), dirname(__FILE__) . '/../resources/xml/relaxng/vendor/getDetails.xml');
        } catch (Lib_XML_Request_Exception $e) {
            // Log exception, if logger available
            if ($log = $this->getLog()) {
                $log->warn('API/Vendor/getDetails: Error validating incoming request message', $e);
            }

            // Elevate as general error
            throw new Zend_Controller_Action_Exception($e->getMessage(), 400);
        }

        $response = new Lib_XML_Response('API/vendor/getDetails');

        try {
            $criteria = array();
            $fields = $request->getElementsByTagName('field');
            for ($i = 0; $i < $fields->length; $i++) {
                $name = trim($fields->item($i)->attributes->getNamedItem('name')->nodeValue);
                if (!isset($criteria[$name])) {
                    $criteria[$name] = array();
                }
                $criteria[$name][] = trim($fields->item($i)->childNodes->item(0)->nodeValue);
            }

            $vendors = $this->_mappers['vendor']->find($criteria);
            if (count($vendors) < 1) {
                throw new Api_VendorController_Exception('Could not find any vendors matching your criteria');
            }

            $response->append('success');
            foreach ($vendors as $vendor) {
                $v = $vendor->toArray();
                $response->append('vendor', $v);
            }

        } catch (Api_VendorController_Exception $e) {
            // Send failure message
            $error = $response->append('error');
            $response->appendChild($error, 'message', $e->getMessage());

            // Log exception, if logger available
            if ($log = $this->getLog()) {
                $log->warn('API/Account/GetDetails: ' . $e->getMessage(), $e);
            }
        }

        echo $response->save();
    }
}

Итак - зная, где общие черты в моих контроллерах, какова лучшая стратегия для рефакторинга, сохраняя его Zend-подобным, а также тестируемым с PHPUnit?

Я действительно думал об абстрагировании большей части логики контроллера в родительский класс (Lib_Controller_Action), но это делает модульное тестирование более сложным, что мне кажется неправильным.

Ответы [ 2 ]

1 голос
/ 13 января 2012

Поскольку вы должны выполнять этот шаг каждый раз, когда делается запрос, вы можете сохранить полученный, проанализировать и проверить полученный запрос в Zend_Controller_Plugin, который будет запускаться на каждом PreDispatch всех контроллеров. (Выполняется только в том случае, если ваш XML-запрос стандартизирован) (Если вы используете XMLRPC, REST или какой-то стандартный способ для создания запросов к вашему сервису, вы можете посмотреть те модули, встроенные в ZF)

Проверка данных (специфических для действия) может быть выполнена в методе контроллера (который затем вызывается действием (ами), нуждающимся в этом) (если ваши параметры относятся к одному или нескольким действиям этого контроллера) или Вы можете сделать это с помощью шаблонов Factory и Builder в случае, если у вас много общих параметров между контроллерами / действиями

// call to the factory
$filteredRequest = My_Param_Factory::factory($controller, $action, $paramsArray) // call the right builder based on the action/controller combination

// the actual Factory

class My_Param_Factory 
{
    public static function factory($controller, $action, $params)
    {
        $builderClass = "My_Param_Builder_" . ucfirst($controller) . '_' . ucfirst($action);

        $builder = new $builderClass($params);

        return $builder->build();
    }
}

Затем ваш конструктор вызовет определенные параметры, проверяющие классы на основе потребностей этого конкретного компоновщика (что улучшит возможность повторного использования)

В вашем контроллере, если все необходимые параметры верны, вы передаете обработку правильному методу подходящей модели

$userModel->getUserInfo($id) // for example

Что бы удалить все операции обработки данных из контроллеров, которые должны были бы только проверить, в порядке ли ввод, и затем соответствующим образом выполнить диспетчеризацию.

Сохранить результаты (ошибки или успехи) в переменной, которая будет отправлена ​​в представление

Обработка данных (формат и экранирование (например, замените <на & lt; если они должны быть включены в ответ)), отправьте помощнику представления для построения XML, а затем напечатайте (<code>echo) данные в представлении (который будет ответом для вашего пользователя).

public function getDetailsAction()
{
    if ($areRequestParamsValid === true) {
        // process data
    } else {
        // Build specific error message (or call action helper or controller method if error is general)
    }

    $this->view->data = $result
}
1 голос
/ 12 января 2012

Две идеи (просто создание ответа из комментариев выше):

  1. Снижение общности до классов обслуживания / хранилища?Такие классы будут тестируемыми, могут использоваться на всех контроллерах и могут сделать код контроллера более компактным.

  2. Соберите общность в помощниках действий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...