Laravel源码 --容器初始化和服务绑定

Laravel容器初始化

1.缘起

在public目录下的入口文件index.php中,除了加载autoload.php外,接下来就是进行Laravel容器的初始化:
$app = require_once __DIR__.'/../bootstrap/app.php';
此处的$app就是laravel容器。
接下来加载内核Kernel函数,处理请求消息,并返回响应;

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

2. Application初始化

bootstrap目录下的app.php文件中,首先需要创建Application

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

由此可看到先执行了Application的构造方法

2.1 Application 构造方法
class Application extends Container implements ApplicationContract, HttpKernelInterface

先看Application的定义:继承Container类,并实现ApplicationContract和HttpKernelInterface 接口

Container类中定义了很多重要的属性:$instance $bindings $instances数组
也实现了bing,make,instance等很多重要的方法。

    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath); 
        }

        $this->registerBaseBindings();

        $this->registerBaseServiceProviders();

        $this->registerCoreContainerAliases();
    }

Application的构造方法源码如上,可以看到总共做了 四件事情,每件事情的意义是什么,我们从源码中进行剖析。

2.1.1 为容器设置base path

Application.php中设置basepath的代码如下。可以看到将资源路径,存储路径均进行了instance操作。

    public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, '\/');

        $this->bindPathsInContainer();

        return $this;
    }

    /**
     * Bind all of the application paths in the container.
     *
     * @return void
     */
    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());
    }

那么instance操作具体完成了什么动作呢?我们来看父类Container类中的方法:注意这个instance方法是个公共方法,很多地方在调用;我们先按照路径绑定的方式解读。

