Heim  >  Artikel  >  PHP-Framework  >  Was ist Laravel-Routing?

Was ist Laravel-Routing?

青灯夜游
青灯夜游Original
2021-09-02 11:57:262698Durchsuche

In Laravel ist Routing der Weg für die Außenwelt, auf Laravel-Anwendungen zuzugreifen, oder Routing definiert die spezifische Art und Weise, wie Laravel-Anwendungen Dienste für die Außenwelt bereitstellen. Durch das Routing wird die Anfrage des Benutzers an den angegebenen Controller und die angegebene Methode zur Verarbeitung gemäß dem vorab geplanten Plan weitergeleitet.

Was ist Laravel-Routing?

Die Betriebsumgebung dieses Tutorials: Windows 7-System, Laravel 6-Version, DELL G3-Computer.

Routing ist der Weg für die Außenwelt, auf Laravel-Anwendungen zuzugreifen, oder Routing definiert die spezifische Art und Weise, wie Laravel-Anwendungen Dienste für die Außenwelt bereitstellen: Nur über den angegebenen URI, die HTTP-Anforderungsmethode und die Routing-Parameter (optional) können die Auf die Routing-Definition muss korrekt zugegriffen werden.

Egal, ob der dem URI entsprechende Handler ein einfacher Verschluss ist oder die Controller-Methode keine entsprechende Route hat, die Außenwelt kann nicht darauf zugreifen

Heute werfen wir einen Blick darauf, wie Laravel Routing entwirft und implementiert.

Normalerweise definieren wir die Route wie folgt in der Routing-Datei:

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

Durch das obige Routing können wir wissen, dass Laravel die Anfrage an den sendet, wenn der Client den URI „/user“ über HTTP GET anfordert Indexmethode der UsersController-Klasse zur Verarbeitung und geben Sie dann die Antwort in der Indexmethode an den Client zurück.

Die bei der Registrierung der Route verwendete Route-Klasse heißt Facade in Laravel. Sie bietet eine einfache Möglichkeit, auf den an den Service-Container gebundenen Service-Router zuzugreifen Hier müssen wir nur wissen, dass die aufgerufenen statischen Methoden der Route-Fassade den Methoden des Router-Dienstes im Service-Container entsprechen, sodass Sie die obige Route auch so registriert sehen können:

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

router Dieser Dienst wird an den Dienstcontainer gebunden, indem der RoutingServiceProvider beim Instanziieren der Anwendung im Konstruktor registriert wird. Anwendung:

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

Durch den obigen Code wissen wir, dass die von Route aufgerufenen statischen Methoden alle IlluminateRoutingRouter entsprechen Code>Methoden in der Klasse „Router“ enthalten Methoden zur Routing-Registrierung, Adressierung und Planung. <code>IlluminateRoutingRouter类里的方法,Router这个类里包含了与路由的注册、寻址、调度相关的方法。

下面我们从路由的注册、加载、寻址这几个阶段来看一下laravel里是如何实现这些的。

路由加载

注册路由前需要先加载路由文件,路由文件的加载是在AppProvidersRouteServiceProvider这个服务器提供者的boot方法里加载的:

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

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

        $this->mapWebRoutes();
    }

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

    protected function mapApiRoutes()
    {
        Route::prefix(&#39;api&#39;)
             ->middleware(&#39;api&#39;)
             ->namespace($this->namespace)
             ->group(base_path(&#39;routes/api.php&#39;));
    }
}
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[&#39;router&#39;]->getRoutes()->refreshNameLookups();
                $this->app[&#39;router&#39;]->getRoutes()->refreshActionLookups();
            });
        }
    }

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

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

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

    public function getCachedRoutesPath()
    {
        return $this->bootstrapPath().&#39;/cache/routes.php&#39;;
    }
}

