在文章开头先解释下instance、bind、singleton、以及make的作用,下一篇文章会具体分析这几个函数的作用
你现在只要知道instance是用来向容器中注册已存在的实例
bind方法用来将服务绑定到容器,singleton是单例绑定,make是用来将注册的服务解析出来。
打开项目入口文件public/index.php可以见到
require __DIR__.'/../bootstrap/autoload.php'; //注册composer依赖的自动加载
接下来通过实例化Application得到容器
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
进入到Illuminate\Foundation\Application的构造函数
public function __construct($basePath = null)
{
$this->registerBaseBindings();//初始化容器
$this->registerBaseServiceProviders();//注册主要服务提供者
$this->registerCoreContainerAliases();//注册核心实例
if ($basePath) {
$this->setBasePath($basePath); //设置常用路径
}
}
根据以上代码,可以看到Laravel执行了以上四个函数,我们一步步跟入分析。
registerBaseBindings函数:
protected function registerBaseBindings()
{
static::setInstance($this);//将当前的Application实例作为容器(Application类继承自Container类实现了ArrayAccess接口)
$this->instance('app', $this);
$this->instance('Illuminate\Container\Container', $this);
//这里注册的两个对象都是同一个,给了两个不同的名称,一个app一个Illuminate\Container\Container
}
看下instance函数的实现
public function instance($abstract, $instance)
{
$abstract = $this->normalize($abstract);//normalize函数对传进来的$abstract判断是否为字符串,如果是字符串,开头有\就去掉,不是字符串就直接返回
//这里判断传递进来是否为数组,比如['app'=>'Illuminate\Container\Container']
if (is_array($abstract)) {
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);//存放到aliases[]数组中
}
unset($this->aliases[$abstract]);
$bound = $this->bound($abstract);//判断bindings、instances、aliases数组中是否已经存在当前对象
$this->instances[$abstract] = $instance;//将对象丢进instances数组中
if ($bound) {
$this->rebound($abstract);
}
}
registerBaseServiceProviders函数
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
可以看到注册了两个服务,我们看下register函数如何实现的
public function register($provider, $options = [], $force = false)
{ //判断注册了服务实例,有就直接返回
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
//如果传递的是字符串,那么会执行new 字符串($this) 实例化这个服务
if (is_string($provider)) {
$provider = $this->resolveProviderClass($provider);
}
//判断该实例是否有register方法,有就调用register方法
if (method_exists($provider, 'register')) {
$provider->register();
}
foreach ($options as $key => $value) {
$this[$key] = $value;
}
$this->markAsRegistered($provider);//见下面单独拆解
//判断当前框架是否已经启动,启动了就执行服务中的boot方法,bootProvider见下面单独拆解
if ($this->booted) {
$this->bootProvider($provider);
}
return $provider;
}
在执行到markAsRegistered这个函数的时候,Laravel已经执行了EventServiceProvider中的register函数。
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make('Illuminate\Contracts\Queue\Factory');
});
});
}
通过上面的register函数可以看到,他注册了一个名为events的单例。events为Dispatcher的实例
继续回头看markAsRegistered函数
protected function markAsRegistered($provider)
{
//这里给注册服务触发了一个事件监听器
$this['events']->fire($class = get_class($provider), [$provider]);
//将服务存放到serviceProviders
$this->serviceProviders[] = $provider;
//将当前服务标记已加载
$this->loadedProviders[$class] = true;
}
下面看bootProvider方法
protected function bootProvider(ServiceProvider $provider)
{
//判断是否存在boot方法,存在的话调用call方法
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
public function call($callback, array $parameters = [], $defaultMethod = null)
{
//这里的isCallableWithAtSign方法判断传递进来的$callback是否为字符串并且包含@符号 列如XXXServiceProvider@boot
if ($this->isCallableWithAtSign($callback) || $defaultMethod) {
//callClass函数通过@拆分再次调用call函数([$this->make('XXXServiceProvider'), ‘boot’], $parameters);
return $this->callClass($callback, $parameters, $defaultMethod);
}
//现在传递的参数为([$this->make('XXXServiceProvider'), ‘boot’], $parameters)
$dependencies = $this->getMethodDependencies($callback, $parameters);//处理函数依赖,比较复杂,见下面单独拆解
//处理完依赖后这个函数就可以被调用执行了
return call_user_func_array($callback, $dependencies);
}
···
getMethodDependencies函数详解
```php
protected function getMethodDependencies($callback, array $parameters = [])
{
$dependencies = [];
//通过调用getCallReflector得到方法的参数,并且循环调用addDependencyForCallParameter方法
foreach ($this->getCallReflector($callback)->getParameters() as $parameter) {
$this->addDependencyForCallParameter($parameter, $parameters, $dependencies);
}
return array_merge($dependencies, $parameters);
}
//getCallReflector获取给定方法的所有依赖项。
protected function getCallReflector($callback)
{ //判断传递进来的$callback是否是字符串,并且含有::字符 如className::method
if (is_string($callback) && strpos($callback, '::') !== false) {
//拆分为数组 如array('className','method')
$callback = explode('::', $callback);
}
//如果是$callback是数组格式
if (is_array($callback)) {
这里调用php的类反射,传入类名和方法名 返回方法的信息(方法可见性、方法参数列表,方法个数,反调用类的方法)
return new ReflectionMethod($callback[0], $callback[1]);
}
return new ReflectionFunction($callback);
}
protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters, &$dependencies)
{
//分别判断参数类型、如果参数是一个类,就调用make方法解析出来返回
if (array_key_exists($parameter->name, $parameters)) {
$dependencies[] = $parameters[$parameter->name];
unset($parameters[$parameter->name]);
} elseif ($parameter->getClass()) {
$dependencies[] = $this->make($parameter->getClass()->name);
} elseif ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
}
}
接下来看RoutingServiceProvider注册了什么内容:
public function register()
{
$this->registerRouter();
$this->registerUrlGenerator();
$this->registerRedirector();
$this->registerPsrRequest();
$this->registerPsrResponse();
$this->registerResponseFactory();
}
就是注册其他几个重要的服务,我们来挑一个分析下
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function ($app) {//调用share方法,参数传递一个匿名函数
return new Router($app['events'], $app);
});
}
//share方法接受匿名函数同样返回一个匿名函数
public function share(Closure $closure)
{
return function ($container) use ($closure) {
static $object;
if (is_null($object)) {
$object = $closure($container);
}
return $object;
};
}
所以调用返回的匿名函数为:
function ($container){
static $object;////这里用了static静态变量,共享这个变量
if (is_null($object)) {
$object = new Router($container['events'], $container);
}
return $object;
};
//最终的赋值语句为
$this->app['router']=上面的匿名函数
$this->app就是Container容器,它实现了ArrayAccess接口,并且实现了offsetExists、offsetGet、offsetSet、offsetUnset这几个方法
public function offsetSet($key, $value)
{
// If the value is not a Closure, we will make it one. This simply gives
// more "drop-in" replacement functionality for the Pimple which this
// container's simplest functions are base modeled and built after.
if (! $value instanceof Closure) {
$value = function () use ($value) {
return $value;
};
}
$this->bind($key, $value);
}
所以对app['router']赋值会调用offsetSet方法,可以看到里面调用了bind方法。
registerCoreContainerAliases方法
public function registerCoreContainerAliases()
{
$aliases = [
'app' => ['Illuminate\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'],
'auth' => ['Illuminate\Auth\AuthManager', 'Illuminate\Contracts\Auth\Factory'],
'auth.driver' => ['Illuminate\Contracts\Auth\Guard'],
'blade.compiler' => ['Illuminate\View\Compilers\BladeCompiler'],
'cache' => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'],
'cache.store' => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'],
'config' => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'],
'cookie' => ['Illuminate\Cookie\CookieJar', 'Illuminate\Contracts\Cookie\Factory', 'Illuminate\Contracts\Cookie\QueueingFactory'],
'encrypter' => ['Illuminate\Encryption\Encrypter', 'Illuminate\Contracts\Encryption\Encrypter'],
'db' => ['Illuminate\Database\DatabaseManager'],
'db.connection' => ['Illuminate\Database\Connection', 'Illuminate\Database\ConnectionInterface'],
'events' => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'],
'files' => ['Illuminate\Filesystem\Filesystem'],
'filesystem' => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'],
'filesystem.disk' => ['Illuminate\Contracts\Filesystem\Filesystem'],
'filesystem.cloud' => ['Illuminate\Contracts\Filesystem\Cloud'],
'hash' => ['Illuminate\Contracts\Hashing\Hasher'],
'translator' => ['Illuminate\Translation\Translator', 'Symfony\Component\Translation\TranslatorInterface'],
'log' => ['Illuminate\Log\Writer', 'Illuminate\Contracts\Logging\Log', 'Psr\Log\LoggerInterface'],
'mailer' => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'],
'auth.password' => ['Illuminate\Auth\Passwords\PasswordBrokerManager', 'Illuminate\Contracts\Auth\PasswordBrokerFactory'],
'auth.password.broker' => ['Illuminate\Auth\Passwords\PasswordBroker', 'Illuminate\Contracts\Auth\PasswordBroker'],
'queue' => ['Illuminate\Queue\QueueManager', 'Illuminate\Contracts\Queue\Factory', 'Illuminate\Contracts\Queue\Monitor'],
'queue.connection' => ['Illuminate\Contracts\Queue\Queue'],
'queue.failer' => ['Illuminate\Queue\Failed\FailedJobProviderInterface'],
'redirect' => ['Illuminate\Routing\Redirector'],
'redis' => ['Illuminate\Redis\Database', 'Illuminate\Contracts\Redis\Database'],
'request' => ['Illuminate\Http\Request', 'Symfony\Component\HttpFoundation\Request'],
'router' => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'],
'session' => ['Illuminate\Session\SessionManager'],
'session.store' => ['Illuminate\Session\Store', 'Symfony\Component\HttpFoundation\Session\SessionInterface'],
'url' => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'],
'validator' => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'],
'view' => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'],
];
foreach ($aliases as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
主要就是注册了一些核心的类的别名到容器中,这里解释一下这个函数的作用,Laravel自带的几个服务提供者里面已经注册了简短的别名(也就是这个数组的key),当你去make这些已经注册过实例的接口或者类,make函数会自动帮你把这些长的类名转化成前面的短的别名去解析。
比如在registerBaseBindings函数中的$this->instance('app', $this);,这里将Application实例注册到容器中,Application类继承自Container类,且实现了Container接口。那么当你去make(Application类或者是他的接口) make函数会根据这里的数组最终执行make('app')。
setBasePath
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
其实就是绑定一些常用的文件路径
回到bootstrap/app.php文件,看剩下的代码
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
这里我们看到容器又进行单例注册了中间件、路由、异常类。
这个时候容器已经真正全部初始化完成,可以开始处理用户的请求啦。
本文地址:https://www.blear.cn/article/laravel-Ioc-Container-source
转载时请以链接形式注明出处
评论