/**
     * Register an existing instance as shared in the container.
     *
     * @param  string  $abstract
     * @param  mixed   $instance
     * @return mixed
     */
    public function instance($abstract, $instance)
    {
        $this->removeAbstractAlias($abstract);  //判断传入的abstract 是否在Alias中,如果不在则直接返回,如果在,则从AbstractAlias中移除

        $isBound = $this->bound($abstract); // 判断传入的abstract  是否在bindings,instances,aliases中,如果在其中一个,则返回true

        unset($this->aliases[$abstract]);  // 从别名数组中删除

        // We'll check to determine if this type has been bound before, and if it has
        // we will fire the rebound callbacks registered with the container and it
        // can be updated with consuming classes that have gotten resolved here.
        $this->instances[$abstract] = $instance; //添加到instances中

        if ($isBound) { 
            $this->rebound($abstract);      //如果isBound为true,则重新bound; rebound中调用了make方法,后面我们重点看下
        }

        return $instance;
    }

    /**

总结:对路径进行的操作,最终结果实际上是将所有路径,绑定到容器的instances数组中;
样例结果如下:

$this->instances['path'] = '/var/www/laravel/app';  
$this->instances['path.base'] = '/var/www/laravel';  
$this->instances['path.lang'] = '/var/www/laravel/resources/lang';  
$this->instances['path.config'] = '/var/www/laravel/config';  
$this->instances['path.public'] = '/var/www/laravel/public';  
$this->instances['path.storage'] = '/var/www/laravel/storage';  
$this->instances['path.database'] = '/var/www/laravel/database';  
$this->instances['path.resources'] = '/var/www/laravel/resources';  
$this->instances['path.bootstrap'] = '/var/www/laravel/bootstrap';  
2.1.2 完成基础绑定registerBaseBindings();
  protected function registerBaseBindings()
    {
        static::setInstance($this);   //将容器赋值给属性instance   static::$instance = $container;   可以静态访问容器

        $this->instance('app', $this); //

        $this->instance(Container::class, $this);

        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

总结:基础绑定将容器本身设置给自己的静态属性,并将app和container类追加到instances数组,value都指向了容器本身

$this->instances['app'] = object(Illuminate\Foundation\Application);  
$this->instances['Illuminate\Container\Container'] = object(Illuminate\Foundation\Application);
2.1.3 注册基础服务registerBaseServiceProviders

Application.php中注册了三个基础服务,分别是 Event,Log 还有路由服务

    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

是如何注册的呢?来跟踪下register的源码:

public function register($provider, $force = false)
    {
    //如果已经注册过,则直接从serviceProviders中取出,并返回
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // If the given "provider" is a string, we will resolve it, passing in the
        // application instance automatically for the developer. This is simply
        // a more convenient way of specifying your service provider classes.
        //若传的是字符串,则直接创建对象 new $provider($this);
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }
		// 如果服务提供者里若存在register方法,则直接自动调用
        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        // If there are bindings / singletons set as properties on the provider we
        // will spin through them and register them with the application, which
        // serves as a convenience layer while registering a lot of bindings.
        //如果存在bindings属性,则遍历bindings中的数据,进行bind绑定
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }
		//如果存在singletons属性,则遍历singletons数据,进行singleton
        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }
	
        $this->markAsRegistered($provider);

        // If the application has already booted, we will call this boot method on
        // the provider class so it has an opportunity to do its boot logic and
        // will be ready for any usage by this developer's application logic.
       //**如果容器启动,则直接调用服务provider的启动方法,供开发者调用  if (method_exists($provider, 'boot')) { return $this->call([$provider, 'boot']);}**
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

总结:基础服务注册过程中,默认注册了路由,事件以及日志三种服务。在服务之前未进行过注册的情况下,首先调用服务本身的register方法进行注册,然后对服务提供者的bindings以及singletons属性进行绑定处理,最后在容器启动的前提下,调用服务提供者本身的boot方法进行启动;

实际上,在这个地方,还有两点需要注意的
1)是 bind 方法和 singleton 方法的区别?
2)服务提供者调用注册register方法,具体是完成了什么事情?

2.1.4 注册核心容器别名registerCoreContainerAliases

这部分比较简单,就是将核心的容器类,赋予一个别名。

public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
            'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
            'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
            'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
            'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
            'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
            'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
            'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
            'db'                   => [\Illuminate\Database\DatabaseManager::class],
            'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
            'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
            'files'                => [\Illuminate\Filesystem\Filesystem::class],
            'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
            'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
            'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
            'hash'                 => [\Illuminate\Hashing\HashManager::class],
            'hash.driver'          => [\Illuminate\Contracts\Hashing\Hasher::class],
            'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
            'log'                  => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
            'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
            'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
            'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
            'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
            'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
            'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
            'redirect'             => [\Illuminate\Routing\Redirector::class],
            'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
            'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
            'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
            'session'              => [\Illuminate\Session\SessionManager::class],
            'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
            'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
            'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
            'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }

生成的结果参考如下:

$this->aliases['Illuminate\Foundation\Application'] = 'app';
$this->aliases['Illuminate\Contracts\Container\Container'] = 'app';
$this->aliases['Illuminate\Contracts\Foundation\Application'] = 'app';
$this->abstractAliases['app'][] = 'Illuminate\Foundation\Application';
$this->abstractAliases['app'][] = 'Illuminate\Contracts\Container\Container';
$this->abstractAliases['app'][] = 'Illuminate\Contracts\Foundation\Application';

3. 绑定重要的接口

在绑定接口的过程中,我们可以看到均使用的是单例方法。其中Kernel服务的是从Http和Console进入的请求

|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/

$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
);

4. 服务绑定形式 bind/singleton/instance

服务容器中的服务绑定有三种,分别是bind /singleton/instance
结合服务容器初始化的过程,可以看出

  1. 完成基础绑定时,用的是instance
  2. 注册基础服务时,会对服务提供者的bindings属性中的数据进行bind绑定
  3. 基础服务的注册以及Kernel服务绑定使用的是singleton方法

到底有什么区别呢?

1. singleton源码:其实底层还是调用了bind方法,只不过第三个参数始终为true

    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

2. bind源码:

 public function bind($abstract, $concrete = null, $shared = false)
    {
         //--------------------1、首先从instances and aliases数组中删除$abstract
        $this->dropStaleInstances($abstract);

        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        // --------------2、如果第二个参数为null,则将其设置为$abstract
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        //  如果第二个参数不是闭包函数,则应该是class名,会封装成闭包返回。封装的过程中,会判断$abstract和$concrete是否相等,相等就调用容器中的build方法,否则调用容器的make方法解析
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
     // 保存在bindings数组中
        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

第三个字段为$shared,true和false有何不同呢?
仅仅体现在bindings数组中的值中

bindings['events']=>[
    'concrete'=>$this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make('Illuminate\Contracts\Queue\Factory');
            });
        }),
    'shared'=>false
]
singleton函数其实就是调用了bind方法,不过$shared参数为true;
也就是说bindings[]中存放内容如下:
bindings['events']=>[
    'concrete'=>$this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make('Illuminate\Contracts\Queue\Factory');
            });
        }),
    'shared'=>true
]

遗留问题:build和make方法有何不同?

3. instance源码:

instance源码在前面已经讨论过;instance是用来向容器中注册已存在的实例

5. build和make方法

在讲bind源码中,获取闭包方法时;如果 if ($abstract == $concrete) 则使用build方法进行构造;否则使用make方法。

 /**
     * Get the Closure to be used when building a type.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->make($concrete, $parameters);
        };
    }
5.1 build方法
public function build($concrete)
    {
        // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.
        //==如果传入的参数为闭包函数,则直接执行,返回
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }
//不是闭包的话,说明是一个类名,这里利用反射方法
        $reflector = new ReflectionClass($concrete);

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface or Abstract Class and there is
        // no binding registered for the abstractions so we need to bail out.
       //如果这个类不能被实例化,抛出异常信息
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }
//将类存放到buildStack数组
        $this->buildStack[] = $concrete;
//获取构造函数
        $constructor = $reflector->getConstructor();

        // If there are no constructors, that means there are no dependencies then
        // we can just resolve the instances of the objects right away, without
        // resolving any other types or dependencies out of these containers.
        //如果构造函数为空,说明没有依赖,可以直接实例化返回
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }
	 //利用php反射获取构造函数的参数
        $dependencies = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        $instances = $this->resolveDependencies(
            $dependencies
        );
	 //从buildStack中删除这个类
        array_pop($this->buildStack);
//返回实例化后的类
        return $reflector->newInstanceArgs($instances);
    }

可以看出build的主要作用是用来执行闭包函数,或者实例化类名后返回

5.2 make方法
   public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }
protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        //如果该实例已经存在,直接返回,也就是instances注册的实例
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;
//通过别名获取之前bind方法保存在bindings[]数组中存放的闭包函数
        $concrete = $this->getConcrete($abstract);

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        //如果是单例注册,放到instances,下次执行make直接返回已经实例化好的对象
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值