看 Lumen 源代码解析 Request 到 Response 过程

当我想分析 Laravel 是如何做到从 Request -> Response 的解析过程的,发现 Lumen 相对简单,所以今天从 Lumen 源代码入手,说一说Request -> Response 的解析过程

载入 Router

我们使用 Lumen 项目时,都是通过创建 route,将请求的方法 method、路径 uri 和执行 action关联在一起,用于解析 Request

如:

<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/
// 1️⃣
$router->get('/', function () use ($router) {
    return "hello yemeishu ".$router->app->version();
});

// 2️⃣
$router->post('data', 'TempController@index');

我先看看 $router 怎么来的:

/**
 * Create a new Lumen application instance.
 *
 * @param  string|null  $basePath
 * @return void
 */
public function __construct($basePath = null)
{
    if (! empty(env('APP_TIMEZONE'))) {
        date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
    }

    $this->basePath = $basePath;

    $this->bootstrapContainer();
    $this->registerErrorHandling();

    // 这是 Router 引导函数
    $this->bootstrapRouter();
}

...

/**
 * Bootstrap the router instance.
 *
 * @return void
 */
public function bootstrapRouter()
{
    $this->router = new Router($this);
}

有了 $this->router = new Router($this),我们就看 Lumen 是如何装载 routes 的?

$app->router->group([
    'namespace' => 'App\Http\Controllers',
], function ($router) {
    require __DIR__.'/../routes/web.php';
});

...

/**
 * Register a set of routes with a set of shared attributes.
 *
 * @param  array  $attributes
 * @param  \Closure  $callback
 * @return void
 */
public function group(array $attributes, \Closure $callback)
{
    if (isset($attributes['middleware']) && is_string($attributes['middleware'])) {
        $attributes['middleware'] = explode('|', $attributes['middleware']);
    }

    $this->updateGroupStack($attributes);

    call_user_func($callback, $this);

    array_pop($this->groupStack);
}

先判断传入的 $attributes 是否有中间件「middleware」,有则解析成数组一并导入到 $this->groupStack[] 中,具体关联的函数如下,代码简单就不做分析了:

/**
 * Update the group stack with the given attributes.
 *
 * @param  array  $attributes
 * @return void
 */
protected function updateGroupStack(array $attributes)
{
    if (! empty($this->groupStack)) {
        $attributes = $this->mergeWithLastGroup($attributes);
    }

    $this->groupStack[] = $attributes;
}

...

/**
 * Merge the given group attributes with the last added group.
 *
 * @param  array $new
 * @return array
 */
protected function mergeWithLastGroup($new)
{
    return $this->mergeGroup($new, end($this->groupStack));
}

...

/**
 * Merge the given group attributes.
 *
 * @param  array  $new
 * @param  array  $old
 * @return array
 */
public function mergeGroup($new, $old)
{
    $new['namespace'] = static::formatUsesPrefix($new, $old);

    $new['prefix'] = static::formatGroupPrefix($new, $old);

    if (isset($new['domain'])) {
        unset($old['domain']);
    }

    if (isset($old['as'])) {
        $new['as'] = $old['as'].(isset($new['as']) ? '.'.$new['as'] : '');
    }

    if (isset($old['suffix']) && ! isset($new['suffix'])) {
        $new['suffix'] = $old['suffix'];
    }

    return array_merge_recursive(Arr::except($old, ['namespace', 'prefix', 'as', 'suffix']), $new);
}

注:$this->groupStack[] 主要数组 keys 包含:namespaceprefixdomainassuffix,这对下文的分析很有作用。

然后执行 call_user_func($callback, $this),即回调函数:

function ($router) {
    require __DIR__.'/../routes/web.php';
}

web.php 载入到这个函数里,进一步得到函数:

function ($router) {
    $router->get('/', function () use ($router) {
        return "hello yemeishu ".$router->app->version();
    });

    $router->post('data', 'TempController@index');
}

我们的主角进场了,我们看看这些 getpost 函数,基本都是一样的:

public function head($uri, $action)
{
    $this->addRoute('HEAD', $uri, $action);

    return $this;
}

public function get($uri, $action)
{
    $this->addRoute('GET', $uri, $action);

    return $this;
}

