Maison  >  Article  >  développement back-end  >  Interprétation du routage Laravel (Route)

Interprétation du routage Laravel (Route)

不言
不言original
2018-07-06 14:37:362630parcourir

Cet article présente principalement l'interprétation du routage Laravel (Route), qui a une certaine valeur de référence. Maintenant, je le partage avec tout le monde. Les amis dans le besoin peuvent s'y référer

La route est la voie pour le monde extérieur. pour accéder aux applications Laravel. En d'autres termes, le routage définit la manière spécifique dont l'application de Laravel fournit des services au monde extérieur : le gestionnaire défini par le routage est correctement accessible via l'URI spécifié, la méthode de requête HTTP et les paramètres de routage (facultatif). . Que le gestionnaire correspondant à l'URI soit une simple fermeture ou que la méthode du contrôleur n'ait pas de route correspondante, le monde extérieur ne peut pas y accéder. Aujourd'hui, nous allons voir comment Laravel conçoit et implémente le routage.

Nous définissons généralement la route comme suit dans le fichier de routage :

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

À partir de la route ci-dessus, nous pouvons savoir que le client demande l'URI en utilisant la méthode HTTP GET "/ user", Laravel finira par envoyer la requête à la méthode d'index de la classe UsersController pour traitement, puis renverra la réponse au client dans la méthode d'index.

La classe Route utilisée lors de l'enregistrement des routes ci-dessus s'appelle Facade dans Laravel. Elle fournit un moyen simple d'accéder au routeur de service lié au conteneur de service. Le concept de conception et la mise en œuvre de Facade Je prévois d'écrire un blog séparé. poster sur la méthode à l'avenir. Ici, nous avons seulement besoin de savoir que les méthodes statiques de la façade Route qui sont appelées correspondent aux méthodes du service de routeur dans le conteneur de service, vous pouvez donc également considérer la route ci-dessus comme une inscription comme ceci :

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

Le service du routeur est lié au conteneur de services en enregistrant le RoutingServiceProvider dans le constructeur lors de l'instanciation de l'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);
    });
}

Grâce au code ci-dessus, nous savons que les méthodes statiques appelées par Route correspondent aux méthodes de la classe IlluminateRoutingRouter. La classe Router contient des méthodes liées à l'enregistrement, à l'adressage et à la planification des routes.

Voyons comment cela est implémenté dans Laravel à partir des étapes d'enregistrement, de chargement et d'adressage du routage.

Chargement de l'itinéraire

Avant d'enregistrer un itinéraire, vous devez d'abord charger le fichier de routage. Le fichier de routage est chargé dans la AppProvidersRouteServiceProviderméthode de démarrage de ce fournisseur de serveur :

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'));
    }
}
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';
    }
}
Laravel recherche d'abord le fichier cache de la route. S'il n'y a pas de fichier cache, il chargera la route. Le fichier cache se trouve généralement dans le fichier bootstrap/cache/routes.php. La méthode

loadRoutes appellera la méthode map pour charger les routes dans le fichier de route. La fonction map est dans la classe
, qui hérite de AppProvidersRouteServiceProvider. Grâce à la méthode map, nous pouvons voir que laravel divise le routage en deux grands groupes : api et web. Les routes de ces deux parties sont écrites respectivement dans deux fichiers : routes/web.php et routes/api.php. IlluminateFoundationSupportProvidersRouteServiceProvider

Dans Laravel 5.5, les routes sont placées dans plusieurs fichiers. La version précédente était dans le fichier app/Http/routes.php. Le mettre dans plusieurs fichiers peut faciliter la gestion des routes API et des routes WEB

Enregistrement des routes

Nous utilisons généralement la Route Facade pour appeler la méthode statique get, post , head, options, put, patch, delete...etc. pour enregistrer les routes Comme nous l'avons dit ci-dessus, ces méthodes statiques appellent en fait des méthodes de la classe Router :

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);
}
....
OK Vous pouvez voir que le l'enregistrement des routes est entièrement géré par la méthode addRoute de la classe router :

//注册路由到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;
}
La troisième action de paramètre passée à addRoute lors de l'enregistrement d'une route peut être une fermeture, une chaîne ou un tableau, et le tableau est Similaire à ['uses' => 'Controller@action', 'middleware' => '...']. Si l'action est une route de type

, elle sera convertie en un tableau d'actions. Une fois convertToControllerAction exécuté, le contenu de l'action est : Controller@action

[
    'uses' => 'App\Http\Controllers\SomeController@someAction',
    'controller' => 'App\Http\Controllers\SomeController@someAction'
]
. ajouté au nom du contrôleur pour former un nom de classe de contrôleur complet et le tableau d'actions sont construits. L'étape suivante consiste à créer une route. Pour créer une route, utilisez la méthode de requête HTTP spécifiée, la chaîne URI et le tableau d'actions pour créer. une instance de la classe

 : IlluminateRoutingRoute

protected function newRoute($methods, $uri, $action)
{
    return (new Route($methods, $uri, $action))
                ->setRouter($this)
                ->setContainer($this->container);
}
Une fois la route créée, ajoutez la Route à la RouteCollection :

protected function addRoute($methods, $uri, $action)
{
    return $this->routes->add($this->createRoute($methods, $uri, $action));
}
L'attribut $routes du routeur est une RouteCollection Lors de l'ajout d'une route à l'objet RouteCollection, les attributs routes, allRoutes, nameList et actionList

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);
        }
    }

}
Les quatre attributs de RouteCollection

routes stockent le mappage entre les méthodes de requête HTTP et le routage. objets :

[
    'GET' => [
        $routeUri1 => $routeObj1
        ...
    ]
    ...
]
contenu stocké dans l'attribut allRoutes Lors de la programmation du tableau à deux chiffres de l'attribut routes dans le tableau à un chiffre :

[
    'GET' . $routeUri1 => $routeObj1
    'GET' . $routeUri2 => $routeObj2
    ...
]
nameList est une table de mappage entre le routage noms et objets de routage

[
    $routeName1 => $routeObj1
    ...
]
actionList est un contrôle de routage La table de mappage entre la chaîne de méthode du gestionnaire et l'objet de routage

[
    'App\Http\Controllers\ControllerOne@ActionOne' => $routeObj1
]
est comme ceci, et la route est enregistrée.

Adressage de routage

Dans l'article sur le middleware, nous disions que la requête HTTP arrive à destination après être passée par la pré-opération du middleware sur le canal Pipeline :

//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);
        };
    }
    
}
D'après le code ci-dessus, vous pouvez voir que la destination du Pipeline est la fermeture renvoyée par la fonction dispatchToRouter :

$destination = function ($request) {
    $this->app->instance('request', $request);
    return $this->router->dispatch($request);
};
La méthode de dispatch du routeur est appelée dans la fermeture, et la route l'adressage se produit dans la première phase de répartition, findRoute Inside :

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

寻找路由的任务由 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;
    }
}

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

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;
        });
    }
}

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

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();
        }
    }

}

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

  1. 依赖注入

  2. 服务容器

  3. 中间件

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

相关推荐:

Laravel事件系统的解读

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn