[php]laravel框架容器管理的一些要點

来源:http://www.cnblogs.com/lyzg/archive/2016/12/16/6181055.html
-Advertisement-
Play Games

本文面向php語言的laravel框架的用戶,介紹一些laravel框架裡面容器管理方面的使用要點。文章很長,但是內容應該很有用,希望有需要的朋友能看到。php經驗有限,不到位的地方,歡迎幫忙指正。 1. laravel容器基本認識 laravel框架是有一個容器框架,框架應用程式的實例就是一個超大 ...


本文面向php語言的laravel框架的用戶,介紹一些laravel框架裡面容器管理方面的使用要點。文章很長,但是內容應該很有用,希望有需要的朋友能看到。php經驗有限,不到位的地方,歡迎幫忙指正。

1. laravel容器基本認識

laravel框架是有一個容器框架,框架應用程式的實例就是一個超大的容器,這個實例在bootstrap/app.php內進行初始化:

image

這個文件在每一次請求到達laravel框架都會執行,所創建的$app即是laravel框架的應用程式實例,它在整個請求生命周期都是唯一的。laravel提供了很多服務,包括認證,資料庫,緩存,消息隊列等等,$app作為一個容器管理工具,負責幾乎所有服務組件的實例化以及實例的生命周期管理。這種方式能夠很好地對代碼進行解耦,使得應用程式的業務代碼不必操心服務組件的對象從何而來,當需要一個服務類來完成某個功能的時候,僅需要通過容器解析出該類型的一個實例即可。從最終的使用方式來看,laravel容器對服務實例的管理主要包括以下幾個方面:

  • 服務的綁定與解析
  • 服務提供者的管理
  • 別名的作用
  • 依賴註入

弄清這幾個方面的思想, 以及laravel容器的實現機制,就能熟練掌握laravel容器的管理。

2. 如何在代碼中獲取到容器實例

laravel容器實例在整個請求生命周期中都是唯一的,且管理著所有的服務組件實例。那麼有哪些方式能夠拿到laravel容器的實例呢?常用的有以下幾種方式:

1) 通過app這個help函數:

$app = app();

app這個輔助函數定義在
image
文件裡面,這個文件定義了很多help函數,並且會通過composer自動載入到項目中。所以,在參與http請求處理的任何代碼位置都能夠訪問其中的函數,比如app()。

2)通過App這個Facade

<?php

Route::get('/', function () {
    dd(App::basePath());
    return '';
});

通過App這個Facade拿容器實例的方式,跟上面不同的是,不能把App先賦給一個變數,然後通過變數來調用容器的方法。這是因為App相當於只是一個類名,我們不能把一個類名複製一個變數。$app = App;不是一個合法的可執行的語句,而$app = app();卻是一個合法的可執行的語句,因為它後面有app(),表示函數調用。App::basePath();也是一個合法的語句,它就是在調用類的靜態方法。

再補充2點:

第一點: Facade是laravel框架裡面比較特殊的一個特性,每個Facade都會與容器裡面的一個實例對象關聯,我們可以直接通過Facade類靜態方法調用的形式來調用它關聯的實例對象的方法。比如App這個Facade,調用App::basePath()的時候,實際相當於app()->basePath()。這個底層機制也是依賴於php語言的特性才能實現的,需要在每一個Facade裡面,設定一個靜態成員並關聯到一個服務的實例對象,當調用Facade類的靜態方法的時候,解析出調用的方法名,再去調用關聯的服務實例的同名方法,最後把結果返回。我認為理解Facade能起到什麼作用就夠了,不一定要深究到它底層去瞭解實現的細節,畢竟在實際的開發中,不用Facade,也完全不影響laravel框架的使用。另外在實際編碼中,要自定義一個Facade也非常容易,只要繼承laravel封裝的Facade基類即可:

<?php

namespace ThirdProviders\CasServer\Facades;

use Illuminate\Support\Facades\Facade;
use ThirdProviders\CasServer\CasServerManager;