public function post($uri, $action)
{
    $this->addRoute('POST', $uri, $action);

    return $this;
}

public function put($uri, $action)
{
    $this->addRoute('PUT', $uri, $action);

    return $this;
}

public function patch($uri, $action)
{
    $this->addRoute('PATCH', $uri, $action);

    return $this;
}

public function delete($uri, $action)
{
    $this->addRoute('DELETE', $uri, $action);

    return $this;
}

public function options($uri, $action)
{
    $this->addRoute('OPTIONS', $uri, $action);

    return $this;
}

注:这里可以看出 Router 主要是处理这 7个 methodheadgetpostputpatchdeleteoptions

执行的都是 $this->addRoute() 函数:

/**
 * Add a route to the collection.
 *
 * @param  array|string  $method
 * @param  string  $uri
 * @param  mixed  $action
 * @return void
 */
public function addRoute($method, $uri, $action)
{
    $action = $this->parseAction($action);

    $attributes = null;

    if ($this->hasGroupStack()) {
        $attributes = $this->mergeWithLastGroup([]);
    }

    if (isset($attributes) && is_array($attributes)) {
        if (isset($attributes['prefix'])) {
            $uri = trim($attributes['prefix'], '/').'/'.trim($uri, '/');
        }

        if (isset($attributes['suffix'])) {
            $uri = trim($uri, '/').rtrim($attributes['suffix'], '/');
        }

        $action = $this->mergeGroupAttributes($action, $attributes);
    }

    $uri = '/'.trim($uri, '/');

    if (isset($action['as'])) {
        $this->namedRoutes[$action['as']] = $uri;
    }

    if (is_array($method)) {
        foreach ($method as $verb) {
            $this->routes[$verb.$uri] = ['method' => $verb, 'uri' => $uri, 'action' => $action];
        }
    } else {
        $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
    }
}

我们一步步来解析:

$action = $this->parseAction($action);

...

/**
 * Parse the action into an array format.
 *
 * @param  mixed  $action
 * @return array
 */
protected function parseAction($action)
{
    if (is_string($action)) {
        return ['uses' => $action];
    } elseif (! is_array($action)) {
        return [$action];
    }

    if (isset($action['middleware']) && is_string($action['middleware'])) {
        $action['middleware'] = explode('|', $action['middleware']);
    }

    return $action;
}

$action 转为数组,如果传入的参数包含中间件,顺便也转为数组结构。

此方法可以看出,$action 不仅可以是 string 类型,也可以是数组类型,可以传入 key 为:usesmiddleware

如上面例子结果变为:

// 1️⃣
[function () use ($router) {
    return "hello yemeishu ".$router->app->version();
}]

// 2️⃣
['uses' => 'TempController@index']

继续往下看:

if (isset($attributes) && is_array($attributes)) {
    if (isset($attributes['prefix'])) {
        $uri = trim($attributes['prefix'], '/').'/'.trim($uri, '/');
    }

    if (isset($attributes['suffix'])) {
        $uri = trim($uri, '/').rtrim($attributes['suffix'], '/');
    }

    $action = $this->mergeGroupAttributes($action, $attributes);
}

$uri = '/'.trim($uri, '/');

这个比较好理解了,只是将「前缀」和「后缀」拼接到 $uri 上。

// 1️⃣
$uri = '/';

// 2️⃣
$uri = '/data';

同时,将 $attributes 合并到 $action

往下走:

if (isset($action['as'])) {
    $this->namedRoutes[$action['as']] = $uri;
}

如果 $action 数组还传入 key:as,则将该 $uri保存到命名数组中,利用别名与 $uri关联。

最后处理 $method 了:

if (is_array($method)) {
    foreach ($method as $verb) {
        $this->routes[$verb.$uri] = ['method' => $verb, 'uri' => $uri, 'action' => $action];
    }
} else {
    $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
}

注:这也可以看出 $method 可以传入数组,并且将路由三要素「methoduriaction」存于数组 $routes 中,并用 $method.$uri 当 key。

到此,我们基本解读了 Router 这个类的 416行所有代码和功能了。

我们把所有定义的路由信息都存入 Router 对象中,供 Request -> Response 使用。

dispatch request

系统的运行,主要就是为了响应各种各样的 Request,得到 Response 反馈给请求者。

