Interpretation of Laravel routing (Route)

不言
Release: 2023-04-02 17:04:01
Original
2629 people have browsed it

This article mainly introduces the interpretation of Laravel routing (Route), which has certain reference value. Now I share it with everyone. Friends in need can refer to it.

Route is the way for the outside world to access Laravel applications. In other words, routing defines the specific way in which Laravel's application provides services to the outside world: the handler defined by the routing can be correctly accessed through the specified URI, HTTP request method, and routing parameters (optional). Whether the handler corresponding to the URI is a simple closure or the controller method does not have a corresponding route, the outside world cannot access them. Today we will take a look at how Laravel designs and implements routing.

We usually define the route as follows in the routing file:

Route::get('/user', 'UsersController@index');
Copy after login

Through the above routing, we can know that the client requests the URI "/user" through HTTP GET. At this time, Laravel will finally dispatch the request to the index method of the UsersController class for processing, and then return the response to the client in the index method.

The Route class used when registering routes above is called Facade in Laravel. It provides a simple way to access the service router bound to the service container. The design concept and implementation of Facade I plan to write a separate blog post about the method in the future. Here we only need to know that the static methods of the Route facade that are called correspond to the methods of the router service in the service container, so you can also think of the above route as registering like this:

app()->make('router')->get('user', 'UsersController@index');
Copy after login

The router service is bound to the service container by registering the RoutingServiceProvider in the constructor when instantiating the application Application:

//bootstrap/app.php
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

//Application: 构造方法
public function __construct($basePath = null)
{
    if ($basePath) {
        $this->setBasePath($basePath);
    }

    $this->registerBaseBindings();

    $this->registerBaseServiceProviders();

    $this->registerCoreContainerAliases();
}

//Application: 注册基础的服务提供器
protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));

    $this->register(new LogServiceProvider($this));

    $this->register(new RoutingServiceProvider($this));
}

//\Illuminate\Routing\RoutingServiceProvider: 绑定router到服务容器
protected function registerRouter()
{
    $this->app->singleton('router', function ($app) {
        return new Router($app['events'], $app);
    });
}
Copy after login

Through the above code, we know the Route call The static methods all correspond to the methods in the \Illuminate\Routing\Router class. The Router class contains methods related to route registration, addressing, and scheduling.

Let’s take a look at how this is implemented in laravel from the stages of routing registration, loading, and addressing.

Route loading

You need to load the routing file before registering the route. The routing file is loaded in the boot method of App\Providers\RouteServiceProviderThis server provider :

class RouteServiceProvider extends ServiceProvider
{
    public function boot()
    {
        parent::boot();
    }

    public function map()
    {
        $this->mapApiRoutes();

        $this->mapWebRoutes();
    }

    protected function mapWebRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }

    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }
}
Copy after login
namespace Illuminate\Foundation\Support\Providers;

class RouteServiceProvider extends ServiceProvider
{

