Laravel can parse Request -> Response from the Lumen source code. Lumen can parse Request -> Response from the Lumen source code

In the Router

When we use Lumen projects, we create a route, associating the Request method, path URI, and execution action to parse the Request.

Such as:


      

/*
|--------------------------------------------------------------------------
| 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 (a) use ($router) {
    return "hello yemeishu ".$router->app->version();
});

/ / 2 ️ ⃣
$router->post('data'.'TempController@index');
Copy the code

$router = 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();
    
    // This is the Router boot function
    $this->bootstrapRouter(); }.../**
 * Bootstrap the router instance.
 *
 * @return void
 */
public function bootstrapRouter(a)
{
    $this->router = new Router($this);
}
Copy the code

$this->router = new router ($this);

$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);
}
Copy the code

$this->groupStack[]; $this->groupStack[]; $this->groupStack[]; $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);
}
Copy the code

$this->groupStack[] $this->groupStack[] $this->groupStack[] $this->groupStack[]

Then call call_user_func($callback, $this) :

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

Load web.php into this function to further produce the function:

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

    $router->post('data'.'TempController@index');
}
Copy the code

Let’s look at these get and POST functions, which are basically the same:

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;
}
Copy the code

The Router handles 7 methods: head, GET, POST, PUT, Patch, delete, and options

$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]; }}Copy the code

Let’s break it down step by step:

$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;
}
Copy the code

Convert $action to an array or, incidentally, to an array structure if the passed argument contains middleware.

As you can see, $action can be a string or an array and can be passed keys: uses and Middleware

As in the above example, the result becomes:

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

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

Read on:

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, '/');
Copy the code

This is easier to understand, just concatenate the “prefix” and “suffix” to $uri.

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

/ / 2 ️ ⃣
$uri = '/data';
Copy the code

Also, merge $Attributes into $Action.

Go down:

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

If the $action array also passes key: as, the $URI is saved to a named array, associated with the $URI by an alias.

$method = $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];
}
Copy the code

$routes = method; $uri = action; $routes = method;

At this point, we have basically interpreted all 416 lines of Router code and functionality.

We store all defined routing information into the Router object for Request -> Response.

dispatch request

The operation of the system is mainly to respond to a variety of requests and get feedback to the requester.

// Lumen entry method$app->run(); .// Enter the code directly: 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 executes the function:
/**
 * 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 (a) 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)); }}Copy the code

ParseIncomingRequest () returns $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(), '/')];
}
Copy the code

$request->getMethod() and $request->getPathInfo() will be used to analyze the request.

Let’s move on:

try {
    // The first ️ retail, which is the last one carried out, is the last analysis
    return $this->sendThroughPipeline($this->middleware, function (a) use ($method, $pathInfo) {
        if (isset($this->router->getRoutes()[$method.$pathInfo])) {
            / / 2 ️ ⃣ step
            return $this->handleFoundRoute([true.$this->router->getRoutes()[$method.$pathInfo]['action'], []]);
        }
        // The implementation of 3️ will call on "2️ discount", so we first study 3️ discount
        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));
}
Copy the code

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


      

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);
}
Copy the code

How do you implement Dispatcher?

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

Here \FastRoute\simpleDispatcher() is a global function:

/ * * *@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());
}
Copy the code

This method uses new FastRoute\ RouteParser\ Std() and new FastRoute\ DataGenerator\ GroupCountBased() to create a routeCollector object. Used to store all routes:

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

Moving on to the addRoute() method:

/**
 * 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); }}}Copy the code

There are two approaches we can follow: $this->routeParser->parse($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); }}Copy the code

There are two main cases: single route data, stored in the array $staticRoutes, and regular expression route data, stored in $MethodToregOutesMap. We’re more concerned now with how we’re going to use these two arrays.

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]; }}Copy the code

Now that we have created the Dispatcher dispatcher, we can consider how to use it.

$this->createDispatcher()->dispatch($method, $pathInfo)
Copy the code

The dispatch method simply looks for the corresponding method and URI in the above two arrays to retrieve the handler.


      

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[The '*'][$uri])) {
            $handler = $this->staticRouteMap[The '*'][$uri];
            return [self::FOUND, $handler, []];
        }
        if (isset($varRouteData[The '*'])) {
            $result = $this->dispatchVariableRoute($varRouteData[The '*'], $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];
}
Copy the code

The above parsing process is relatively simple, so there is no need to explain.

Now that we have handler, we can process $request and get $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); }}Copy the code

$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 (a) {
        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 (a) {
            return $this->callActionOnArrayBasedRoute($this['request']->route());
        }));
    }

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

Leaving aside the question of “middleware” for now, let’s focus on the last statement.

/**
 * 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(); }}Copy the code

At this point, we are finally starting to get into the “Controller” level of analysis.

$this->callControllerAction($routeInfo)

The situation of 2️ one as above:

/ / 2 ️ ⃣
['uses'= >'TempController@index']
Copy the code
/**
 * 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, The '@')) {
        $uses .= '@__invoke';
    }

    list($controller, $method) = explode(The '@', $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]); }}Copy the code

And this makes a lot of sense to those of us who write Lumen or Laravel code every day, by using “@” to split controller and method; $this->make($controller) $this->make($LumenController) $this->make($controller) $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);
}
Copy the code

To reflect parse classes and methods, call methods, and return results. For details, look at the illuminate\\container\ BoundMethod class.

Encapsulate into Response result:

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;
}
Copy the code

We start and we end, and we output Response, and we go back to our original run method

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); }}Copy the code

conclusion

So far, we have finally analyzed a relatively complete process from Request to the final Response. This article combined with the Lumen document lumen.laravel.com/docs/5.6/ro… Let’s see, it’s going to be even better.

We also found a few Easter eggs along the way:

️ $router->addRoute($method, $uri, $action) method can pass array, e.g. [‘GET’, ‘POST’]

Eggs ️ 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]); }Copy the code

$this->make $this->make $this->make $this->make $this->make $this->make $this->make

Finally, there is a lot to learn about middleware, Pipeline principles, Request parsing, how $URIs with regular expressions are parsed, and more.

To be continued