class CasServer extends Facade
{
    protected static function getFacadeAccessor()
    {
        return CasServerManager::class;
    }
}

實現Facade基類的getFacadeAccessor方法,laravel框架就知道這個Facade類該與哪個服務實例關聯起來了。實際上這個getFacadeAccess方法,返回的名稱就是後面要介紹的服務綁定名稱。在laravel容器裡面,一個服務實例,都會有一個固定的綁定名稱,通過這個名稱就能找到這個實例。所以為啥Facade類只要返回服務綁定名稱即可。

我們可以看看App這個Facade類的代碼:

<?php

namespace Illuminate\Support\Facades;

/**
 * @see \Illuminate\Foundation\Application
 */
class App extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'app';
    }
}

它的getFacadeAccessor返回的就是一個字元串“app”,這個app就是laravel容器自己綁定自己時用的名稱。

第二點: 從上一點最後App這個Facade的源碼可以看出,App這個Facade的全類名其實是:Illuminate\Support\Facades\App,那為什麼我們在代碼裡面能夠直接通過App這個簡短的名稱就能訪問到呢:

<?php

Route::get('/', function () {
    dd(App::basePath());
    return '';
});

你看以上代碼完全沒有用到use或者完全限定的方式來使用Illuminate\Support\Facades\App。實際上App跟Illuminate\Support\Facades\App是完全等價的,只不過App比Illuminate\Support\Facades\App要簡短很多,而且不需要use,所以用起來方便,那麼它是怎麼實現的?這跟laravel容器配置的別名有關係,在config/app.php中,有一節aliases專門用來配置一些類型的別名:

'aliases' => [

    'App' => Illuminate\Support\Facades\App::class,
    'Artisan' => Illuminate\Support\Facades\Artisan::class,
    'Auth' => Illuminate\Support\Facades\Auth::class,
    'Blade' => Illuminate\Support\Facades\Blade::class,
    'Bus' => Illuminate\Support\Facades\Bus::class,
    'Cache' => Illuminate\Support\Facades\Cache::class,
    'Config' => Illuminate\Support\Facades\Config::class,
    'Cookie' => Illuminate\Support\Facades\Cookie::class,
    'Crypt' => Illuminate\Support\Facades\Crypt::class,
    'DB' => Illuminate\Support\Facades\DB::class,
    'Eloquent' => Illuminate\Database\Eloquent\Model::class,
    'Event' => Illuminate\Support\Facades\Event::class,
    'File' => Illuminate\Support\Facades\File::class,
    'Gate' => Illuminate\Support\Facades\Gate::class,
    'Hash' => Illuminate\Support\Facades\Hash::class,
    'Lang' => Illuminate\Support\Facades\Lang::class,
    'Log' => Illuminate\Support\Facades\Log::class,
    'Mail' => Illuminate\Support\Facades\Mail::class,
    'Notification' => Illuminate\Support\Facades\Notification::class,
    'Password' => Illuminate\Support\Facades\Password::class,
    'Queue' => Illuminate\Support\Facades\Queue::class,
    'Redirect' => Illuminate\Support\Facades\Redirect::class,
    'Redis' => Illuminate\Support\Facades\Redis::class,
    'Request' => Illuminate\Support\Facades\Request::class,
    'Response' => Illuminate\Support\Facades\Response::class,
    'Route' => Illuminate\Support\Facades\Route::class,
    'Schema' => Illuminate\Support\Facades\Schema::class,
    'Session' => Illuminate\Support\Facades\Session::class,
    'Storage' => Illuminate\Support\Facades\Storage::class,
    'URL' => Illuminate\Support\Facades\URL::class,
    'Validator' => Illuminate\Support\Facades\Validator::class,
    'View' => Illuminate\Support\Facades\View::class
],

然後在laravel框架處理請求過程中,會通過Illuminate\Foundation\Bootstrap\RegisterFacades這個類來註冊這些別名到全局環境裡面:

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Support\Facades\Facade;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Contracts\Foundation\Application;