// Lumen 的入口方法
$app->run();

...
// 直接进入代码:Laravel\Lumen\Concerns\RoutesRequests
/**
 * Run the application and send the response.
 *
 * @param  SymfonyRequest|null  $request
 * @return void
 */
public function run($request = null)
{
    $response = $this->dispatch($request);

    if ($response instanceof SymfonyResponse) {
        $response->send();
    } else {
        echo (string) $response;
    }

    if (count($this->middleware) > 0) {
        $this->callTerminableMiddleware($response);
    }
}

// $dispatch 执行函数:
/**
 * Dispatch the incoming request.
 *
 * @param  SymfonyRequest|null  $request
 * @return Response
 */
public function dispatch($request = null)
{
    list($method, $pathInfo) = $this->parseIncomingRequest($request);

    try {
        return $this->sendThroughPipeline($this->middleware, function () use ($method, $pathInfo) {
            if (isset($this->router->getRoutes()[$method.$pathInfo])) {
                return $this->handleFoundRoute([true, $this->router->getRoutes()[$method.$pathInfo]['action'], []]);
            }

            return $this->handleDispatcherResponse(
                $this->createDispatcher()->dispatch($method, $pathInfo)
            );
        });
    } catch (Exception $e) {
        return $this->prepareResponse($this->sendExceptionToHandler($e));
    } catch (Throwable $e) {
        return $this->prepareResponse($this->sendExceptionToHandler($e));
    }
}

庖丁解牛,我们首要看的是如何利用 parseIncomingRequest() 返回 $method, $pathInfo 的?

list($method, $pathInfo) = $this->parseIncomingRequest($request);

...

/**
 * Parse the incoming request and return the method and path info.
 *
 * @param  \Symfony\Component\HttpFoundation\Request|null  $request
 * @return array
 */
protected function parseIncomingRequest($request)
{
    if (! $request) {
        $request = Request::capture();
    }

    $this->instance(Request::class, $this->prepareRequest($request));

    return [$request->getMethod(), '/'.trim($request->getPathInfo(), '/')];
}

这里主要使用 $request->getMethod()$request->getPathInfo(),这放在对 Request 的分析时再做研究。

我们接着往下看:

try {
    // 第1️⃣步,这是最后执行的,暂且最后分析
    return $this->sendThroughPipeline($this->middleware, function () use ($method, $pathInfo) {
        if (isset($this->router->getRoutes()[$method.$pathInfo])) {
            // 第2️⃣步
            return $this->handleFoundRoute([true, $this->router->getRoutes()[$method.$pathInfo]['action'], []]);
        }
        // 第3️⃣步的执行会调用到「第2️⃣步」方法,所以我们先研究第3️⃣步
        return $this->handleDispatcherResponse(
            $this->createDispatcher()->dispatch($method, $pathInfo)
        );
    });
} catch (Exception $e) {
    return $this->prepareResponse($this->sendExceptionToHandler($e));
} catch (Throwable $e) {
    return $this->prepareResponse($this->sendExceptionToHandler($e));
}

「第3️⃣步」的 $this->createDispatcher()->dispatch($method, $pathInfo) 主要是返回数组结构如下:

<?php

namespace FastRoute;

interface Dispatcher
{
    const NOT_FOUND = 0;
    const FOUND = 1;
    const METHOD_NOT_ALLOWED = 2;

    /**
     * Dispatches against the provided HTTP method verb and URI.
     *
     * Returns array with one of the following formats:
     *
     *     [self::NOT_FOUND]
     *     [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
     *     [self::FOUND, $handler, ['varName' => 'value', ...]]
     *
     * @param string $httpMethod
     * @param string $uri
     *
     * @return array
     */
    public function dispatch($httpMethod, $uri);
}

我们接着看是如何实现 Dispatcher 的?

/**
 * Create a FastRoute dispatcher instance for the application.
 *
 * @return Dispatcher
 */
protected function createDispatcher()
{
    return $this->dispatcher ?: \FastRoute\simpleDispatcher(function ($r) {
        foreach ($this->router->getRoutes() as $route) {
            $r->addRoute($route['method'], $route['uri'], $route['action']);
        }
    });
}

这里的 \FastRoute\simpleDispatcher() 是一个全局函数:

