开放的编程资料库

当前位置:我爱分享网 > PHP教程 > 正文

简单接口和微型 MVC

我的工作很棒:大部分时间我都在玩技术和代码。我的工作也很艰巨:如何在编程界面中平衡功能性和可用性?

我一直在与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;
    }
}

不用担心引用的各种对象;要理解的主要事情是它使用了我之前提到的那些相同的构建块:RequestResponseDispatchable。实际上,它看起来像这样:

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();

非常简单:我们将命名回调附加到DispatcherDispatcher检查Router是否在Request中找到了一个控制器名称,如果itdid和它的回调存在,则执行它.如果它得到一个字符串,我们就用它作为内容;异常会触发ApplicationErrorResponse,如果我们得到一个Response对象,我们就直接使用它。

虽然我在同一个脚本中执行了Dispatcher配置/设置,但它可以作为一个包含文件来完成以简化该脚本端点。

重点是接口定义让这一切变得非常非常容易在几分钟内想出并实施。

我不确定这是否最终会出现在ZF2中;即使不是,它仍然符合我在本文开头设定的目标:平衡可用性和灵活性。

讨论!

更新

  • 2011-02-24:修复了使用“implements”而不是“extends”的一流声明示例
未经允许不得转载:我爱分享网 » 简单接口和微型 MVC

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