class RegisterFacades
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance($app->make('config')->get('app.aliases', []))->register();
    }
}

所以我們才能直接通過別名,代替完整的類型名做同樣的訪問功能。如果你自己寫了一些類,名稱很長,並且在代碼裡面用的特別多,也可以考慮配置到config/app.php別名裡面去,laravel會幫我們註冊。

3)另外一種方式拿到laravel容器實例就是在服務提供者裡面直接使用$this->app

服務提供者後面還會介紹,現在只是引入。因為服務提供者類都是由laravel容器實例化的,這些類都繼承自Illuminate\Support\ServiceProvider,它定義了一個實例屬性$app:

image

laravel在實例化服務提供者的時候,會把laravel容器實例註入到這個$app上面。所以我們在服務提供者裡面,始終能通過$this->$app訪問到laravel容器實例,而不需要再使用app()函數或者App Facade了。

3. 直觀的認識laravel容器

一直在說容器,既然它是用來存取實例對象的時候,那麼它裡面應該至少有一個數組充當容器存儲功能的角色才行,所以我們可以通過列印的方式來直觀地看下laravel容器實例的結構:

<?php

Route::get('/', function () {
    dd(app());
    return '';
});

結果如下:
image
從這個結構可以看出,laravel容器實例上包含了很多的數組,其中紅框部分的數組,從名字也可以猜測出它們跟後面要介紹的服務,服務提供者與服務別名之間的聯繫。理清這幾個數組的存儲結構,自然就明白了laravel容器如何管理服務。

4. 如何理解服務綁定與解析

淺義層面理解,容器既然用來存儲對象,那麼就要有一個對象存入跟對象取出的過程。這個對象存入跟對象取出的過程在laravel裡面稱為服務的綁定與解析。

先來看服務綁定,在laravel裡面,服務綁定到容器,有多種形式:

app()->singleton('service', 'this is service1');

app()->singleton('service2', [
    'hi' => function(){
        //say hi
    }
]);

class Service {

}

app()->singleton('service3', function(){
    return new Service();
});

singleton是laravel服務綁定的方法之一,詳細作用後面會介紹,目前只是用它來展現服務綁定的形式。籠統的說容器的時候,我們說容器管理的是服務對象,但是laravel的容器可以管理不僅僅是對象,它能夠管理的是任意類型的數據,包括基本數據類型和對象。所以在服務綁定的時候,我們也可以綁定任意的數據,正如以上代碼展示的那樣。在綁定的時候,我們可以直接綁定已經初始化好的數據(基本類型、數組、對象實例),還可以用匿名函數來綁定。用匿名函數的好處在於,這個服務綁定到容器以後,並不會立即產生服務最終的對象,只有在這個服務解析的時候,匿名函數才會執行,此時才會產生這個服務對應的服務實例。

實際上,當我們使用singleton,bind方法以及數組形式,(這三個方法是後面要介紹的綁定的方法),進行服務綁定的時候,如果綁定的服務形式,不是一個匿名函數,也會在laravel內部用一個匿名函數包裝起來,這樣的話, 不輪綁定什麼內容,都能做到前面介紹的懶初始化的功能,這對於容器的性能是有好處的。這個可以從bind的源碼中看到一些細節:

image

服務綁定時的第一個參數就是服務的綁定名稱。服務綁定完成後,容器會把這個服務的綁定記錄存儲到實例屬性bindings裡面:

image

這個bindings裡面的每條記錄,代表一個服務綁定。它的key值就是服務的綁定名稱,value值也是一個數組,這個數組的concrete屬性就是服務綁定時產生的匿名函數,也就是閉包;另外一個參數表示這個服務在多次解析的時候,是否只返回第一次解析得到的對象。這個參數在介紹服務綁定方法時會再繼續介紹。

接下來看看服務綁定的幾種方法及區別:

a. 通過bind方法

app()->bind('service', function(){
    return new Service();
},true);

bind是laravel服務綁定的底層方法,它的簽名是:

image

第一個參數服務綁定名稱,第二個參數服務綁定的結果,第三個參數就表示這個服務是否在多次解析的時候,始終返回第一次解析出的實例。它的預設值是false,意味著這樣的服務在每次解析的時候都會返回一個新的實例。它的值與bindings裡面服務綁定記錄value數組裡面的share屬性是對應的。

b. 通過singleton方法

舉例略。它跟bind的區別在於,它始終是以shared=true的形式進行服務綁定,這是因為它的源碼是這樣的:

image

c. 通過數組的形式

app()['service'] = function(){
    return new Service();
};

為什麼可以直接把容器實例直接當成數組來用呢,這是因為容器實現了php的ArrayAccess介面:

/**
 * Set the value at a given offset.
 *
 * @param  string  $key
 * @param  mixed   $value
 * @return void
 */
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);
}

所以實際上以上這種數組形式的綁定實際上相當於沒有第三個參數的bind方法。

再來看服務的解析。上面的內容都是在說明把如何獲取服務實例的方式綁定到容器,那麼如何從容器獲取到需要的服務實例呢?這個過程就是服務解析,在laravel裡面通過make方法來完成服務的解析:

$service= app()->make('service');

這個方法接收兩個參數,第一個是服務的綁定名稱和服務綁定名稱的別名,如果是別名,那麼就會根據服務綁定名稱的別名配置,找到最終的服務綁定名稱,然後進行解析;第二個參數是一個數組,最終會傳遞給服務綁定產生的閉包。

我們可以通過make的源碼理解服務解析的邏輯,這個是Illuminate\Container\Container類中的make方法源碼,laravel的容器實例是Illuminate\Foundation\Application類的對象,這個類繼承了Illuminate\Container\Container,這裡暫時只展示Illuminate\Container\Container類中的make方法的代碼,先不涉及Illuminate\Foundation\Application類的make方法,因為後者覆蓋了Illuminate\Container\Container類中的make方法,加了一些服務提供者的邏輯,所以這裡先不介紹它。其實前面的很多源碼也都是從Illuminate\Container\Container中拿出來的,不過那些代碼Application沒有覆蓋,不影響內容的介紹。

public function make($abstract, array $parameters = [])
{
    $abstract = $this->getAlias($this->normalize($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.
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }

    $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, $parameters);
    } else {
        $object = $this->make($concrete, $parameters);
    }

    // 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.
    if ($this->isShared($abstract)) {
        $this->instances[$abstract] = $object;
    }

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

    $this->resolved[$abstract] = true;

    return $object;
}

從這個源碼可以看到:

a. 在解析一個服務的時候,它會先嘗試把別名轉換成有效的服務綁定名稱;

b. 如果這個服務是一個shared為true的服務綁定,且之前已經做過解析的話,就會直接返回之前已經解析好的對象;

c. 如果這個服務是一個shared為true的服務綁定,並且是第一次解析的話,就會把已解析的對象存入到instances這個容器屬性裡面去,也就是說只有shared為true的服務綁定,在解析的時候才會往instances屬性裡面存入記錄,否則不會存入;

d. 解析完畢,還會在容器的resolved屬性裡面存入一條記錄,表示這個服務綁定解析過;

e. resolved,instances數組的key值跟bindings數組的key值一樣,都是服務綁定名稱;

f. 服務綁定的shared屬性在整個服務綁定生命周期內都是不能更改的。

服務的解析也有多種形式,常用的有:

a. make方法

b. 數組形式

app()['service'];

這個的原理還是跟容器實現了ArrayAccess的介面有關係:

public function offsetGet($key)
{
    return $this->make($key);
}

所以數組形式的訪問跟不使用第二個參數的make方法形式是一樣的。

c. app($service)的形式

app('service');

看了app這個help函數的源碼就明白了:

function app($make = null, $parameters = [])
{
    if (is_null($make)) {
        return Container::getInstance();
    }

    return Container::getInstance()->make($make, $parameters);
}

