Heim  >  Artikel  >  Backend-Entwicklung  >  Interpretation des Laravel-Routings (Route)

Interpretation des Laravel-Routings (Route)

不言
不言Original
2018-07-06 14:37:362644Durchsuche

Dieser Artikel stellt hauptsächlich die Interpretation des Laravel-Routings (Route) vor, das einen gewissen Referenzwert hat. Jetzt kann ich es mit allen teilen, die es brauchen.

Route ist der Weg für die Außenwelt Um auf Laravel-Anwendungen zuzugreifen, definiert Routing die spezifische Art und Weise, wie die Laravel-Anwendung Dienste für die Außenwelt bereitstellt: Auf den durch das Routing definierten Handler kann über den angegebenen URI, die HTTP-Anforderungsmethode und die Routing-Parameter (optional) korrekt zugegriffen werden. . Unabhängig davon, ob der dem URI entsprechende Handler eine einfache Schließung ist oder die Controller-Methode keine entsprechende Route hat, kann die Außenwelt nicht darauf zugreifen. Heute werfen wir einen Blick darauf, wie Laravel Routing entwirft und implementiert.

Wir definieren die Route normalerweise wie folgt in der Routing-Datei:

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

Durch das obige Routing können wir erkennen, dass der Client zu diesem Zeitpunkt den URI „/user“ über HTTP GET anfordert , sendet Laravel die Anfrage schließlich zur Verarbeitung an die Indexmethode der UsersController-Klasse und gibt dann die Antwort in der Indexmethode an den Client zurück.

Die Route-Klasse, die oben bei der Registrierung von Routen verwendet wird, heißt Facade in Laravel. Sie bietet eine einfache Möglichkeit, auf den an den Service-Container gebundenen Service-Router zuzugreifen. Ich habe vor, einen separaten Blog zu schreiben 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 sich die obige Route auch als Registrierung vorstellen können Dies:

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

Der Router-Dienst wird an den Service-Container 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 kennen wir den Route-Aufruf. Die statischen Methoden Alle entsprechen den Methoden in der Klasse IlluminateRoutingRouter. Die Klasse Router enthält Methoden zur Routing-Registrierung, Adressierung und Planung.

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

Routenladen

Sie müssen die Routing-Datei laden, bevor Sie die Route registrieren. Die Routing-Datei wird in der Boot-Methode von AppProvidersRouteServiceProviderdiesem Serveranbieter geladen:

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 sucht zunächst 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. Die
-Methode „loadRoutes“ ruft die Kartenmethode auf, um die Routen in die Routing-Datei zu laden. Die Kartenfunktion befindet sich in der Klasse 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-Routen und WEB-Routen erleichtern.

Routenregistrierung

Wir verwenden normalerweise die Routenfassade, um die statische Methode get, post aufzurufen , head, options, put, patch, delete ... usw., um Routen zu registrieren. Wie oben erwähnt, rufen diese statischen Methoden tatsächlich Methoden in der Router-Klasse auf:

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

Sie können sehen, dass die Registrierung von Routen erfolgt Wird einheitlich von der addRoute-Methode der Router-Klasse behandelt:

//注册路由到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;
}

Die dritte Parameteraktion, die bei der Registrierung einer Route an addRoute übergeben wird, kann ein Abschluss, eine Zeichenfolge oder ein Array sein. Das Array ähnelt ['uses ' = > 'Controller@action', 'middleware' => '...'] in dieser Form. Wenn es sich bei der Aktion um eine Route vom Typ Controller@action handelt, wird sie in ein Aktionsarray umgewandelt. Nach der Ausführung von „convertToControllerAction“ lautet der Inhalt:

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

Der Name des Controllers wird zum Erstellen eines vollständigen Steuerelements verwendet. Der nächste Schritt besteht darin, eine Route zu erstellen eine Instanz der IlluminateRoutingRoute-Klasse:

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

Routenerstellung. Fügen Sie nach Abschluss die Route zur RouteCollection hinzu:

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

Das $routes-Attribut des Routers ist ein RouteCollection-Objekt Route zum RouteCollection-Objekt, die Routen-, allRoutes-, nameList- und actionList-Attribute des RouteCollection-Objekts werden aktualisiert

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

}

Die vier Attribute von RouteCollection

Routen speichern die Zuordnung zwischen HTTP-Anforderungsmethoden und Routing Objekte:

[
    'GET' => [
        $routeUri1 => $routeObj1
        ...
    ]
    ...
]

Der im Attribut „allRoutes“ gespeicherte Inhalt wird mit dem zweistelligen Array im Attribut „routes“ programmiert. Der Inhalt nach dem Ein-Bit-Array:

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

nameList ist eine Zuordnungstabelle zwischen Routing-Namen und Routing-Objekten

[
    $routeName1 => $routeObj1
    ...
]

actionList ist eine Zuordnungstabelle zwischen Routing-Controller-Methodenzeichenfolgen und Routing-Objekten

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

Auf diese Weise wird die Route registriert.

Routing-Adressierung

Im Artikel über Middleware haben wir gesagt, dass die HTTP-Anfrage das Ziel erreicht, nachdem sie die Voroperation der Middleware auf dem Pipeline-Kanal durchlaufen hat:

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

Oben können Sie dem Code entnehmen, dass das Ziel der Pipeline der Abschluss ist, der von der Funktion „dispatchToRouter“ zurückgegeben wird:

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

Die Dispatch-Methode des Routers wird im Abschluss aufgerufen und die Routenadressierung erfolgt in findRoute. die erste Versandstufe:

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事件系统的解读

Das obige ist der detaillierte Inhalt vonInterpretation des Laravel-Routings (Route). 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