laravel 首先去寻找路由的缓存文件,没有缓存文件再去进行加载路由。缓存文件一般在 bootstrap/cache/routes.php 文件中。
方法loadRoutes会调用map方法来加载路由文件里的路由,map这个函数在AppProvidersRouteServiceProvider类中,这个类继承自IlluminateFoundationSupportProvidersRouteServiceProvider。通过map方法我们能看到laravel将路由分为两个大组:api、web。这两个部分的路由分别写在两个文件中:routes/web.php、routes/api.php。

Laravel5.5里是把路由分别放在了几个文件里,之前的版本是在app/Http/routes.php文件里。放在多个文件里能更方便地管理API路由和与WEB路由

路由注册

我们通常都是用Route这个Facade调用静态方法get, post, head, options, put, patch, delete......等来注册路由,上面我们也说了这些静态方法其实是调用了Router类里的方法:

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

public function post($uri, $action = null)
{
    return $this->addRoute(&#39;POST&#39;, $uri, $action);
}
....

可以看到路由的注册统一都是由router类的addRoute方法来处理的:

//注册路由到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 = [&#39;uses&#39; => $action];
    }

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

    return $action;
}

注册路由时传递给addRoute的第三个参数action可以闭包、字符串或者数组,数组就是类似['uses' => 'Controller@action', 'middleware' => '...']这种形式的。如果action是Controller@action类型的路由将被转换为action数组, convertToControllerAction执行完后action的内容为:

[
    &#39;uses&#39; => &#39;App\Http\Controllers\SomeController@someAction&#39;,
    &#39;controller&#39; => &#39;App\Http\Controllers\SomeController@someAction&#39;
]

可以看到把命名空间补充到了控制器的名称前组成了完整的控制器类名,action数组构建完成接下里就是创建路由了,创建路由即用指定的HTTP请求方法、URI字符串和action数组来创建IlluminateRoutingRoute

Sehen wir uns an, wie dies in Laravel in den Phasen der Routing-Registrierung, des Ladens und der Adressierung implementiert wird.

Route wird geladen

Sie müssen die Routing-Datei laden, bevor Sie die Route registrieren. Die Routing-Datei wird in AppProvidersRouteServiceProviderGeladen in der Boot-Methode dieses Serveranbieters:<p><pre class="brush:js;toolbar:false;">protected function newRoute($methods, $uri, $action) { return (new Route($methods, $uri, $action)) -&gt;setRouter($this) -&gt;setContainer($this-&gt;container); }</pre><pre class="brush:js;toolbar:false;">protected function addRoute($methods, $uri, $action) { return $this-&gt;routes-&gt;add($this-&gt;createRoute($methods, $uri, $action)); }</pre></p>laravel sucht zuerst nach der Cache-Datei der Route und lädt dann die Route, wenn keine Cache-Datei vorhanden ist. Die Cache-Datei befindet sich normalerweise in der Datei bootstrap/cache/routes.php. <p>Die Methode „loadRoutes“ ruft die Map-Methode auf, um die Routen in die Routing-Datei zu laden. Die Map-Funktion befindet sich in der Klasse <code>AppProvidersRouteServiceProvider, die von IlluminateFoundationSupportProvidersRouteServiceProvider erbt. Durch die Kartenmethode können wir sehen, dass Laravel das Routing in zwei große Gruppen unterteilt: API und Web. Die Routen für diese beiden Teile werden jeweils in zwei Dateien geschrieben: Routen/web.php und Routen/api.php.

In Laravel 5.5 werden Routen in mehreren Dateien abgelegt. Die vorherige Version befand sich in der Datei app/Http/routes.php. Das Einfügen in mehrere Dateien kann die Verwaltung von API-Routing und WEB-Routing erleichtern

Routenregistrierung