原來app這個函數在第一個參數為空的時候,返回的是容器實例本身。在有參數的時候等價於調用容器實例的make方法。

以上就是服務綁定與解析的主要內容,涉及的要點較多,希望描述的比較清楚。

5. 服務提供者的作用與使用

前面介紹了服務的綁定。那麼服務的綁定應該在哪個位置處理呢?雖然說,能夠拿到容器實例的地方,就都能進行服務的綁定;但是我們使用服務的綁定的目的,是為了在合適的位置解析出服務實例並使用,如果服務綁定的位置過於隨意,那麼就很難保證在解析的位置能夠準確的解析出服務實例。因為服務能夠解析的前提是服務綁定的代碼先與服務解析的代碼執行;所以,服務綁定通常會在應用程式初始化的時候進行,這樣才能保證業務代碼中(通常是router和controller裡面)一定能解析出服務實例。這個最佳的位置就是服務提供者。

服務提供者,在laravel裡面,其實就是一個工廠類。它最大的作用就是用來進行服務綁定。當我們需要綁定一個或多個服務的時候,可以自定義一個服務提供者,然後把服務綁定的邏輯都放在該類的實現中。在larave裡面,要自定一個服務提供者非常容易,只要繼承Illuminate\Support\ServiceProvider這個類即可。下麵通過一個簡單的自定義服務提供者來說明服務提供者的一些要點:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    protected $defer = true;

    public function boot()
    {
        //
    }

    public function register()
    {
        $this->app->singleton('service1', function(){
            return 'service1';
        });
        $this->app->singleton('service2', function(){
            return 'service2';
        });
        $this->app->singleton('service3', function(){
            return 'service3';
        });
    }

    public  function provides()
    {
        return ['service1','service2','service3'];
    }
}

1). 首先,自定義的服務提供者都是放在下麵這個目錄的:
image
其實你放在哪都可以,不過得告訴laravel你的服務提供者在哪,laravel才會幫你註冊。怎麼告訴它,後面還有介紹。

2)在這個舉例裡面,可以看到有一個register方法,這個方法是ServiceProvider裡面定義的。自定義的時候,需要重寫它。這個方法就是用來綁定服務的。你可以在這個服務裡面,根據需要加入任意數量的服務綁定。前面要介紹過,在服務提供者裡面,始終能通過$this->app拿到容器實例,所以上面的舉例中,我們直接用這種方式來完成服務綁定。這個方法是怎麼完成服務綁定的呢?因為當laravel找到這個服務提供者的類以後,就會初始化這個服務提供者類,得到一個服務提供者的對象,然後調用它的register方法,自然它裡面的所有服務綁定代碼就都會執行了。

laravel初始化自定義服務提供者的源碼是:

public function registerConfiguredProviders()
    {
        $manifestPath = $this->getCachedServicesPath();

        (new ProviderRepository($this, new Filesystem, $manifestPath))
                    ->load($this->config['app.providers']);
    }

這個代碼是在Illuminate\Foundation\Application的源碼裡面拿出來的,從中你能看到laravel會把所有的自定義服務提供者都註冊進來。這個註冊的過程其實就是前面說的實例化服務提供者的類,並調用register方法的過程。

3). 從上一步的源碼也能看到,laravel載入自定義服務提供者的時候,實際是從config/app.php這個配置文件裡面的providers配置節找到所有要註冊的服務提供者的。

