从头开始搭建Web框架(二)- 控制器
从头开始搭建Web框架(二)- 控制器

在上一篇,介绍了路由的实现,有了路由做请求流量的分发,现在需要在控制器取得请求数据,并进一步实现我们的业务逻辑。

这时候需要我们使用到另一个模块:http-kernel

http-kernel

> composer require symfony/http-kernel

安装完此模块之后,我们先不着急使用,先新建出我们控制器所在的目录,并在composer.json中将此目录添加到autoload

我在index.php的同级新建一个Controller目录,用来存放控制器文件,并在composer.json中添加如下内容:

"autoload": {
        "psr-4": {
            "Controller\\": "controller/"
        }
    }

然后运行:

> composer dump-autoload

修改index.php文件,修改原来的路由添加的代码如下:

只是增加控制器所在路径,和对应的函数,这里需要注意,控制器的路径一定是要包含命名空间的全路径,并且每层命名空间是以\分隔,而不是/,在后面调用的时候,我会稍作解释实现原理。

最后修改后面的部分代码如下:

在初始化ControllerResolverArgumentResolver之后,将路由匹配的参数添加到Request对象中,也就是:

$request->attributes->add($attributes);

controllerarguments便是由此解析而来。如果你点到getController()里面看一看,就可以发现,源码也是如此的简单。

在创建控制器的时候,会将Controller\IndexController::index::分隔为两个部分,第一分部为类路径,另一个部分为函数名,在调用初始化控制器的时候,将第一部分的类路径Controller\IndexController直接作为参数传递给instantiateController(),而直接new就完成了实例化过程。

控制器

完成所有路由部分的代码,在我们新建Controller目录下,新建IndexController:

我们可以发现,不仅可以将$name做为index()方法的参数传递过来,而且甚至可以将Request对象注入其中,甚至两个变量的位置都可以颠倒,但必须要注意的是必须要加上类型约束,这种方式的实现得益于PHP原生的Reflection类,具体实现可以查阅部分源码,这里不赘述。

重新刷新页面,我们可以看到如下的结果:

http://127.0.0.1:7777/hello => Hello World and your Languages is zh_CN

至此,就完成路由到控制器的流程,整个过程是非常简单的,但是如果你想深入,也是可以通过阅读里面的部分源码来深入了解整个解析器模块,如何解析出控制器实力,如何解析出参数等等。

在下面的一章,我不会接着MVC的逻辑去实现Model层。Model层和整个框架没什么强关联,仅仅是安装指定的包罢了,你可能喜欢Laravel的Eloquent,或者其他的第三方的包,比如:Doctrine ORM,即装即用。

我会讨论更加有意思的事件分发模块symfony/event-dispatcher,让我们的框架更容易扩展,更加灵活。

代码片段


<?php
    require_once __DIR__.'/vendor/autoload.php';

    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    use Symfony\Component\Routing\RequestContext;
    use Symfony\Component\Routing\Matcher\UrlMatcher;
    use Symfony\Component\Routing\Exception\ResourceNotFoundException;
    use Symfony\Component\HttpKernel;

    // 增加路由
    $routes = new RouteCollection();
    $routes->add('hello', new Route('/hello/{name}', array(
        'name' => 'World',
        '_controller' => 'Controller\IndexController::index'
    )));

    $request = Request::createFromGlobals();

    // 初始化路由上下文
    $context = new RequestContext();
    $context->fromRequest($request);

    // URL匹配器
    $matcher = new UrlMatcher($routes, $context);

    // 控制器解释器和参数解释器
    $controllerResolver = new HttpKernel\Controller\ControllerResolver();
    $argumentResolver = new HttpKernel\Controller\ArgumentResolver();


    try {
        $attributes = $matcher->match($request->getPathInfo());

        $request->attributes->add($attributes);

        $controller = $controllerResolver->getController($request);
        $arguments = $argumentResolver->getArguments($request, $controller);

        $content = call_user_func_array($controller, $arguments);
        $response = new Response($content, 200);

    } catch(ResourceNotFoundException $exception) {
        $response = new Response('Not Found', 404);
    } catch (Exception $exception) {
        $response = new Response('An error occurred', 500);
    }

    $response->send();

参考

https://symfony.com/doc/current/create_framework/http_kernel_controller_resolver.html

http://php.net/manual/zh/class.reflection.php


If You Have Any Question, You Can Contact Me Through liam@blue7wings.com, @Blue7Wings, #Liam_Hsia