Laravel学习笔记之IOC容器源码分析


在文章开头先解释下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

转载时请以链接形式注明出处

评论