'providers' => [

    /*
     * Laravel Framework Service Providers...
     */
    Illuminate\Auth\AuthServiceProvider::class,
    Illuminate\Broadcasting\BroadcastServiceProvider::class,
    Illuminate\Bus\BusServiceProvider::class,
    Illuminate\Cache\CacheServiceProvider::class,
    Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
    Illuminate\Cookie\CookieServiceProvider::class,
    Illuminate\Database\DatabaseServiceProvider::class,
    Illuminate\Encryption\EncryptionServiceProvider::class,
    Illuminate\Filesystem\FilesystemServiceProvider::class,
    Illuminate\Foundation\Providers\FoundationServiceProvider::class,
    Illuminate\Hashing\HashServiceProvider::class,
    Illuminate\Mail\MailServiceProvider::class,
    Illuminate\Notifications\NotificationServiceProvider::class,
    Illuminate\Pagination\PaginationServiceProvider::class,
    Illuminate\Pipeline\PipelineServiceProvider::class,
    Illuminate\Queue\QueueServiceProvider::class,
    Illuminate\Redis\RedisServiceProvider::class,
    Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
    Illuminate\Session\SessionServiceProvider::class,
    Illuminate\Translation\TranslationServiceProvider::class,
    Illuminate\Validation\ValidationServiceProvider::class,
    Illuminate\View\ViewServiceProvider::class,

    /*
     * Package Service Providers...
     */

    //

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    // App\Providers\BroadcastServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
    Xavrsl\Cas\CasServiceProvider::class,
    ThirdProviders\CasServer\CasServerProvider::class
],

所以你如果自己寫了一個服務提供者,那麼只要配置到這裡面,laravel就會自動幫你註冊它了。

4)除了register方法,服務提供者裡面還有一個boot方法,這個boot方法,會在所有的服務提供者都註冊完成之後才會執行,所以當你想在服務綁定完成之後,通過容器解析出其它服務,做一些初始化工作的時候,那麼就可以這些邏輯寫在boot方法裡面。因為boot方法執行的時候,所有服務提供者都已經被註冊完畢了,所以在boot方法裡面能夠確保其它服務都能被解析出來。

5)前面說的服務提供者的情況,在laravel應用程式初始化的時候,就會去註冊服務提供者,調用register方法。但是還有一種需求,你可能需要在真正用到這個服務提供者綁定的服務的時候,才會去註冊這個服務提供者,以減少不必要的註冊處理,提高性能。這也是延遲處理的一種方式。那麼這種服務提供者該怎麼定義呢?

其實最前面的這個舉例已經告訴你了,只要定義一個$defer的實例屬性,並把這個實例屬性設置為true,然後添加一個provides的實例方法即可。這兩個成員都是ServiceProvider基類裡面定義好的,自定義的時候,只是覆蓋而已。

在基類中,$defer的預設值是false,表示這個服務提供者不需要延遲註冊。provides方法,只要簡單的返回這個服務提供register方法裡面,註冊的所有服務綁定名稱即可。

延遲註冊的服務提供者的機制是:

  • 當laravel初始化服務提供者的實例後,如果發現這個服務提供者的$defer屬性為true,那麼就不會去調用它的register方法
  • 當laravel解析一個服務的時候,如果發現這個服務是由一個延遲服務提供的(它怎麼知道這個服務是延遲服務提供的,是provides方法告訴它的),那麼就會先把這個延遲服務提供者先註冊,再去解析。這個可以看看Illuminate\Foundation\Application的make方法就清楚了:
    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);
    
        if (isset($this->deferredServices[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }
    
        return parent::make($abstract, $parameters);
    }

6)還記得容器實例結構上幾個帶有providers名稱的屬性數組吧:

image

在瞭解以上provider的機制後,這幾個數組的作用也就比較清晰了。其中serviceProviders用來存放所有已經註冊完畢的服務提供者:

image

loadedProviders跟serviceProviders的作用類似,只是存儲的記錄形式不同:

image

deferredProviders用來存儲所有的延遲註冊的服務提供者:

image

跟前面兩個不同的是,deferredProviders存儲的記錄的key值並不是服務提供者的類型名稱,而是服務提供者的provides返回數組裡面的名稱。並且如果一個服務提供者的provides裡面返回了多個服務綁定名稱的話,那麼deferredProviders裡面就會存多條記錄:

image