/**
 * @param callable $routeDefinitionCallback
 * @param array $options
 *
 * @return Dispatcher
 */
function simpleDispatcher(callable $routeDefinitionCallback, array $options = [])
{
    $options += [
        'routeParser' => 'FastRoute\\RouteParser\\Std',
        'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
        'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
        'routeCollector' => 'FastRoute\\RouteCollector',
    ];

    /** @var RouteCollector $routeCollector */
    $routeCollector = new $options['routeCollector'](
        new $options['routeParser'], new $options['dataGenerator']
    );
    $routeDefinitionCallback($routeCollector);

    return new $options['dispatcher']($routeCollector->getData());
}

这个方法主要利用 new FastRoute\\RouteParser\\Std()new FastRoute\\DataGenerator\\GroupCountBased() 来创建 routeCollector 对象,用于存储所有 route

function ($routeCollector) {
    foreach ($this->router->getRoutes() as $route) {
        $routeCollector->addRoute($route['method'], $route['uri'], $route['action']);
    }
}

我们继续看 addRoute() 方法:

/**
 * Adds a route to the collection.
 *
 * The syntax used in the $route string depends on the used route parser.
 *
 * @param string|string[] $httpMethod
 * @param string $route
 * @param mixed  $handler
 */
public function addRoute($httpMethod, $route, $handler)
{
    $route = $this->currentGroupPrefix . $route;
    $routeDatas = $this->routeParser->parse($route);
    foreach ((array) $httpMethod as $method) {
        foreach ($routeDatas as $routeData) {
            $this->dataGenerator->addRoute($method, $routeData, $handler);
        }
    }
}

这里有两个方法我们可以往下研究:$this->routeParser->parse($route) 解析 $route 这个暂且不表,和 $this->dataGenerator->addRoute($method, $routeData, $handler) 收集路由信息:

public function addRoute($httpMethod, $routeData, $handler)
{
    if ($this->isStaticRoute($routeData)) {
        $this->addStaticRoute($httpMethod, $routeData, $handler);
    } else {
        $this->addVariableRoute($httpMethod, $routeData, $handler);
    }
}

这里主要分成两种情况,一种是单一路由数据,保存在数组 $staticRoutes 中,另一种是正则表达式路由数据,存于 $methodToRegexToRoutesMap 中。我们此时更关心以后怎么使用这两个数组数据。

最后就是创建分发器 FastRoute\\Dispatcher\\GroupCountBased

// 其中 `$routeCollector->getData()` 后续继续研究
return new $options['dispatcher']($routeCollector->getData());

...

class GroupCountBased extends RegexBasedAbstract
{
    public function __construct($data)
    {
        list($this->staticRouteMap, $this->variableRouteData) = $data;
    }

    protected function dispatchVariableRoute($routeData, $uri)
    {
        foreach ($routeData as $data) {
            if (!preg_match($data['regex'], $uri, $matches)) {
                continue;
            }

            list($handler, $varNames) = $data['routeMap'][count($matches)];

            $vars = [];
            $i = 0;
            foreach ($varNames as $varName) {
                $vars[$varName] = $matches[++$i];
            }
            return [self::FOUND, $handler, $vars];
        }

        return [self::NOT_FOUND];
    }
}

创建了 dispatcher 分配器之后,我们就可以考虑怎么使用了。

$this->createDispatcher()->dispatch($method, $pathInfo)

分派方法,无非从上面的两个数组中去寻找对应 method 和 uri,以获得 handler

<?php

namespace FastRoute\Dispatcher;

use FastRoute\Dispatcher;

abstract class RegexBasedAbstract implements Dispatcher
{
    /** @var mixed[][] */
    protected $staticRouteMap = [];

    /** @var mixed[] */
    protected $variableRouteData = [];

    /**
     * @return mixed[]
     */
    abstract protected function dispatchVariableRoute($routeData, $uri);