    public function boot()
    {
        $this->setRootControllerNamespace();

        if ($this->app->routesAreCached()) {
            $this->loadCachedRoutes();
        } else {
            $this->loadRoutes();

            $this->app->booted(function () {
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    }

    protected function loadCachedRoutes()
    {
        $this->app->booted(function () {
            require $this->app->getCachedRoutesPath();
        });
    }

    protected function loadRoutes()
    {
        if (method_exists($this, 'map')) {
            $this->app->call([$this, 'map']);
        }
    }
}

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function routesAreCached()
    {
        return $this['files']->exists($this->getCachedRoutesPath());
    }

    public function getCachedRoutesPath()
    {
        return $this->bootstrapPath().'/cache/routes.php';
    }
}
Copy after login

laravel first looks for the cache file of the route, and then loads the route if there is no cache file. The cache file is usually in the bootstrap/cache/routes.php file.
The loadRoutes method will call the map method to load the routes in the routing file. The map function is in the App\Providers\RouteServiceProvider class. This class inherits from Illuminate\Foundation\Support\Providers\ RouteServiceProvider. Through the map method, we can see that laravel divides routing into two large groups: api and web. The routes for these two parts are written in two files respectively: routes/web.php and routes/api.php.

In Laravel 5.5, routes are placed in several files. The previous version was in the app/Http/routes.php file. Putting it in multiple files can make it easier to manage API routing and WEB routing

Route registration

We usually use the Route Facade to call the static method get, post, head, options, put, patch, delete...etc. to register routes. As we said above, these static methods actually call methods in the Router class:

public function get($uri, $action = null)
{
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

public function post($uri, $action = null)
{
    return $this->addRoute('POST', $uri, $action);
}
....
Copy after login

You can see The registration of routes is uniformly handled by the addRoute method of the router class:

//注册路由到RouteCollection
protected function addRoute($methods, $uri, $action)
{
    return $this->routes->add($this->createRoute($methods, $uri, $action));
}

//创建路由
protected function createRoute($methods, $uri, $action)
{
    if ($this->actionReferencesController($action)) {
        //controller@action类型的路由在这里要进行转换
        $action = $this->convertToControllerAction($action);
    }

    $route = $this->newRoute(
        $methods, $this->prefix($uri), $action
    );

    if ($this->hasGroupStack()) {
        $this->mergeGroupAttributesIntoRoute($route);
    }

    $this->addWhereClausesToRoute($route);

    return $route;
}

protected function convertToControllerAction($action)
{
    if (is_string($action)) {
        $action = ['uses' => $action];
    }

    if (! empty($this->groupStack)) {        
        $action['uses'] = $this->prependGroupNamespace($action['uses']);
    }
    
    $action['controller'] = $action['uses'];

    return $action;
}
Copy after login

The third parameter action passed to addRoute when registering a route can be a closure, a string, or an array. The array is similar to ['uses ' => 'Controller@action', 'middleware' => '...'] in this form. If the action is a route of type Controller@action, it will be converted into an action array. After convertToControllerAction is executed, the content of the action is:

[
    'uses' => 'App\Http\Controllers\SomeController@someAction',
    'controller' => 'App\Http\Controllers\SomeController@someAction'
]
Copy after login

You can see that the namespace is added to the name of the controller. The complete controller class name is formed before. After the action array is constructed, the next step is to create the route. To create a route, use the specified HTTP request method, URI string and action array to create it\Illuminate\Routing\RouteInstance of the class:

protected function newRoute($methods, $uri, $action)
{
    return (new Route($methods, $uri, $action))
                ->setRouter($this)
                ->setContainer($this->container);
}
Copy after login

After the route is created, add the Route to the RouteCollection:

protected function addRoute($methods, $uri, $action)
{
    return $this->routes->add($this->createRoute($methods, $uri, $action));
}
Copy after login

The $routes attribute of the router is a RouteCollection object, which will be updated when adding a route to the RouteCollection object. The routes, allRoutes, nameList and actionList attributes of the RouteCollection object

class RouteCollection implements Countable, IteratorAggregate
{
    public function add(Route $route)
    {
        $this->addToCollections($route);

        $this->addLookups($route);

        return $route;
    }
    
    protected function addToCollections($route)
    {
        $domainAndUri = $route->getDomain().$route->uri();

        foreach ($route->methods() as $method) {
            $this->routes[$method][$domainAndUri] = $route;
        }

        $this->allRoutes[$method.$domainAndUri] = $route;
    }
    
    protected function addLookups($route)
    {
        $action = $route->getAction();

        if (isset($action['as'])) {
            //如果时命名路由,将route对象映射到以路由名为key的数组值中方便查找
            $this->nameList[$action['as']] = $route;
        }

        if (isset($action['controller'])) {
            $this->addToActionList($action, $route);
        }
    }

}
Copy after login

The four attributes of RouteCollection

routes stores the mapping between HTTP request methods and routing objects:

[
    'GET' => [
        $routeUri1 => $routeObj1
        ...
    ]
    ...
]
Copy after login

allRoutes The content stored in the attribute is the content after programming the two-digit array in the routes attribute into a one-digit array:

[
    'GET' . $routeUri1 => $routeObj1
    'GET' . $routeUri2 => $routeObj2
    ...
]
Copy after login

nameList is a mapping table between route names and routing objects

[
    $routeName1 => $routeObj1
    ...
]
Copy after login

actionList is the route Mapping table of controller method string and routing object

[
    'App\Http\Controllers\ControllerOne@ActionOne' => $routeObj1
]
Copy after login

In this way, the route is registered.

Routing Addressing

In the middleware article, we said that the HTTP request reaches the destination after going through the pre-operation of the middleware on the Pipeline channel:

//Illuminate\Foundation\Http\Kernel
class Kernel implements KernelContract
{
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }
    
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            return $this->router->dispatch($request);
        };
    }
    
}
Copy after login

Above You can see from the code that the destination of the Pipeline is the closure returned by the dispatchToRouter function:

$destination = function ($request) {
    $this->app->instance('request', $request);
    return $this->router->dispatch($request);
};
Copy after login

The dispatch method of the router is called in the closure, and routing addressing occurs in findRoute, the first stage of dispatch:

class Router implements RegistrarContract, BindingRegistrar
{    
    public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }
    
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    protected function findRoute($request)
    {
        $this->current = $route = $this->routes->match($request);

        $this->container->instance(Route::class, $route);

        return $route;
    }
    
}
Copy after login

