从头开始搭建Web框架(一)- 路由
从头开始搭建Web框架(一)- 路由

能够熟练地使用一个框架是远远不够的,还需要了解她的内心。—— J.J Terry

为什么要自己搭建Web框架

如果跟你周围的工程师说「我要自己搭建一款web框架」,他们可能会说「疯了吧,何必自己造轮子呢?」,大多数情况下他们是对的,完全没必要自己重新造轮子,这是一件吃力而且不讨好的事情,但是总有人想尝试点新东西,他们的原因可能有:

  • 想简单了解一些Web框架的构架
  • 对Web框架有独特的需求,比如:完全不想要视图层
  • 好玩

如果其中有一个原因命中你,那么就跟着这几篇文章,来构建你自己的一个Web框架吧,放轻松,我们不会从源码开始。

概览

如果你稍微看过Laravel的组成结构,可以发现,Laravel也是由很多个组件所组成的,并不是所有部分都是自己完成,其中大部分都是Symfony的组件,我们同样用Symfony的组件来完成这个简易的Web框架。

HTTP-Foundation

试想一下,我们第一次接触到PHP,没用任何框架的时候,代码是怎样的?

接收到参数,逻辑处理,然后做出响应,所有框架都是在此基础上扩展而来,这是框架最基本的功能。

我们使用http-foundation这个组件对这个最基本的功能进行扩展。

> composer require symfony/http-foundation

代码就可以修改为如下内容:

$request = Request::createFromGlobals();表示创建的Request对象是基于PHP全局变量($_GET, $_POST, $_FILE, $_COOKIE, $_SESSION等等)的,send()方法则将Response对象返回给客户端,这种面向对象类型的编程方式,大大改善了代码可用性和稳健性。


// 获取URL中路径
$request->getPathInfo();

// 获取GET或POST请求中的参数对应的值
$request->query->get('foo');
$request->request->get('bar', 'default value if bar does not exist');

// 获取 SERVER 的变量信息
$request->server->get('HTTP_HOST');

// 获取上传的文件
$request->files->get('foo');

// 获取Cookie的值
$request->cookies->get('PHPSESSID');

// 获取header头相关的信息
$request->headers->get('host');
$request->headers->get('content_type');

$request->getMethod();    // 获取请求方式:GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // 获取接受的语言

Routing

要知道,我们不可能用一个PHP文件只处理来自一个URL的请求,这时候还需要用路由做分发,这时候,我们需要第二个模块http-kernel

> composer require symfony/routing

则将我们的代码修改成如下:

$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));

这是我们添加的第一个路由规则,hello为路由名,而name则为匹配到的变量名,默认值为World

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

经过路由匹配器的匹配之后,会分解出包含_routename的数组。

 [
        '_route' => 'hello',
        'name'   => 'World'
 ]

知道路由名和取得的参数,我们便可以实现业务逻辑:

        extract($attributes);
        if($_route == 'hello') {
            $content = 'Hello ' . $name;
        }
        $response = new Response($content, 200);

在终端中运行php -S 127.0.0.1:7777, 在浏览器中访问对应的路由可得到如下的结果:

http://127.0.0.1:7777/hello => Hello World

http://127.0.0.1:7777/hello/Liam => Hello Liam

http://127.0.0.1:7777/foo => Not Found

至此,使用http-foundationhttp-routing这两个组件就基本上实现了我们的路由功能,在下个部分,会带大家使用控制器去实现逻辑代码。

代码片段

<?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;

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

    $request = Request::createFromGlobals();

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

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

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

        // 匹配的数组分解为变量
        extract($attributes);
        if($_route == 'hello') {
            $content = 'Hello ' . $name;
        }
        $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_foundation.html#going-oop-with-the-httpfoundation-component

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

http://php.net/manual/zh/function.extract.php


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