服务提供者原理解析
服务提供者是所有 Laravel 应用程序引导中心。应用程序自定义的服务、第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引导(boot)的。
class AuthServiceProvider extends ServiceProvider {
/*
** Register the service provider.
** @return void
*/
public function register() {
$this‐>registerAuthenticator();
$this‐>registerUserResolver();
$this‐>registerAccessGate();
$this‐>registerRequestRebindHandler();
$this‐>registerEventRebindHandler();
}
/*
** Register the authenticator services.
** @return void
*/
protected function registerAuthenticator() {
$this‐>app‐>singleton('auth', function ($app) {
$app['auth.loaded'] = true;
return new AuthManager($app);
});
$this‐>app‐>singleton('auth.driver', function ($app) {
return $app['auth']‐>guard();
});
}
protected function registerUserResolver() {
$this‐>app‐>bind(
AuthenticatableContract::class,
function ($app) {
return call_user_func($app['auth']‐>userResolver());
}
);
}
protected function registerAccessGate() {
$this‐>app‐>singleton(
GateContract::class,
function ($app) {
return new Gate($app, function () use ($app) {
return call_user_func($app['auth']‐>userResolver());
});
});
}
register解析
laravel注册和引导应用需要的服务是发生在寻找路由处理客户端请求之前的Bootstrap阶段的,在框架的入口文件里我们可以看到,框架在实例化了Application对象后从服务容器中解析出了HTTP Kernel对象
$kernel = $app‐>make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel‐>handle(
$request = Illuminate\Http\Request::capture()
);
然后在Kernel处理请求时会先让请求通过中间件然后在发送请求给路由对应的控制器方法, 在这之前有一个 BootStrap阶段来引导启动Laravel应用程序服务,如下面代码所示。
public function handle($request) {
‐‐‐‐‐‐
//引导BootStrap来进行应用程序的启动方法
$response = $this‐>sendRequestThroughRouter($request);
‐‐‐‐‐‐
return $response;
}
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());
}
//引导启动Laravel应用程序
public function bootstrap() {
if (! $this‐>app‐>hasBeenBootstrapped()) {
/**依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数
$bootstrappers = [
//检查环境,加载 .env 文件,将配置加载到内存中 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
// 加载应用配置,把 config 目录下的所有配置文件 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
//修改系统默认异常处理函数(如语法错误等),将其替代为 Laravel 的异常处理。这就是为什么在开发 Laravel 应 用时,语法出现错误,出现对于错误界面的缘故 \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
//注册 Facades ,将 Laravel 内的 Facades 类中的静态属性 $app 赋值为 服务容器 \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
//注册服务提供者,区分即时实例化和延时实例化,即时的不需要标识别名.而延时的需要标识别名,主要对 config/app.php 中 providers 键对应值(服务器提供者类全名)进行分类 \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
//执行服务提供者的 boot 方法,其中路由文件加载和读取 \Illuminate\Foundation\Bootstrap\BootProviders::class, ];
*/
$this‐>app‐>bootstrapWith($this‐>bootstrappers());
}
}
启动应用程序的最后两步就是注册服务提供者和启动提供者。
服务提供器的注册类是由 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 提供支持,用于加载所有服务提供器的 register 函数,并保存延迟加载的服务的信息,以便实现延迟加载。
class RegisterProviders {
/*
** Bootstrap the given application.
** @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app) {
//调用了Application的registerConfiguredProviders()
$app‐>registerConfiguredProviders();
}
}
/*
** 注册所有配置的提供程序
* @return void
*/
public function registerConfiguredProviders() {
$providers = Collection::make($this‐>config['app.providers'])
‐>partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\');
});
$providers‐>splice(1, 0, [$this‐>make(PackageManifest::class)‐>providers()]); //所有服务提供器都在配置文件 app.php 文件的 providers 数组中。类 ProviderRepository 负责所有的服务加载
(new ProviderRepository($this, new Filesystem, $this‐>getCachedServicesPath())) ‐>load($providers‐>collapse()‐>toArray()); }
public function load(array $providers) {
//loadManifest()会加载服务提供器缓存文件services.php,如果框架是第一次启动时没有这个文件的,或者是 缓存文件中的providers数组项与config/app.php里的providers数组项不一致都会编译生成services.php。
$manifest = $this‐>loadManifest();
if ($this‐>shouldRecompile($manifest, $providers)) {
$manifest = $this‐>compileManifest($providers);
}
foreach ($manifest['when'] as $provider => $events) {
$this‐>registerLoadEvents($provider, $events);
}
foreach ($manifest['eager'] as $provider) {
$this‐>app‐>register($provider);
}
$this‐>app‐>addDeferredServices($manifest['deferred']);
}
//判断是否需要编译生成services文件
public function shouldRecompile($manifest, $providers) {
return is_null($manifest) || $manifest['providers'] != $providers;
}
//编译生成文件的具体过程
protected function compileManifest($providers) {
$manifest = $this‐>freshManifest($providers);
foreach ($providers as $provider) {
$instance = $this‐>createProvider($provider);
if ($instance‐>isDeferred()) {
foreach ($instance‐>provides() as $service) {
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance‐>when();
}else {
$manifest['eager'][] = $provider;
}
}
return $this‐>writeManifest($manifest);
}
/*
** 创建新服务清单数据结构
** @param array $providers
* @return array
*/
protected function freshManifest(array $providers) {
return [
'providers' => $providers,
'eager' => [],
'deferred' => []
];
}
- 缓存文件中 providers 放入了所有自定义和框架核心的服务。
- 如果服务提供器是需要立即注册的,那么将会放入缓存文件中 eager 数组中。
- 如果服务提供器是延迟加载的,那么其函数 provides() 通常会提供服务别名,这个服务别名通常是 向服务容器中注册的别名,别名将会放入缓存文件的 deferred 数组中,与真正要注册的服务提供器组成一个键值对。
- 延迟加载若由 event 事件激活,那么可以在 when 函数中写入事件类,并写入缓存文件的 when 数组中。
生成最后的格式如下:
array (
'providers' => array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
...
),
'eager' => array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Cookie\\CookieServiceProvider',
...
),
'deferred' => array (
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
...
),
'when' => array (
'Illuminate\\Broadcasting\\BroadcastServiceProvider' => array ( ),
...
)
延迟服务提供器除了利用 IOC 容器解析服务方式激活,还可以利用 Event 事件来激活。
protected function registerLoadEvents($provider, array $events) {
if (count($events) < 1) { return; }
$this‐>app‐>make('events')‐>listen($events, function () use ($provider) {
$this‐>app‐>register($provider);
});
}
public function addDeferredServices(array $services) {
$this‐>deferredServices = array_merge($this‐>deferredServices, $services);
}
即时注册服务提供器 :
- 解析服务提供器
- 调用服务提供器的 register 函数
- 标记当前服务提供器已经注册完毕
- 若框架已经加载注册完毕所有的服务容器,那么就启动服务提供器的 boot 函数,该函数由于是 call 调用, 所以支持依赖注入。
public function register($provider, $force = false) { //判断当前服务提供器是否被注册过,如注册过直接返回对象 if (($registered = $this‐>getProvider($provider)) && ! $force) { return $registered; } // 服务提供者数据类型是字符串吗? 作为解析服务提供器 if (is_string($provider)) { $provider = $this‐>resolveProvider($provider); } //调用服务的注册方法 $provider‐>register(); // 服务提供者对象有 bindings 方法吗,有就绑定到容器里面 if (property_exists($provider, 'bindings')) { foreach ($provider‐>bindings as $key => $value) { $this‐>bind($key, $value); } } // 服务提供者对象有 singletons 属性吗 if (property_exists($provider, 'singletons')) { foreach ($provider‐>singletons as $key => $value) { $this‐>singleton($key, $value); } } //既然服务提供者已完成注册,那么记录一下,向 serviceProviders 属性和 loadedProviders 进行赋值 $this‐>markAsRegistered($provider); //是否执行服务提供者的 boot 方法,初始化不执行 if ($this‐>isBooted()) { $this‐>bootProvider($provider); } return $provider; } //获取已经注册的服务 public function getProvider($provider) { return array_values($this‐>getProviders($provider))[0] ?? null; } protected function markAsRegistered($provider) { //这个属性在稍后booting服务时会用到 $this‐>serviceProviders[] = $provider; $this‐>loadedProviders[get_class($provider)] = true; } protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, 'boot')) { return $this‐>call([$provider, 'boot']); } }
boot解析
Application的启动由类 \Illuminate\Foundation\Bootstrap\BootProviders 负责:
BootProviders调用下面方法就引导应用 Application 的 serviceProviders 属性中记录的所有服务提供器,就是依次调用这些服务提供器的boot方法,引导完成后$this>booted = true 就代表应用Application正式启动了,然后就开始处理请求了,之所以等到所有服务提供器都注册完后再来进行引导是因为有可能在一个服务提供器的boot方法里调用了其他服务提供器注册的服务,所以需要等到所有即时注册的服务提供器都register完成后再来boot。class BootProviders { public function bootstrap(Application $app) { $app‐>boot(); } } class Application extends Container implements ApplicationContract, HttpKernelInterface { public function boot() { if ($this‐>isBooted()) { return; } $this‐>fireAppCallbacks($this‐>bootingCallbacks); array_walk($this‐>serviceProviders, function ($p) { $this‐>bootProvider($p); }); $this‐>booted = true; $this‐>fireAppCallbacks($this‐>bootedCallbacks); } //启动给定的服务提供程序。 protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, 'boot')) { return $this‐>call([$provider, 'boot']); } } }
服务的约束与契约解析
如果调用的类里面是基于一个来自闭包的具体的缓存类,如果包的对象变了,那么相应的,我们的代码必须做修改。 类似的,如果我们想要替换底层的缓存技术(Memcached)为别的技术实现(Redis),这样将再一次不得不修改我们的代码库。我们的代码库应该并不知道谁提供的数据或者数据是怎么提供的。 所以我们可以基于一种简单的、与提供者无关的接口来优化我们的代码,从而替代上述那种实现
Laravel为所有核心功能都定义契约接口的目的就是为了让开发者能够根据自己项目的需要自己定义实现类,而对于这些接口的消费者(比如:Controller、或者内核提供的 CacheManager这些)他们不需要关心接口提供的方法具体是怎么实现的, 只关心接口的方法能提供什么功能然后去使用这些功能就可以了,我们可以根据需求在必要的时候为接口更换实现类,而消费端不用进行任何改动。//类库耦合在一起 <?php namespace App\Orders; class Factory { /* ** 缓存 */ protected $cache; /* ** 创建一个新的Factory实例 ** @param \SomePackage\Cache\Memcached $cache * @return void */ public function __construct(\SomePackage\Cache\Memcached $cache) { $this‐>cache = $cache; } /* ** 通过ID获取订单 ** @param int $id * @return Order */ public function find($id) { if ($this‐>cache‐>has($id)) { // } } }
//通过接口的契约的方式来约束具体实现类 namespace App\Orders; use Illuminate\Contracts\Cache\Factory as Cache; class Repository { /* ** 创建一个新的Factoryy实例 ** @param Cache $cache * @return void */ public function __construct(Cache $cache) { $this‐>cache = $cache; } }
服务的快速获取之facade
在路由文件中经常会出现Route::get()这样的写法,但实际上并没有Route类,Route只是 \Illuminate\Support\Facades\Route::class外观类的别名,这样取个别名只是为了简化作用,通过AliasLoader 下面 register()里面的load() 使用的PHP内置函数class_alias(string alias)来给类设置别名。看下RegisterFacades::bootstrap()的源码: ```php public function bootstrap(Application $app) { Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); //$app‐>make(‘config’)‐>get(‘app.aliases’, [])是从config/app.php中读取’aliases’的值,然后注册外观类 的别名,注册的外观类有 AliasLoader::getInstance(array_merge( $app‐>make(‘config’)‐>get(‘app.aliases’, []), $app‐>make(PackageManifest::class)‐>aliases() ))‐>register(); } // 清除所有已解决的实例 public static function clearResolvedInstances() { static::$resolvedInstance = []; } //设置App的实例 public static function setFacadeApplication($app) { static::$app = $app; } /**
- 加载类别名 ** @param string $alias
- @return bool|null
*/
public function load($alias) {
if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
$this‐>loadFacade($alias);
return true;
}
if (isset($this‐>aliases[$alias])) {
return class_alias($this‐>aliases[$alias], $alias);
}
}
<a name="R8vvQ"></a> ### facade 与契约的关系 **契约**:Laravel 的契约是指框架提供的一系列定义核心服务的接口,关键字是接口。 <br />**门面**:叫Facade门面。又称为 静态代理。相当于给一个类起一个别名,调用时不需要再使用完整路径来调用 Laravel 门面为 Laravel 服务的使用提供了便捷方式——不再需要从服务容器中类型提示 和 契约(Contracts)解析即可直接通过静态 门面(Facade) 调用。<br />不同于 门面(Facade) 不需要再构造器中进行类型提示,契约(Contracts) 允许你在类中定义显式的依赖。有些开发者喜欢门面(Facade)带来的便捷,也有些开发者倾向于使用契约(Contracts)。 <br />门面不需要显示的声明依赖后来调用类库,而契约就需要来类里面进行声明。 <a name="YacZb"></a> ## 路由服务提供者 <a name="ATVoW"></a> ### 基本流程 路由是外界访问Laravel应用程序的通路或者说路由定义了Laravel的应用程序向外界提供服务的具体方式:通过指定的URI、HTTP请求方法以及路由参数(可选)才能正确访问到路由定义的处理程序。无论URI对应的处理程序是一个简单的闭包还是说是控制器方法没有对应的路由。外界都访问不到它们。 <br />服务提供者来完成路由的绑定router这个服务是在实例化应用程序Application时在构造方法里通过注册 RoutingServiceProvider 时绑定到服务容器里的: ```php //bootstrap/app.php $app = new Illuminate\Foundation\Application( $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ); //Application: 构造方法 public function __construct($basePath = null) { //从 `bootstrap\app.php` 传递过来的 $basePath 是有值的 if ($basePath) { //注册基本路径 $this‐>setBasePath($basePath); } //注册应用基本绑定,如 app 指向容器对象 $this‐>registerBaseBindings(); // 注册基本服务提供者 $this‐>registerBaseServiceProviders(); //注册标识 bindings 属性标识的获取套路 $this‐>registerCoreContainerAliases(); } //Application: 注册基础的服务提供器 /** **注册所有基本服务提供程序。 * @return void */ 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); }); }
路由加载
路由文件中的路由到底是怎么注册进入到路由组件的呢?
上面可以看到在\Illuminate\Routing\RoutingServiceProvider::register() 类中往容器对象的 $bindings 数组属性 以 keyvalue 形式注册进去,然后就会由服务提供者进行相关服务的注册,并通过引导服务( \Illuminate\Foundation\Bootstrap\BootProviders::class)下面的 Boot 方法来引导每个服务的执行作。
恰好就会去调用 \App\Providers\RouteServiceProvider::boot() 方法加载路由列表。在这当中会调用父类的boot方法,获取到先去容器里面的Router的注册类
laravel 首先去寻找路由的缓存文件,没有缓存文件再去进行加载路由。缓存文件一般在 bootstrap/cache/routes.php 文件中。
方法loadRoutes会调用map方法来加载路由文件里的路由,map这个函数在App\Providers\RouteServiceProvider类中,这个类继承自Illuminate\Foundation\Support\Providers\RouteServiceProvider。
通过map方法我们能看到 laravel将路由分为两个大组:api、web。这两个部分的路由分别写在两个文件中:routes/web.php、 routes/api.php。 ```php //app\Providers\RouteServiceProvider.php class RouteServiceProvider extends ServiceProvider { public function boot() { parent::boot(); } public function map() { $this‐>mapApiRoutes(); $this‐>mapWebRoutes(); } protected function mapWebRoutes() { Route::middleware(‘web’)
} protected function mapApiRoutes() { Route::prefix(‘api’)‐>namespace($this‐>namespace) ‐>group(base_path('routes/web.php'));
} } //Illuminate\Foundation\Support\Providers\RouteServiceProvider.php namespace Illuminate\Foundation\Support\Providers; class RouteServiceProvider extends ServiceProvider { public function boot() { $this‐>setRootControllerNamespace(); if ($this‐>app‐>routesAreCached()) {‐>middleware('api') ‐>namespace($this‐>namespace) ‐>group(base_path('routes/api.php'));
} else {$this‐>loadCachedRoutes();
} } protected function loadCachedRoutes(){ $this‐>app‐>booted(function () {$this‐>loadRoutes(); $this‐>app‐>booted(function () { $this‐>app['router']‐>getRoutes()‐>refreshNameLookups(); $this‐>app['router']‐>getRoutes()‐>refreshActionLookups(); });
}); } protected function loadRoutes() { if (method_exists($this, ‘map’)) {require $this‐>app‐>getCachedRoutesPath();
} } } class Application extends Container implements ApplicationContract, HttpKernelInterface { //判断应用程序是否存在路由缓存 public function routesAreCached() { return $this[‘files’]‐>exists($this‐>getCachedRoutesPath()); } //获取路由缓存文件的路径 public function getCachedRoutesPath() { return $this‐>normalizeCachePath(‘APP_ROUTES_CACHE’, ‘cache/routes.php’); }$this‐>app‐>call([$this, 'map']);
<a name="KMKh6"></a>
### 路由注册
通常都是用Route这个Facade调用静态方法get, post, head, options, put, patch, delete......等来注册路由,上面这些静态方法其实是调用了Router类里的方法完成路由注册到路由集合。(RouteCollection) 在前面的处理过程中都是以包含路由文件的内容,然后在通过门面获取router实例来进行路由注册到路由表。
```php
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);
}
//注册路由到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的第三个参数action可以闭包、字符串或者数组,数组就是类似[‘uses’ => ‘Controller@action’, ‘middleware’ => ‘…’]这种形式的。如果action是Controller@action类型的路由将被转换为action 数组, convertToControllerAction执行完后action的内容为:
[
'uses' => 'App\Http\Controllers\HomeController@someAction',
'controller' => 'App\Http\Controllers\HomeController@someAction'
]
接下来可以看到把命名空间补充到了控制器的名称前组成了完整的控制器类名,action数组构建完成接下里就是创建路由了,创建路由即用指定的HTTP请求方法、URI字符串和action数组来创建\Illuminate\Routing\Route类的实例
protected function newRoute($methods, $uri, $action) {
return (new Route($methods, $uri, $action))
‐>setRouter($this)
‐>setContainer($this‐>container);
}
路由创建完成后将Route添加到RouteCollection中去:
protected function addRoute($methods, $uri, $action) {
return $this‐>routes‐>add($this‐>createRoute($methods, $uri, $action));
}
router的$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的四个属性
routes中存放了HTTP请求方法与路由对象的映射:
[ 'GET' => [ $routeUri1 => $routeObj1 ... ]... ]
allRoutes属性里存放的内容时将routes属性里的二位数组变成一位数组后的内容:
[ '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的分发就是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);
$this‐>originalParameters = $this‐>parameters;
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) {
$path = '/'.ltrim($request‐>decodedPath(), '/');
preg_match($this‐>route‐>compiled‐>getRegex(), $path, $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对象返回给客户端。