Laravel底層學習筆記02 - 服務容器,服務提供者

IT小馬發表於2021-11-07

學習資料:
laravel底層核心程式碼分析核心概念
Laravel (5.5.33) 載入過程---instance方法

服務容器和服務提供者

服務容器 ServiceContainner

ServiceContainner通過依賴注入將ServiceProvider提供的能力註冊進服務容器內部。

Laravel直接通過容器的例項化物件,找到對應的服務,就可以直接使用其提供的能力了。

ServiceContainner就是一個類的例項化物件,啟動時載入所有可用服務,用的時候再解析呼叫其中的方法。

服務提供者 ServiceProvider

有能力提供服務的服務提供者
//Laravel 的服務提供者 config/app.php
'providers' => [
    /*
     * Laravel Framework Service Providers...
     */  
    //框架自帶 Service Provider
    
    /*
     * Package Service Providers...
     */
    //需要引入的 Service Provider
]

Laravel原始碼解析

服務容器 $app

/bootstrap/app.php
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app服務容器,就是Application類的例項化物件。

src/Illuminate/Foundation/Application.php
public function __construct($basePath = null)
{
    if ($basePath) {
        $this->setBasePath($basePath);//設定基礎路徑
    }
    $this->registerBaseBindings();//註冊基礎繫結
    $this->registerBaseServiceProviders();//註冊基礎服務提供者
    $this->registerCoreContainerAliases();//註冊核心服務容器別名
}

註冊基礎繫結

src/Illuminate/Foundation/Application.php
protected function registerBaseBindings()
{
    static::setInstance($this);//例項化自己
    $this->instance('app', $this);//將app註冊到instances陣列中
    $this->instance(Container::class, $this);//將Container註冊到instances陣列中
    $this->singleton(Mix::class);
    $this->singleton(PackageManifest::class, function () {
        return new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        );
    });
}

instance函式

vendor/laravel/framework/src/Illuminate/Container/Container.php
/**
 * $abstract 'app'            $instance Application
 * $abstract Container::class $instance Application
 */
public function instance($abstract, $instance)
{
    $this->removeAbstractAlias($abstract); //刪除抽象別名
    $isBound = $this->bound($abstract);//判斷是否例項化
    unset($this->aliases[$abstract]); //刪除別名

    //將例項加入instances陣列
    $this->instances[$abstract] = $instance;
    //如果之前存在例項化 則執行
    if ($isBound) {
        $this->rebound($abstract);
    }
    return $instance;
}

register函式

src/Illuminate/Foundation/Application.php
protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this));
}

public function register($provider, $options = [], $force = false)
{
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }
    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }
    if (method_exists($provider, 'register')) {
        $provider->register();//呼叫ServiceProvider例項的register方法
    }
    //標記為已註冊
    $this->markAsRegistered($provider);
    //判斷App是否已啟動,已建立則呼叫$provider的啟動(boot)方法
    if ($this->booted) {
        $this->bootProvider($provider);
    }
    return $provider;
}

EventServiceProvider中的register函式

public function register()
{
    $this->app->singleton('events', function ($app) {
        return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
            return $app->make(QueueFactoryContract::class);
        });
    });
}

singleton,bind,getClosure函式

vendor/laravel/framework/src/Illuminate/Container/Container.php

singleton函式

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

bind函式

public function bind($abstract, $concrete = null, $shared = false)
{
    //從instances和aliases陣列中移除$abstract
    $this->dropStaleInstances($abstract);
    
    if (is_null($concrete)) {
        $concrete = $abstract;
    }
    //如果$concrete不是匿名函式,則將$concrete轉化為匿名函式
    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }
    //$abstract加入bindings陣列
    $this->bindings[$abstract] = compact('concrete', 'shared');
    //如果抽象型別已在此容器中解析,我們將觸發恢復偵聽器,以便已解析的任何物件可以通過偵聽器回撥更新物件的副本。
    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}

getClosure函式

/**
 *  $abstract Mix $concrete Mix => 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);
    };
}

總結

生成服務容器的過程:

1.通過instance和register方法註冊到instances陣列
2.通過singleton和bind方法繫結到bindings陣列

app.php基本流程

  1. 例項化app (new Illuminate\Foundation\Application())
    1.1 執行建構函式 __construct()

     1.1.1 設定基礎路徑 $this->setBasePath($basePath);
     1.1.2 建立並繫結基礎服務容器(app,Container) $this->registerBaseBindings();
     1.1.3 註冊基礎服務提供者 $this->registerBaseServiceProviders();
     1.1.3 註冊基礎服務別名 $this->registerCoreContainerAliases();
  2. 把App\Http\Kernel::class類下的引數對映到lluminate\Contracts\Http\Kernel::class類中

    $app->singleton(
     Illuminate\Contracts\Http\Kernel::class,
     App\Http\Kernel::class
    );
  3. 把App\Console\Kernel::class類下的引數對映到Illuminate\Contracts\Console\Kernel::class類中

    $app->singleton(
     Illuminate\Contracts\Console\Kernel::class,
     App\Console\Kernel::class
    );
  4. 把App\Exceptions\Handler::class類下的引數對映到Illuminate\Contracts\Debug\ExceptionHandler::class類中

    $app->singleton(
     Illuminate\Contracts\Debug\ExceptionHandler::class,
     App\Exceptions\Handler::class
    );

    5.返回app資源 return $app;

建立ServiceProvider

bind的呼叫方式

第一種
$abstract  'events' //別名
$concrete  匿名函式(功能是建立例項化物件) //比較自由,可以自由給屬性賦值

第二種
$abstract  'Illuminate\Foundation\Mix'
$concrete  'Illuminate\Foundation\Mix' => 匿名函式

第三種
$abstract  'Illuminate\Contracts\Http\Kernel::class'
$concrete  'App\Http\Kernel::class' => 匿名函式 //只能實現建構函式中宣告的屬性和依賴

結果:繫結到$this->bindings陣列,並執行建構函式

自定義ServiceProvider

服務容器類(宣告依賴關係)

/Service/Family/FamilyService.php
<?php
namespace App\Service\Family;
class FamilyService{
    public function __construct(PersonService $person,TvService $tv){
        $this->person = $person;
        $this->tv = $tv;
        echo 'Instance FamilyService';
    }
    public function testPerson(){
       $this->persion->test();
    } 
}
/Service/Family/PersonService.php
<?php
namespace App\Service\Family;
class PersonService{
    public function __construct(){
        echo 'Instance PersonService';
    }
    public function test(){
        echo 'PersonService test()';
    }
}
/Service/Family/TvService.php
<?php
namespace App\Service\Family;
class TService{
    public function __construct(){
        echo 'Instance TvService';
    }
    public function test(){
        echo 'TvService test()';
    }
}

建立ServiceProvider

/Providers/FamilyServiceProvider.php
/**
 * 將可以提供服務的物件繫結到bindings中
 */
public function register(){
    $this->app->bind('Family','App\Service\Family\FamilyService');
}

配置別名

/config/app.php
'providers' => [
    App\Providers\FamilyServiceProvider::class,
]

呼叫

//通過別名呼叫服務
app('Family')->testPerson();//app()呼叫make()方法建立例項

//通過服務名呼叫服務
app('App\Service\Family\FamilyService')->testPerson();

相關文章