laravel核心概念总结
这篇博客在草稿箱堆了挺久的,原本还想趁着过年期间写完这篇博客的,后来又没写成,不过还是趁着上班后的几天写好了,想着使用laravel希望能多了解其内部源码的实现原理,所以结合文档和源码写了这篇博客。以下内容如果有错欢迎指出。
服务容器
服务容器的概念理解可以查看这里我写的服务容器相关概念。
结合文档和源码来分析一下laravel是如何实现的
根据文档的介绍,Laravel 服务容器是一个用于管理类依赖以及实现依赖注入的强有力工具。而服务容器正是laravel实现功能的核心利器。
laravel的服务容器提供了几个比较核心的方法
- singleton($abstract, $concrete = null)
- bind($abstract, $concrete = null, $shared = false)
- make($abstract, array $parameters = [])
实际上singleton和bind调用的函数实现是相同的,只是在调用参数上,将$shared变量置为true,通过$shared来判断是否为单例对象
/**
* 添加单例类到容器中
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
查看bind()方法,根据文档所给出的内容,bind 方法的第一个参数为要绑定的类 / 接口名,第二个参数是一个返回类实例的 Closure
/**
* 添加绑定类到容器中
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
//删除容器实例和别名中$abstract的值
$this->dropStaleInstances($abstract);
//判断是否为空,若为空则等于$abstract,后续容器可通过反射类获取类实例
if (is_null($concrete)) {
$concrete = $abstract;
}
//判断$concrete是否为闭包,如果不为闭包,则转换成闭包的形式
if (! $concrete instanceof Closure) {
if (! is_string($concrete)) {
throw new \TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
}
//将$concrete以闭包形式返回,这里调用反射类获取类实例
$concrete = $this->getClosure($abstract, $concrete);
}
//将当前$abstract以key=>value的形式绑定到$this->binding变量中,其中$concrete为返回类实例,$shared用于判断是否为单例对象
$this->bindings[$abstract] = compact('concrete', 'shared');
//判断当前实例是否已存在,判断是否需要回调重新更新类实例
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
再来看看make()方法,从容器中解析出类实例,也是容器中比较核心的概念,其本质是调用了resolve方法实现,可查看文档make方法
/**
* 从容器中解析当前实例
* @param string $abstract
* @param array $parameters
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
以下为resolve的源码实现
/**
* 解析指定类到容器中
* @param string $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
//查看$abstract是否为别名,如果是则返回真正的类名
$abstract = $this->getAlias($abstract);
//获取返回类实例
$concrete = $this->getContextualConcrete($abstract);
//
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
//判断单例数组中是否已实例过,若已实例过则直接返回,这样在整个框架的生命周期中,单例对象永远只有一个
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
//判断类实例是否为空,为空则查看$this->bingding绑定对象中是否存在实例,若没有则等于$abstract类名
if (is_null($concrete)) {
$concrete = $this->getConcrete($abstract);
}
//判断当前类是否可被解析,可以则调用build()方法对闭包或类名进行解析,不可解析则回调make()方法,其中build()方法使用反射机制是实现框架依赖注入的核心
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
//遍历定义的扩展,若已定义扩展则实例返回扩展闭包,这里应该是为了方便后续更改某些类的实现,所以在这里遍历扩展
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
//判断是否为单例对象,为单例则将实例添加到$this->instances实例对象中
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
//将$this->resolved中的类名置为true,表示已解析完成
$this->resolved[$abstract] = true;
array_pop($this->with);
//返回实例
return $object;
}
最后再来看看依赖注入的核心build()方法
/**
* 实例化具体对象类
* @param \Closure|string $concrete
* @return mixed
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function build($concrete)
{
//在上述讲解,$concrete的值是一个闭包或者类名,这里首先判断值是否为闭包,若为闭包则直接执行,其中$this->getLastParameterOverride()方法用于获取闭包对应参数
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
//生成$concrete的反射类,若类不存在则抛出异常
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
//isInstantiable()为反射类中的函数,用于判断类是否可以被实例化,若类为接口类和抽象类则不能被实例化,则抛出异常
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
//获取类的构造函数
$constructor = $reflector->getConstructor();
//判断构造函数是否为空,若为空则说明该类没有依赖参数,则直接实例化返回
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
//获取构造函数参数
$dependencies = $constructor->getParameters();
//解析获取类依赖参数,若解析失败则抛出异常
try {
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
//通过newInstanceArgs()创建类实例并返回
return $reflector->newInstanceArgs($instances);
}
服务提供者
根据文档所讲解的,服务提供者是所有 Laravel 应用程序的引导中心。你的应用程序,以及 通过服务器引导的 Laravel 核心服务都是通过服务提供器引导。但是,「引导」是什么意思呢? 通常,我们可以理解为注册,比如注册服务容器绑定,事件监听器,中间件,甚至是路由。服务提供者是配置应用程序的中心。
在config/app.php
文件中,我们可以查看到所有服务提供者
这里随便查看一个服务提供者,如以下RedisServiceProvider为使用redis操作的类,根据文档定义我们只需要在register()中定义singleton(),bind()将实例注册到容器中,在这里是将redis操作类绑定为单例对象,redis连接类简单绑定到容器中。同时RedisServiceProvider继承了\Illuminate\Contracts\Support\DeferrableProvider
接口并实现provides(),使服务可以被延时加载,注册绑定的服务只有真的需要时才会被加载
深入分析服务提供者,查看源码是如何实现的
/**
* 将配置中的服务提供者注册到容器中
* @return void
*/
public function registerConfiguredProviders()
{
//用于获取config/app.php中providers变量中的所有服务提供者
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return strpos($provider, 'Illuminate\\') === 0;
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
//加载注册服务提供者
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
register()为注册服务器,将服务提供者中绑定的服务添加到容器中
/**
* 将单个服务提供者中的服务注册到容器中
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $force = false)
{
//检查该服务提供者是否已被加载过,若加载过则直接返回,如果$force为true则该服务提供者会重新加载
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
//判断如果传入的$provider变量为字符串,则实例化服务提供者类
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
//执行服务提供者的register(),实际上就是注册绑定服务到容器中
$provider->register();
//检查类中是否定义$bindings变量,如果定义则将其绑定到容器中,可查看文档https://learnku.com/docs/laravel/7.x/providers/7455#311d29
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
//检查类中是否定义$singletons变量,如果定义则将其绑定到容器中,可查看文档https://learnku.com/docs/laravel/7.x/providers/7455#311d29
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
//将该服务提供者标记为已加载
$this->markAsRegistered($provider);
//检查服务提供者是否定义boot()方法,若定义则会被执行,可查看文档https://learnku.com/docs/laravel/7.x/providers/7455#ea148f
if ($this->isBooted()) {
$this->bootProvider($provider);
}
//返回服务提供者实例
return $provider;
}
Facades(门面)
根据文档描述Facades 为应用的 服务容器 提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法。
Facades流程如下所示:
以Log门面类为例,查看其代码,代码只定义了getFacadeAccessor()方法返回log
字符串,实际上我们使用Log日志门面通常为Log::info()
这样的形式,当调用静态方法时会触发__callStatic魔术方法
public static function __callStatic($method, $args)
{
//获取容器中指定的实例,如上述为log,则返回容器中绑定实例中log的实例
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
//调用实例中指定方法
return $instance->$method(...$args);
}
以上就是Facades(门面)的概念,也比较简单,当然具体的内容也可以看文档Facades
Contracts(契约)
Contracts实际上就是定义一组接口,其理念符合基于接口而非实现编程的设计思想,这里就没什么好说的了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: