我已经在Zend Framework(1.10)上构建了一个首次运行的Web服务,现在我正在研究如何重构我的Action Controllers中的一些逻辑,这样我和其他人都会更容易我的团队扩展和维护服务。
我可以看到哪里有重构的机会,但我不清楚最佳策略如何。控制器上最好的文档和教程只讨论小规模应用程序,并没有真正讨论如何抽象出更大规模的重复代码。
我们的动作控制器的基本结构是:
一个简单的例子是这个动作,它根据一组灵活的标准返回供应商列表:
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-like并且还可以使用PHPUnit进行测试?
我确实考虑过将更多的控制器逻辑抽象为父类(Lib_Controller_Action),但这使得单元测试变得更加复杂,这种方式在我看来是错误的。
答案 0 :(得分:1)
两个想法(只是从上面的评论中创建答案):
将通用性推向服务/存储库类?这些类是可测试的,可以跨控制器使用,并且可以使控制器代码更紧凑。
将共性纳入行动助手。
答案 1 :(得分:1)
由于每次发出请求时都必须执行此步骤,因此您可以在Zend_Controller_Plugin中存储接收,解析和验证收到的请求,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; with&amp; lt;如果要包含在响应中),发送给视图助手以构建XML然后打印(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
}