Wir verwenden normalerweise die Routenfassade, um statische Methoden wie get, post, head, options, put, patch, delete usw. aufzurufen, um Routen zu registrieren. Wir haben oben auch gesagt, dass diese statischen Methoden tatsächlich sind Die Router-Klasse heißt:

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[&#39;as&#39;])) {
            //如果时命名路由,将route对象映射到以路由名为key的数组值中方便查找
            $this->nameList[$action[&#39;as&#39;]] = $route;
        }

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

}

Sie können sehen, dass die Registrierung von Routen von der addRoute-Methode der Router-Klasse abgewickelt wird:

[
    &#39;GET&#39; => [
        $routeUri1 => $routeObj1
        ...
    ]
    ...
]
🎜Der dritte Aktionsparameter, der beim Registrieren einer Route an addRoute übergeben wird, kann ein Abschluss oder eine Zeichenfolge oder sein Array, Array ähnelt ['uses' => 'Controller@action', 'middleware' => '...']. Wenn die Aktion eine Route vom Typ Controller@action ist, wird sie in ein Aktionsarray konvertiert. Nach der Ausführung von „convertToControllerAction“ lautet der Inhalt der Aktion: 🎜
[
    &#39;GET&#39; . $routeUri1 => $routeObj1
    &#39;GET&#39; . $routeUri2 => $routeObj2
    ...
]
🎜Sie können sehen, dass der Namespace ist Vor dem Namen des Controllers werden der vollständige Controller-Klassenname und das Aktionsarray erstellt. Um eine Route zu erstellen, verwenden Sie die angegebene HTTP-Anforderungsmethode, den URI-String und das Aktionsarray die IlluminateRoutingRoute-Klasse: 🎜
[
    $routeName1 => $routeObj1
    ...
]
🎜Nachdem die Routenerstellung abgeschlossen ist, fügen Sie die Route zur RouteCollection hinzu: 🎜
[
    &#39;App\Http\Controllers\ControllerOne@ActionOne&#39; => $routeObj1
]
🎜Das $routes-Attribut des Routers ist ein RouteCollection-Objekt, wenn Sie eine Route zur RouteCollection hinzufügen Objekt werden die Routen-, AllRoutes-, NameList- und ActionList-Attribute des RouteCollection-Objekts aktualisiert Der Inhalt nach der Programmierung des zweistelligen Arrays im Routenattribut in ein einstelliges Array: 🎜
//Illuminate\Foundation\Http\Kernel
class Kernel implements KernelContract
{
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance(&#39;request&#39;, $request);

        Facade::clearResolvedInstance(&#39;request&#39;);

        $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(&#39;request&#39;, $request);

            return $this->router->dispatch($request);
        };
    }
    
}
🎜nameList ist eine Zuordnungstabelle zwischen Routing-Namen und Routing-Objekten. 🎜
$destination = function ($request) {
    $this->app->instance(&#39;request&#39;, $request);
    return $this->router->dispatch($request);
};
🎜actionList ist eine Zuordnungstabelle zwischen Routing-Controller-Methodenzeichenfolgen und Routing-Objekten 🎜
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;
    }
    
}
🎜So wird die Route registriert. 🎜

路由寻址

中间件的文章里我们说过HTTP请求在经过Pipeline通道上的中间件的前置操作后到达目的地:

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

        Facade::clearResolvedInstance(&#39;request&#39;);

        $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(&#39;request&#39;, $request);

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

上面代码可以看到Pipeline的destination就是dispatchToRouter函数返回的闭包:

$destination = function ($request) {
    $this->app->instance(&#39;request&#39;, $request);
    return $this->router->dispatch($request);
};

在闭包里调用了router的dispatch方法,路由寻址就发生在dispatch的第一个阶段findRoute里:

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() == &#39;/&#39; ? &#39;/&#39; : &#39;/&#39;.$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(), &#39;/&#39;.$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(&#39;middleware.disable&#39;) &&
                            $this->container->make(&#39;middleware.disable&#39;) === 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对象返回给客户端。这个过程还会涉及到我们以前介绍过的中间件过滤、服务解析、依赖注入方面的信息。

相关推荐:最新的五个Laravel视频教程

Das obige ist der detaillierte Inhalt vonWas ist Laravel-Routing?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:Welche Idee für Laravel?Nächster Artikel:Welche Idee für Laravel?