我的工作很棒:大部分时间我都在玩技术和代码。我的工作也很艰巨:如何在编程界面中平衡功能性和可用性?
我一直在与RalphSchindler一起研究围绕ZendFramework2.0MVC层的一组提案,特别是三元组的“C”或“控制器”部分。我们正在尝试兼顾大量需求,从使代码对新手来说易于理解,到使代码尽可能可扩展以供激进的性能调优开发人员使用。
我一直在研究的一个接口的灵感来自两个截然不同的来源。第一个是PHP自己的SoapServerAPI(我们已经在各种服务器组件中使用了它);第二个是PHP自己的SoapServerAPI。另一个是几年前我与FabienPotencier(Symfony名人)的一次讨论,他说Symfony2的目标是“将请求转化为响应”。
我现在想到的是:
interface Dispatchable { /** * @return Response */ public function dispatch(Request $request); }
我能听到你们中的一些ZF人已经在说,“真的,这就是你们到目前为止的全部想法?”这就是我认为它可能非常出色的原因:
这使得做ZF1风格的MVC、将服务器端点作为控制器或编写您自己的微型MVC变得非常简单。
/块引用>
这使得做ZF1风格的MVC、将服务器端点作为控制器或编写您自己的微型MVC变得非常简单。
/块引用>
想法是此接口(以及请求/响应接口)成为标准ZFMVC实现或您自己的自定义MVC实现的基本构建块。
这就是微MVC主题最终变得相关的地方。
微型MVC
一年多以前,随着PHP5.3的最终发布,我开始看到许多“微型MVC框架”如雨后春笋般涌现;严重的是,有一段时间,它似乎每隔一天,php开发人员每隔一天发布一个新的。
微型MVC非常有趣。如果你考虑一下你遇到的大部分网站,它们实际上只包含几个页面,以及一些需要表单处理或模型之类的实际功能。因此,使用ZF、Symfony甚至CodeIgniter等成熟的MVC似乎很疯狂。微型MVC同时解决了简化和表现力的问题;重点是尽可能快地完成工作,最好使用尽可能少的代码。
在查看许多这些微型MVC框架时,我注意到了一些事情:
- 大多数人要么使用正则表达式进行路由,要么使用轻量级路由器(例如HordeRoutes)来路由请求。
- 大多数人使用闭包和/或柯里化将路由结果映射到“操作”.
所以我使用上面的
Dispatchable
接口做了一些小事,看看我能做什么。use Zend\Stdlib\Dispatchable, Zend\Http\Response as HttpResponse, Fig\Request, Fig\Response; class Dispatcher implements Dispatchable { protected $controllers; public function attach($spec, $callback = null) { if (is_array($spec) || $spec instanceof \Traversable) { foreach ($spec as $controller => $callback) { $this->attach($controller, $callback); } return $this; } if (!is_scalar($spec)) { throw new \InvalidArgumentException('Spec must be scalar or traversable'); } if (!is_callable($callback)) { throw new \InvalidArgumentException('Callback must be callable'); } $this->controllers[$spec] = $callback; return $this; } /** * Dispatch a request * * @param Request $request * @return Response */ public function dispatch(Request $request) { if (!$controller = $request->getMetadata('controller')) { return new PageNotFoundResponse( '<h1>Page not found</h1>' ); } if (!array_key_exists($controller, $this->controllers)) { return new PageNotFoundResponse('<h1>Page not found</h1>'); } $handler = $this->controllers[$controller]; $response = $handler($request); if (is_string($response)) { return new HttpResponse($response); } if (!is_object($response)) { return new ApplicationErrorResponse('<h1>An error occurred</h1>'); } if (!$response instanceof Response) { if (!method_exists($response, '__toString')) { return new ApplicationErrorResponse('<h1>An error occurred</h1>'); } return new HttpResponse($response->__toString()); } return $response; } }不用担心引用的各种对象;要理解的主要事情是它使用了我之前提到的那些相同的构建块:
Request
、Response
、Dispatchable
。实际上,它看起来像这样:use Zend\Controller\Router, Zend\Http\Request; $request = new Request; $router = new Router; /* * Configure some routes here. We'll assume we've somehow configured routes * mapping the following controllers: * - homepage * - foo * - rest * - foobar */ $router->route($request); $dispatcher = new Dispatcher(); $dispatcher ->attach('homepage', function($request) { // Simply returning a string: return '<h1>Welcome</h1> <p>Welcometo our site!</p>'; }) ->attach('foo', function($request) { // Simply returning a string: return '<h1>Foo!</h1>'; }) ->attach('rest', function($request) { // Example of a "REST" service... switch ($request->getMethod()) { case 'GET': if (!$id = $request->query('id', false)) { // We have a "list operation"... // Assume we somehow grab the list and create a response return $response; } // We have an ID -- fetch it and return the page break; case 'POST': // Create document and return a response break; case 'PUT': if (!$id = $request->query('id', false)) { // No ID in the query string means no document! // Return a failure response } // We have an ID -- fetch and update from PUT params, and // return a response break; case 'DELETE': if (!$id = $request->query('id', false)) { // No ID in the query string means no document! // Return a failure response } // We have an ID -- delete, and // return a response break; default: return new ApplicationErrorResponse('Unknown Method'); break; } }) ->attach('foobar', function($request) { // Curry in controllers to allow them to be lazy-loaded, and to ensure we // get a response object back (Dispatcher will take care of that). $controller = new FooBarController(); return $controller->dispatch($request); }); $response = $dispatcher->dispatch($request); $response->emit();非常简单:我们将命名回调附加到
Dispatcher
。Dispatcher
检查Router
是否在Request
中找到了一个控制器名称,如果itdid和它的回调存在,则执行它.如果它得到一个字符串,我们就用它作为内容;异常会触发ApplicationErrorResponse
,如果我们得到一个Response
对象,我们就直接使用它。虽然我在同一个脚本中执行了
Dispatcher
配置/设置,但它可以作为一个包含文件来完成以简化该脚本端点。重点是接口定义让这一切变得非常非常容易在几分钟内想出并实施。
我不确定这是否最终会出现在ZF2中;即使不是,它仍然符合我在本文开头设定的目标:平衡可用性和灵活性。
讨论!
更新
- 2011-02-24:修复了使用“implements”而不是“extends”的一流声明示例