Table of Contents
Route loading
Route registration
Routing Addressing
路由参数绑定
Home Backend Development PHP Tutorial Interpretation of Laravel routing (Route)

Interpretation of Laravel routing (Route)

Jul 06, 2018 pm 02:37 PM
laravel route router

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!

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

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

How to get the return code when email sending fails in Laravel? How to get the return code when email sending fails in Laravel? Apr 01, 2025 pm 02:45 PM

Method for obtaining the return code when Laravel email sending fails. When using Laravel to develop applications, you often encounter situations where you need to send verification codes. And in reality...

Laravel schedule task is not executed: What should I do if the task is not running after schedule: run command? Laravel schedule task is not executed: What should I do if the task is not running after schedule: run command? Mar 31, 2025 pm 11:24 PM

Laravel schedule task run unresponsive troubleshooting When using Laravel's schedule task scheduling, many developers will encounter this problem: schedule:run...

In Laravel, how to deal with the situation where verification codes are failed to be sent by email? In Laravel, how to deal with the situation where verification codes are failed to be sent by email? Mar 31, 2025 pm 11:48 PM

The method of handling Laravel's email failure to send verification code is to use Laravel...

How to implement the custom table function of clicking to add data in dcat admin? How to implement the custom table function of clicking to add data in dcat admin? Apr 01, 2025 am 07:09 AM

How to implement the table function of custom click to add data in dcatadmin (laravel-admin) When using dcat...

Laravel Redis connection sharing: Why does the select method affect other connections? Laravel Redis connection sharing: Why does the select method affect other connections? Apr 01, 2025 am 07:45 AM

The impact of sharing of Redis connections in Laravel framework and select methods When using Laravel framework and Redis, developers may encounter a problem: through configuration...

Laravel Eloquent ORM in Bangla partial model search) Laravel Eloquent ORM in Bangla partial model search) Apr 08, 2025 pm 02:06 PM

LaravelEloquent Model Retrieval: Easily obtaining database data EloquentORM provides a concise and easy-to-understand way to operate the database. This article will introduce various Eloquent model search techniques in detail to help you obtain data from the database efficiently. 1. Get all records. Use the all() method to get all records in the database table: useApp\Models\Post;$posts=Post::all(); This will return a collection. You can access data using foreach loop or other collection methods: foreach($postsas$post){echo$post->

Laravel multi-tenant extension stancl/tenancy: How to customize the host address of a tenant database connection? Laravel multi-tenant extension stancl/tenancy: How to customize the host address of a tenant database connection? Apr 01, 2025 am 09:09 AM

Custom tenant database connection in Laravel multi-tenant extension package stancl/tenancy When building multi-tenant applications using Laravel multi-tenant extension package stancl/tenancy,...

Laravel's geospatial: Optimization of interactive maps and large amounts of data Laravel's geospatial: Optimization of interactive maps and large amounts of data Apr 08, 2025 pm 12:24 PM

Efficiently process 7 million records and create interactive maps with geospatial technology. This article explores how to efficiently process over 7 million records using Laravel and MySQL and convert them into interactive map visualizations. Initial challenge project requirements: Extract valuable insights using 7 million records in MySQL database. Many people first consider programming languages, but ignore the database itself: Can it meet the needs? Is data migration or structural adjustment required? Can MySQL withstand such a large data load? Preliminary analysis: Key filters and properties need to be identified. After analysis, it was found that only a few attributes were related to the solution. We verified the feasibility of the filter and set some restrictions to optimize the search. Map search based on city

See all articles