    public function dispatch($httpMethod, $uri)
    {
        if (isset($this->staticRouteMap[$httpMethod][$uri])) {
            $handler = $this->staticRouteMap[$httpMethod][$uri];
            return [self::FOUND, $handler, []];
        }

        $varRouteData = $this->variableRouteData;
        if (isset($varRouteData[$httpMethod])) {
            $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
            if ($result[0] === self::FOUND) {
                return $result;
            }
        }

        // For HEAD requests, attempt fallback to GET
        if ($httpMethod === 'HEAD') {
            if (isset($this->staticRouteMap['GET'][$uri])) {
                $handler = $this->staticRouteMap['GET'][$uri];
                return [self::FOUND, $handler, []];
            }
            if (isset($varRouteData['GET'])) {
                $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
                if ($result[0] === self::FOUND) {
                    return $result;
                }
            }
        }

        // If nothing else matches, try fallback routes
        if (isset($this->staticRouteMap['*'][$uri])) {
            $handler = $this->staticRouteMap['*'][$uri];
            return [self::FOUND, $handler, []];
        }
        if (isset($varRouteData['*'])) {
            $result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
            if ($result[0] === self::FOUND) {
                return $result;
            }
        }

        // Find allowed methods for this URI by matching against all other HTTP methods as well
        $allowedMethods = [];

        foreach ($this->staticRouteMap as $method => $uriMap) {
            if ($method !== $httpMethod && isset($uriMap[$uri])) {
                $allowedMethods[] = $method;
            }
        }

        foreach ($varRouteData as $method => $routeData) {
            if ($method === $httpMethod) {
                continue;
            }

            $result = $this->dispatchVariableRoute($routeData, $uri);
            if ($result[0] === self::FOUND) {
                $allowedMethods[] = $method;
            }
        }

        // If there are no allowed methods the route simply does not exist
        if ($allowedMethods) {
            return [self::METHOD_NOT_ALLOWED, $allowedMethods];
        }

        return [self::NOT_FOUND];
    }
}

...

protected function dispatchVariableRoute($routeData, $uri)
{
    foreach ($routeData as $data) {
        if (!preg_match($data['regex'], $uri, $matches)) {
            continue;
        }

        list($handler, $varNames) = $data['routeMap'][count($matches)];

        $vars = [];
        $i = 0;
        foreach ($varNames as $varName) {
            $vars[$varName] = $matches[++$i];
        }
        return [self::FOUND, $handler, $vars];
    }

    return [self::NOT_FOUND];
}

以上解析过程比较简单,就不用解释了。

得到 handler 后,我们就可以处理 $request,得到 $response 结果。

/**
 * Handle the response from the FastRoute dispatcher.
 *
 * @param  array  $routeInfo
 * @return mixed
 */
protected function handleDispatcherResponse($routeInfo)
{
    switch ($routeInfo[0]) {
        case Dispatcher::NOT_FOUND:
            throw new NotFoundHttpException;
        case Dispatcher::METHOD_NOT_ALLOWED:
            throw new MethodNotAllowedHttpException($routeInfo[1]);
        case Dispatcher::FOUND:
            return $this->handleFoundRoute($routeInfo);
    }
}

我们不分析前两个分支,我们主要考虑 Dispatcher::FOUND 这种情况,即:$this->handleFoundRoute($routeInfo)

/**
 * Handle a route found by the dispatcher.
 *
 * @param  array  $routeInfo
 * @return mixed
 */
protected function handleFoundRoute($routeInfo)
{
    $this->currentRoute = $routeInfo;

    $this['request']->setRouteResolver(function () {
        return $this->currentRoute;
    });

    $action = $routeInfo[1];

    // Pipe through route middleware...
    if (isset($action['middleware'])) {
        $middleware = $this->gatherMiddlewareClassNames($action['middleware']);

        return $this->prepareResponse($this->sendThroughPipeline($middleware, function () {
            return $this->callActionOnArrayBasedRoute($this['request']->route());
        }));
    }

    return $this->prepareResponse(
        $this->callActionOnArrayBasedRoute($routeInfo)
    );
}

我们暂时也不去考虑是否存在「中间件」的问题,那么把目光就锁定在最后一条语句上了。

/**
 * Call the Closure on the array based route.
 *
 * @param  array  $routeInfo
 * @return mixed
 */
protected function callActionOnArrayBasedRoute($routeInfo)
{
    $action = $routeInfo[1];

    if (isset($action['uses'])) {
        return $this->prepareResponse($this->callControllerAction($routeInfo));
    }

    foreach ($action as $value) {
        if ($value instanceof Closure) {
            $closure = $value->bindTo(new RoutingClosure);
            break;
        }
    }

    try {
        return $this->prepareResponse($this->call($closure, $routeInfo[2]));
    } catch (HttpResponseException $e) {
        return $e->getResponse();
    }
}

