Laravel核心概念剖析
文章内容纯属个人对理解,如有不对的地方还望大佬多多指教。文章内容基于Laravel5.5
,加黑字体的是关键词可以帮助加深理解。
核心概念
Laravel的核心概念是服务容器和服务提供者,他们是框架的基础。框架提供的所有能力都由服务提供者引导绑定到容器中。容器主要定义了一些绑定和解析服务的方式,这些方式也可以理解为容器对外提供的接口。服务提供者则引导了容器如何去解析和实例化该服务。下面详细剖析服务容器和服务提供者的概念:
1、服务容器
服务容器是一个用于管理类依赖以及实现依赖注入的强有力工具,它主要有laravel/framework/src/Illuminate/Container/Container.php
类提供服务,文中具体代码可以在该类中找到。
1.1 绑定服务接口
容器类用来管理绑定关系的属性主要有两个bindings
和instance
,其中instance
属性的绑定关系是单例的。下面看绑定服务的几种方式:
// 1、通过bind绑定
$this->app->bind('redis.connection', function ($app) {
return $app['redis']->connection();
});
// 2、绑定单例
$this->app->singleton('redis', function ($app) {
$config = $app->make('config')->get('database.redis');
return new RedisManager(Arr::pull($config, 'client', 'predis'), $config);
});
// 3、直接绑定实例(单例)
$this->app->instance('test', new Test);
再看这三种绑定方式的具体实现:
1.1.1bind
函数
bind
在将绑定关系放入到了bindings
属性,此处只是绑定抽象和实现的关系,具体实现不会被实例化。
// 解释:将抽象$abstract绑定到具体的实现$concrete。$shared定义该实现是否为单例
public function bind($abstract, $concrete = null, $shared = false)
{
// 删除alias和instance的绑定,将抽象$abstract绑定到$bindings
$this->dropStaleInstances($abstract);
// 如果没有给出具体类型,则设置具体类型为该抽象类型
if (is_null($concrete)) {
$concrete = $abstract;
}
// 如果具体的实现$concrete不是匿名函数、则把它包装为匿名函数
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
// 将抽象的实现绑定到bindings属性
$this->bindings[$abstract] = compact('concrete', 'shared');
// 如果该抽象已在容器中解析,则触发该抽象绑定的回调。
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
1.1.2singleton
函数
可见singleton
函数调用的是bind
,第三个参数shared
参数为true
,指定该服务为单例。
// 绑定单例
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
1.1.3instance
函数
instance
函数绑定的是实例,也可以是其他基础类型,解析时作为单例处理。
// 绑定实例
public function instance($abstract, $instance)
{
// 删除abstractAliases的绑定
$this->removeAbstractAlias($abstract);
// 检查是否已绑定到$bindings、$instances或$aliases
$isBound = $this->bound($abstract);
unset($this->aliases[$abstract]);
$this->instances[$abstract] = $instance;
// 如果该抽象已绑定过(属于重复绑定),则触发该抽象绑定的回调。
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
关于绑定大概就这些,此处先不考虑服务延迟加载,上下文绑定等等…先抛开复杂的处理看下绑定的本质,下面看下对解析的处理。
2.2 解析服务接口
解析对外提供的函数有app()->make()
,app()->makeWith()
,resolve()
,app()
,app()->get()
这几个,其中app()->get()
会提前判断要解析的服务是否存在,不存在话抛出EntryNotFoundException
异常,其他接口则直接抛出ReflectionException
异常。最终的解析逻辑都一样,主要依赖容器中的resolve
和build
函数,下面具体看下这两个函数的实现:
2.2.1 resolve
函数
protected function resolve($abstract, $parameters = [])
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// 对单例服务的处理,不需要处理上下文的情况下,如果存在直接返回,不会重新new一个新的实例
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
// 尝试从bindings中获取具体实现
$concrete = $this->getConcrete($abstract);
// 如果抽象$abstract和实现$concrete完全相等 或者 $concrete是匿名函数,则用build去解析,否则将具体实现$concrete再走一遍make,最终也是走本接口,可以看作是一个递归调用
if ($this->isBuildable($concrete, $abstract)) {
// 这快处理了类的实例化,依赖检查等,下面具体看
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// 对这个服务做一些扩展处理
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// 对单例的处理
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
// 触发解析回调
$this->fireResolvingCallbacks($abstract, $object);
// 标记抽象已被解析
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
resolve
它主要做了对单例的处理,上下文检查,解析事件的触发和服务的扩展性处理。
2.2.2 build
函数
此处是依赖处理和实例化对象的关键。
public function build($concrete)
{
// 如果具体实现是个匿名函数,直接调用匿名函数,由其返回实现即可。
if ($concrete instanceof Closure) {
// getLastParameterOverrid的用意是?
return $concrete($this, $this->getLastParameterOverride());
}
// 获取类的反射实例
$reflector = new ReflectionClass($concrete);
// 不可以实例化时抛出异常
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
// 获取类的构造函数
$constructor = $reflector->getConstructor();
// 如果没有构造函数,直接new出实例即可
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 获取构造函数依赖项
$dependencies = $constructor->getParameters();
// 解析构造函数依赖项(有兴趣的可以看下这个函数的实现)
$instances = $this->resolveDependencies(
$dependencies
);
array_pop($this->buildStack);
//创建一个类实例,并注入依赖项
return $reflector->newInstanceArgs($instances);
}
到这里,服务绑定和服务解析就差不多了。这些东西有什么用呢?下面再看服务提供者中怎么使用它们。
2、服务提供者
所有的服务提供者都继承于Illuminate\Support\ServiceProvider
抽象类,该抽象类对子类暴露了容器变量$app
用来操作容器。在服务提供者中包会含一个 register
和一个 boot
函数。在 register
方法中, 你可以调用容器对外提供的绑定接口注册服务。boot
函数会在所有服务注册完之后依次调用。下面看一个具体的服务提供者例子
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
/**
* 继承自ServiceProvider类
*/
class RiakServiceProvider extends ServiceProvider
{
/**
* 是否延时加载该服务提供者,此处意味着在框架启动阶段不会调用register函数进行服务绑定,只会将该服务放入延迟加载列表,这些数据会放在bootstrap/cache/services.php文件中,该文件包含了所有服务提供者提供的所有服务,框架每次启动时都会检查是否需要重新生成。
* @var bool
*/
protected $defer = true;
// 在服务容器里注册(框架启动阶段调用)
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection(config('riak'));
});
$this->app->bind('redis', function ($app) {
return new Redis;
});
$this->app->instance('test', new Test);
}
// 所有的服务提供者加载完后,会依次调用boot函数
public function boot()
{
... code
}
/**
* 获取提供器提供的服务。
* 此处用于生成bootstrap/cache/services.php文件时获取该提供者提供的服务列表
* @return array
*/
public function provides()
{
return [Connection::class, 'redis', 'test'];
}
}
添加完服务提供者之后,需要将该提供者在config/app.php
配置文件中注册,才会生效。然后你就可以在框架的任何地方使用了。
3、具体使用
怎么使用呢?这要看框架支持怎样的依赖注入了,主要有以下几种方式:
3.1、构造函数注入
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController
{
/**
* TestController constructor.
* 这里$request变量已经是Request的实例了,不用显示的new(也不能去new,Request构造函数也会依赖其他服务,这些依赖处理交给框架的服务容器去解析就好了)
*/
public function __construct(Request $request)
{
dump($request->all());
}
}
3.2、函数参数注入
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController
{
/**.
* 和构造函数注入同理
*/
public function hello(Request $request)
{
dump('Hello'.$request->name);
}
}
3.3、手动解析
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController
{
public function test(Request $request)
{
// 1、解析服务,这里取出来的redis对象是否为单例取决于哪种方式注册
$redis = app()->make('redis');
$redis = app()->makeWith('redis', ['param' => 'test']);
$redis = resolve('redis');
$redis = app('redis');
$redis = app()->get('redis');
// 2、实例化类
app(\App\Test::class, ['var1' => 'var1'])
app(\App\Test::class, ['var2' => 'var2', 'var1' => 'var1'])
}
}
实例化类为什么不直接new
呢?因为这样实例化,不用处理Test
的依赖Request
。
// 示例类
<?php
namespace App;
use Illuminate\Http\Request;
class Test
{
public $var1;
public $var2;
/**
* Test constructor. * @param $var1
* @param $var2
*/
public function __construct(Request $request, $var1, $var2 = 'var2')
{
$this->var1 = $var1;
$this->var2 = $var2;
// 不用处理,交给容器解析
dump($request->all());
}
}
4、总结
文章主要讲了容器对外提供的绑定和解析服务的几种方式,随后讲了服务提供者对容器的使用,也给了具体的例子,希望对大家有所帮助。文中只给出了一些基本的实现逻辑,关于上下文的处理,有兴趣的可以结合社区文档继续深挖。
本作品采用《CC 协议》,转载必须注明作者和本文链接