這樣是方便根據服務綁定名稱,找到對應的服務提供者,並完成註冊。當服務的解析的時候,會先完成延遲類型的服務提供者的註冊,註冊完畢,這個服務綁定名稱在deferredProviders對應的那條記錄就會刪除掉。不過如果一個服務提供者provides了多個服務綁定名稱,解析其中一個服務的時候,只移除該名稱對應的deferredProviders記錄,而不是所有。

7)服務提供者還有一個小問題值的註意,由於php是一門基本語言,在處理請求的時候,都會從入口文件把所有php都執行一遍。為了性能考慮,laravel會在第一次初始化的時候,把所有的服務提供者都緩存到bootstrap/cache/services.php文件裡面,所以有時候當你改了一個服務提供者的代碼以後,再刷新不一定能看到期望的效果,這有可能就是因為緩存所致。這時把services.php刪掉就能看到你要的效果了。

6. 服務綁定名稱的別名

前面介紹的別名是在config/app.php的aliases配置節裡面定義的,那個別名的作用僅僅是簡化類名的時候,laravel幫你把長的類型名註冊成為簡短的名稱,然後在全局環境了裡面都能使用。laravel還存在另外一個別名,就是服務綁定名稱的別名。通過服務綁定的別名,在解析服務的時候,跟不使用別的效果一致。別名的作用也是為了同時支持全類型的服務綁定名稱以及簡短的服務綁定名稱考慮的。

1)如何指定和使用服務綁定名稱的別名

假如有一個服務做如下綁定:

app()->singleton('service1', function(){
    new CasServerManager();
});

那麼可以通過容器方法alias方法指定別名:

app()->alias('service1', 'alias_a');

這個方法的第一個參數是服務綁定名稱,第二個參數是別名。這個方法調用後,就會在容器實例屬性aliases數組裡面存入一條記錄:

image
image

你看剛纔舉例中的別名就已經添加到這個數組裡面。這個數組裡面每條記錄的key值都是別名。但是value有可能是服務綁定名稱,也有可能是另外一個別名。這是因為別名是可以遞歸的。

2)別名支持遞歸

也就是說,可以對別名再指定別名:

app()->alias('alias_a', 'alias_b');
app()->alias('alias_b', 'alias_c');

image

3)別名如何應用於服務解析

在解析服務的時候,會先確定這個服務名稱是否為一個別名(只要看看在aliases數組裡是否存在記錄即可),如果不是別名,直接用這個服務名稱進行解析。如果這個服務名稱是一個別名,那麼就會通過調用的方式,找到最終的服務名稱:

image

如下所有的服務解析都是等價的:

app('alias_c');
app('alias_b');
app('alias_a');
app('service1');

4)另外一種指定別名的方式

可以在服務綁定的時候,進行別名的指定。只要按照如下的方式進行綁定即可:

app()->singleton(['service1' => 'alias'], function(){
    new CasServerManager();
});

也就是把服務綁定名稱換成數組形式而已。數組記錄的key值就是服務名稱,value值就是別名。

7. 依賴註入的機制

<?php

class Service{
    protected $app;

    public function __construct(\Illuminate\Contracts\Foundation\Application $app)
    {
        $this->app = $app;
    }
}

app()->singleton(Service::class);

Route::get('/', function () {
    dd(app(Service::class));
    return '';
});

在這個舉例中,定義了一個Service類,這個類有一個實例成員$app,它需要一個實現了\Illuminate\Contracts\Foundation\Application 介面的實例對象,也就是容器實例。然後通過直接使用類型名稱的方式把這個類快速地綁定到了容器。app()->singleton(Service::class),等價於app()->singleton(Service::class,Service:class)。這種通過類名形式的綁定,laravel在解析的時候會調用這個類型的構造函數來實例化服務。並且在調用構造函數的時候,會通過反射獲得這個構造函數的參數類型,然後從容器已有的綁定中,解析出對應參數類型的服務實例,傳入構造函數完成實例化。這個過程就是所謂的依賴註入。

在以上代碼中,完全沒有手寫的new Service(app())代碼,就能正確地解析到service實例,這就是依賴註入的好處:

image