到此,我们终于开始进入「Controller」级别的分析了。

先看第一种情况:$this->callControllerAction($routeInfo)

如上面的第2️⃣种情况:

// 2️⃣
['uses' => 'TempController@index']
/**
 * Call a controller based route.
 *
 * @param  array  $routeInfo
 * @return mixed
 */
protected function callControllerAction($routeInfo)
{
    $uses = $routeInfo[1]['uses'];

    if (is_string($uses) && ! Str::contains($uses, '@')) {
        $uses .= '@__invoke';
    }

    list($controller, $method) = explode('@', $uses);

    if (! method_exists($instance = $this->make($controller), $method)) {
        throw new NotFoundHttpException;
    }

    if ($instance instanceof LumenController) {
        return $this->callLumenController($instance, $method, $routeInfo);
    } else {
        return $this->callControllerCallable(
            [$instance, $method], $routeInfo[2]
        );
    }
}

这个对于我们天天写 Lumen or Laravel 代码的我们来说,挺好理解的,通过利用「@」分解 controllermethod;再利用 $this->make($controller) 得到 Controller 对象,如果是 LumenController 类型,则需要去判断是否有中间件一个环节。最后都是调用 $this->callControllerCallable([$instance, $method], $routeInfo[2]):

protected function callControllerCallable(callable $callable, array $parameters = [])
{
    try {
        return $this->prepareResponse(
            $this->call($callable, $parameters)
        );
    } catch (HttpResponseException $e) {
        return $e->getResponse();
    }
}

...

/**
 * Call the given Closure / class@method and inject its dependencies.
 *
 * @param  callable|string  $callback
 * @param  array  $parameters
 * @param  string|null  $defaultMethod
 * @return mixed
 */
public function call($callback, array $parameters = [], $defaultMethod = null)
{
    return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}

来反射解析类和方法,调用方法,返回结果。具体可以详细研究 illuminate\\container\\BoundMethod 类。

封装成 Response 结果:

return $this->prepareResponse(
    $this->call($callable, $parameters)
);

/**
 * Prepare the response for sending.
 *
 * @param  mixed  $response
 * @return Response
 */
public function prepareResponse($response)
{
    if ($response instanceof Responsable) {
        $response = $response->toResponse(Request::capture());
    }

    if ($response instanceof PsrResponseInterface) {
        $response = (new HttpFoundationFactory)->createResponse($response);
    } elseif (! $response instanceof SymfonyResponse) {
        $response = new Response($response);
    } elseif ($response instanceof BinaryFileResponse) {
        $response = $response->prepare(Request::capture());
    }

    return $response;
}

起始亦是终,最后把 Response 输出,回到最开始的 run 方法

public function run($request = null)
{
    $response = $this->dispatch($request);

    if ($response instanceof SymfonyResponse) {
        $response->send();
    } else {
        echo (string) $response;
    }

    if (count($this->middleware) > 0) {
        $this->callTerminableMiddleware($response);
    }
}

总结

到此,我们终于分析了走了一遍较为完整的从 Request 到最后的 Response的流程。此文结合 Lumen 文档 https://lumen.laravel.com/docs/5.6/routing 来看,效果会更好的。

这过程我们也发现了几个彩蛋:

彩蛋1️⃣ $router->addRoute($method, $uri, $action)method 可以传入数组,如 ['GET', 'POST']

彩蛋2️⃣

if (! method_exists($instance = $this->make($controller), $method)) {
    throw new NotFoundHttpException;
}

if ($instance instanceof LumenController) {
    return $this->callLumenController($instance, $method, $routeInfo);
} else {
    return $this->callControllerCallable(
        [$instance, $method], $routeInfo[2]
    );
}

可以看出,处理我们 route 的类可以不用继承「Controller」,只要依赖注入,能利用 $this->make解析到的「类」均可。

最后,我们还有很多需要深入研究的内容,如:中间件 middlewarePipeline 原理、Request 解析、带有正则表达式的 $uri是怎么解析的,等等。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!