laravel では、ルーティングは外部の世界が Laravel アプリケーションにアクセスするための方法、またはルーティングは Laravel アプリケーションが外部の世界にサービスを提供する特定の方法を定義します。ルーティングは、事前に計画された計画に従って、ユーザーのリクエストを指定されたコントローラーおよびメソッドに送信して処理します。
このチュートリアルの動作環境: Windows 7 システム、Laravel 6 バージョン、DELL G3 コンピューター。
ルーティングは、外部の世界が Laravel アプリケーションにアクセスする方法、またはルーティングは、Laravel アプリケーションが外部の世界にサービスを提供する特定の方法を定義します。正しいアクセスは、指定された URI、HTTP リクエスト メソッド、およびルートによって定義されたハンドラーへのルーティング パラメーター (オプション)。
URI に対応するハンドラーが単純なクロージャであっても、コントローラー メソッドに対応するルートがない場合でも、外部からアクセスすることはできません。
今日は、Laravel がどのように設計されたのかを見ていきます。そしてルーティングを実装します。
通常、ルーティング ファイルで次のようにルートを定義します。
Route::get('/user', 'UsersController@index');
上記のルーティングを通じて、クライアントが HTTP GET " /user" を通じて URI を要求していることがわかります。 Laravel は最終的にリクエストを UsersController クラスのインデックス メソッドにディスパッチして処理し、インデックス メソッドでクライアントに応答を返します。
上記のルート登録時に使用する 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ルーティングの管理が楽になります
ルート登録
us 通常Route ファサードは、静的メソッド get、post、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 番目のパラメーター アクションは、 close 、文字列、または配列にすることができます。配列は ['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)); }
ルーターの $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请求在经过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); }; } }
上面代码可以看到Pipeline的destination就是dispatchToRouter函数返回的闭包:
$destination = function ($request) { $this->app->instance('request', $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() == '/' ? '/' : '/'.$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对象返回给客户端。这个过程还会涉及到我们以前介绍过的中间件过滤、服务解析、依赖注入方面的信息。
相关推荐:最新的五个Laravel视频教程
以上がLaravelルーティングとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。