當一個類需要某個服務類型的實例時,不需要自己去創造這個服務的實例,只要告訴容器,它需要的實例類型即可,然後容器會根據這個類型, 解析出滿足該類型的服務。如何根據參數類型解析出該參數類型的服務實例呢?其實就是根據參數類型的類型名稱進行解析得到的,所以依賴註入能夠成功的前提是根據參數類型的名稱,能夠成功地解析到一個服務對象。以上之所以能夠通過Illuminate\Contracts\Foundation\Application 這個名稱解析到服務,那是因為在容器實例aliases數組裡面有一條Illuminate\Contracts\Foundation\Application 的別名記錄:

image

也就是說Illuminate\Contracts\Foundation\Application 實際上是app這個服務綁定名稱的一個別名,所以laravel在解析Illuminate\Contracts\Foundation\Application的時候,就能得到對應的服務實例了。

這些別名屬於laravel容器核心的別名,在laravel初始化的時候會被註冊:

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', 'Illu

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 開放中經常會要做單元測試,新的項目又沒有單元測試項目,怎麼才能搭建一個單元測試項目呢? 下麵跟我四步走,如有錯誤之處,還請指正! 1、添加項目 2、添加配置文件 新建app.config文件,註意不是web.config,添加connectionstring 3、設置文件屬性 Bulid Actio ...
  • 通過程式集掃描, 能夠自動註冊符合規則的類型. 這種方式, 很方便. 這一篇就介紹下程式集掃描吧. 一、掃描 其實前面已經介紹過, 這種方式. 不過並不全. 先看一個之前的方式: 二、過濾 如果我並不想註冊那麼多的類型, 但是又想通過程式集的註冊方式去註冊, 那怎麼辦呢? 1. Where過濾 只需 ...
  • 在上一篇Log4net入門(ASP.NET MVC 5篇)中,我們講述瞭如何在ASP.NET MVC 5項目中使用log4net。在這一篇中,我們將講述如何在WCF應用中使用log4net,為了講述這個過程,我們將創建三個項目:WCF服務庫項目、WCF服務應用程式和客戶端應用程式。WCF服務庫項目主 ...
  • 最近深圳突然降溫,身體不適感冒發燒,各種不舒服,故請假休息一天,我是個閑不下來的人,這麼好的時光豈能浪費,抄起筆記本,年終總結走起來。 今年是我的幸福年,主要體現在兩個方面:愛情、工作。 割 1.愛情 作為一個對家庭對愛情專一的北方男人(河北邢台人士),2014年畢業後,為了維持美好的校園愛情放棄了 ...
  • 由於第一次寫博客,寫的不好的地方,還請各位大神多多指點, 講解一下:xml動態插入數據並保存,寫這個時候費了我不少勁,最後終於皇天不負有心人讓我搞出來了,特意分享給大家,寫的不完美的地方還請大家多多指點 資料庫表結構 Categoryid GUid自動生成 CategoryName 分類名稱Cate ...
  • ASP.NET Core 中間件(Middleware)Diagnostics使用。對於中間件的介紹可以查看之前的文章ASP.NET Core 開發-中間件(Middleware)。 Diagnostics中間件,主要功能是用於報告和處理ASP.NET Core中的異常和錯誤信息,以及診斷Entit ...
  • 在日常的開發中,運行定時任務基本上已經是很普遍的需求了,可以通過windows服務+timer組件來實現,也可以使用第三方框架來集成,Quartz.NET就是一款從JAVA的Quartz移植過來的一個不錯的作業調度組件,但是當我們把作業都寫好,並部署完成的時候,管理成為了很麻煩的事情,因此我基於Qu ...
  • 今日問題: 請問主程式輸出結果是什麼?(點擊以下“【Java每日一題】20161216”查看20161215問題解析) 題目原發佈於公眾號、簡書:【Java每日一題】20161216,【Java每日一題】20161216 註:weknow團隊近期開通並認證了分答,歡迎大家收聽,有問題也歡迎到分答來咨 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...