寻找路由的任务由 RouteCollection 负责,这个函数负责匹配路由,并且把 request 的 url 参数绑定到路由中:

class RouteCollection implements Countable, IteratorAggregate
{
    public function match(Request $request)
    {
        $routes = $this->get($request->getMethod());

        $route = $this->matchAgainstRoutes($routes, $request);

        if (! is_null($route)) {
            //找到匹配的路由后,将URI里的路径参数绑定赋值给路由(如果有的话)
            return $route->bind($request);
        }

        $others = $this->checkForAlternateVerbs($request);

        if (count($others) > 0) {
            return $this->getRouteForMethods($request, $others);
        }

        throw new NotFoundHttpException;
    }

    protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
    {
        return Arr::first($routes, function ($value) use ($request, $includingMethod) {
            return $value->matches($request, $includingMethod);
        });
    }
}

class Route
{
    public function matches(Request $request, $includingMethod = true)
    {
        $this->compileRoute();

        foreach ($this->getValidators() as $validator) {
            if (! $includingMethod && $validator instanceof MethodValidator) {
                continue;
            }

            if (! $validator->matches($this, $request)) {
                return false;
            }
        }

        return true;
    }
}
Copy after login

$routes = $this->get($request->getMethod());会先加载注册路由阶段在RouteCollection里生成的routes属性里的值,routes中存放了HTTP请求方法与路由对象的映射。

然后依次调用这堆路由里路由对象的matches方法, matches方法, matches方法里会对HTTP请求对象进行一些验证,验证对应的Validator是:UriValidator、MethodValidator、SchemeValidator、HostValidator。
在验证之前在$this->compileRoute()里会将路由的规则转换成正则表达式。

UriValidator主要是看请求对象的URI是否与路由的正则规则匹配能匹配上:

class UriValidator implements ValidatorInterface
{
    public function matches(Route $route, Request $request)
    {
        $path = $request->path() == '/' ? '/' : '/'.$request->path();

        return preg_match($route->getCompiled()->getRegex(), rawurldecode($path));
    }
}
Copy after login

MethodValidator验证请求方法, SchemeValidator验证协议是否正确(http|https), HostValidator验证域名, 如果路由中不设置host属性,那么这个验证不会进行。

一旦某个路由通过了全部的认证就将会被返回,接下来就要将请求对象URI里的路径参数绑定赋值给路由参数:

路由参数绑定

class Route
{
    public function bind(Request $request)
    {
        $this->compileRoute();

        $this->parameters = (new RouteParameterBinder($this))
                        ->parameters($request);

        return $this;
    }
}

class RouteParameterBinder
{
    public function parameters($request)
    {
        $parameters = $this->bindPathParameters($request);

        if (! is_null($this->route->compiled->getHostRegex())) {
            $parameters = $this->bindHostParameters(
                $request, $parameters
            );
        }

        return $this->replaceDefaults($parameters);
    }
    
    protected function bindPathParameters($request)
    {
            preg_match($this->route->compiled->getRegex(), '/'.$request->decodedPath(), $matches);

            return $this->matchToKeys(array_slice($matches, 1));
    }
    
    protected function matchToKeys(array $matches)
    {
        if (empty($parameterNames = $this->route->parameterNames())) {
            return [];
        }

        $parameters = array_intersect_key($matches, array_flip($parameterNames));

        return array_filter($parameters, function ($value) {
            return is_string($value) && strlen($value) > 0;
        });
    }
}
Copy after login

赋值路由参数完成后路由寻址的过程就结束了,结下来就该运行通过匹配路由中对应的控制器方法返回响应对象了。

class Router implements RegistrarContract, BindingRegistrar
{    
    public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }
    
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        $this->events->dispatch(new Events\RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }
    
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;
    //收集路由和控制器里应用的中间件
        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

        return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
    
    }
    
}

namespace Illuminate\Routing;
class Route
{
    public function run()
    {
        $this->container = $this->container ?: new Container;
        try {
            if ($this->isControllerAction()) {
                return $this->runController();
            }
            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }

}
Copy after login

这里我们主要介绍路由相关的内容,runRoute的过程通过上面的源码可以看到其实也很复杂, 会收集路由和控制器里的中间件,将请求通过中间件过滤才会最终到达目的地路由,执行目的路由地run()方法,里面会判断路由对应的是一个控制器方法还是闭包然后进行相应地调用,最后把执行结果包装成Response对象返回给客户端。这个过程还会涉及到我们以前介绍过的中间件过滤、服务解析、依赖注入方面的信息,如果在看源码时有不懂的地方可以翻看我之前写的文章。

  1. 依赖注入

  2. 服务容器

  3. 中间件

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

Laravel事件系统的解读

The above is the detailed content of Interpretation of Laravel routing (Route). For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!