Laravelルーティング(Route)の解釈

不言
リリース: 2023-04-02 17:04:01
オリジナル
2642 人が閲覧しました

この記事では主にLaravelのルーティング(Route)の解釈を紹介していますが、これには参考値がありますので、皆さんにも共有しておきますので、困っている友達は参考にしてください。

Routeは外部への道ですつまり、ルーティングは、Laravel アプリケーションが外部の世界にサービスを提供する特定の方法を定義します: ルーティングによって定義されたハンドラーには、指定された URI、HTTP リクエスト メソッド、およびルーティング パラメーター (オプション) を通じて正しくアクセスできます。 )。 URI に対応するハンドラーが単純なクロージャであっても、コントローラー メソッドに対応するルートがない場合でも、外部からアクセスすることはできません。今日は、Laravel がルーティングをどのように設計して実装するかを見ていきます。

通常、ルーティング ファイルで次のようにルートを定義します:

Route::get('/user', 'UsersController@index');
ログイン後にコピー

上記のルーティングを通じて、クライアントが HTTP GET を通じて URI "/user" をリクエストしていることがわかります。 , Laravelは最終的にリクエストをUsersControllerクラスのindexメソッドにディスパッチして処理し、indexメソッドでクライアントにレスポンスを返します。

上記のルート登録時に使用する Route クラスは Laravel では Facade と呼ばれており、サービスコンテナにバインドされたサービスルーターにアクセスする簡単な方法を提供します Facade の設計思想と実装については別ブログで書く予定ですここでは、呼び出される Route ファサードの静的メソッドがサービス コンテナ内のルーター サービスのメソッドに対応することだけを知っておく必要があるため、上記のルートを次のように登録すると考えることもできます。これ:

app()->make('router')->get('user', 'UsersController@index');
ログイン後にコピー

ルーター サービスは、アプリケーションのインスタンス化時にコンストラクターに RoutingServiceProvider を登録することによってサービス コンテナーにバインドされます。アプリケーション:

//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);
    });
}
ログイン後にコピー

上記のコードを通じて、Route 呼び出しがわかります。メソッドはすべて、\Illuminate\Routing\Router クラスのメソッドに対応します。Router クラスには、ルートの登録、アドレス指定、およびスケジューリングに関連するメソッドが含まれています。

これがlaravelでどのように実装されるかを、ルーティングの登録、読み込み、アドレス指定の段階から見てみましょう。

ルートの読み込み

ルートを登録する前にルーティング ファイルを読み込む必要があります。ルーティング ファイルは、App\Providers\RouteServiceProviderこのサーバー プロバイダーのブート メソッドに読み込まれます。 :

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 は、まずルートのキャッシュ ファイルを検索し、キャッシュ ファイルがない場合はルートをロードします。キャッシュ ファイルは通常、bootstrap/cache/routes.php ファイル内にあります。
loadRoutes メソッドは、マップ メソッドを呼び出してルーティング ファイルにルートをロードします。マップ関数は App\Providers\RouteServiceProvider クラスにあります。このクラスは Illuminate\Foundation\ から継承しますサポート\プロバイダー\ RouteServiceProvider。 Map メソッドを通して、laravel がルーティングを API と Web という 2 つの大きなグループに分割していることがわかります。これら 2 つの部分のルートは、それぞれ Route/web.php と Route/api.php の 2 つのファイルに書き込まれます。

Laravel 5.5 では、ルーティングは複数のファイルに配置されます。以前のバージョンは app/Http/routes.php ファイルにありました。複数のファイルに置くと、API ルーティングと WEB ルーティングの管理が容易になります。

ルート登録

通常、静的メソッド get、post を呼び出すために Route Facade を使用します。 、head、options、put、patch、delete...などを使用してルートを登録します。上で述べたように、これらの静的メソッドは実際には 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);
}
....
ログイン後にコピー

ルートの登録は次のとおりです。ルータ クラスの 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 = ['uses' => $action];
    }

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

    return $action;
}
ログイン後にコピー

ルートの登録時に addRoute に渡される 3 番目のパラメータ アクションは、クロージャ、文字列、または配列にすることができます。配列は ['uses ' に似ています。 => 'Controller@action', 'middleware' => '...'] この形式。アクションがタイプ Controller@action のルートである場合、それはアクション配列に変換されます。convertToControllerAction が実行された後、アクションの内容は次のようになります:

[
    'uses' => 'App\Http\Controllers\SomeController@someAction',
    'controller' => 'App\Http\Controllers\SomeController@someAction'
]
ログイン後にコピー

名前空間がコントローラの名前に追加されます。完全なコントローラ クラス名がその前に形成されます。アクション配列が構築された後、次のステップはルートを作成することです。ルートを作成するには、指定された HTTP リクエスト メソッド、URI 文字列を使用します\Illuminate\Routing\Routeクラスのインスタンス:

protected function newRoute($methods, $uri, $action)
{
    return (new Route($methods, $uri, $action))
                ->setRouter($this)
                ->setContainer($this->container);
}
ログイン後にコピー

ルートが作成されたら、ルートを RouteCollection に追加します:

protected function addRoute($methods, $uri, $action)
{
    return $this->routes->add($this->createRoute($methods, $uri, $action));
}
ログイン後にコピー

Theルーターの $routes 属性は RouteCollection オブジェクトであり、RouteCollection オブジェクトにルートを追加するときに更新されます。RouteCollection オブジェクトの Routes、allRoutes、nameList、および 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);
        }
    }

}
ログイン後にコピー

RouteCollection の 4 つの属性

routes は、HTTP リクエスト メソッドとルーティング オブジェクト間のマッピングを格納します。

[
    'GET' => [
        $routeUri1 => $routeObj1
        ...
    ]
    ...
]
ログイン後にコピー

allRoutes 属性に格納される内容は、routes 属性の 2 桁の配列を 1 桁にプログラムした後の内容です。配列:

[
    'GET' . $routeUri1 => $routeObj1
    'GET' . $routeUri2 => $routeObj2
    ...
]
ログイン後にコピー

nameList はルート名とルーティング オブジェクトのマッピング テーブル

[
    $routeName1 => $routeObj1
    ...
]
ログイン後にコピー

actionList はルート コントローラーのメソッド文字列とルーティング オブジェクトのマッピング テーブル

[
    'App\Http\Controllers\ControllerOne@ActionOne' => $routeObj1
]
ログイン後にコピー

このように, ルートが登録されます。

ルーティング アドレス指定

ミドルウェアの記事では、HTTP リクエストはパイプライン チャネルでのミドルウェアの事前操作を経た後に宛先に到達すると述べました:

//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);
        };
    }
    
}
ログイン後にコピー

上記のコードから、パイプラインの宛先が、dispatchToRouter 関数によって返されるクロージャであることがわかります。

$destination = function ($request) {
    $this->app->instance('request', $request);
    return $this->router->dispatch($request);
};
ログイン後にコピー

ルーターのディスパッチ メソッドがクロージャ内で呼び出され、最初の 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() == '/' ? '/' : '/'.$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事件系统的解读

以上がLaravelルーティング(Route)の解釈の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート