本文面向php語言的laravel框架的用戶,介紹一些laravel框架裡面容器管理方面的使用要點。文章很長,但是內容應該很有用,希望有需要的朋友能看到。php經驗有限,不到位的地方,歡迎幫忙指正。 1. laravel容器基本認識 laravel框架是有一個容器框架,框架應用程式的實例就是一個超大 ...
本文面向php語言的laravel框架的用戶,介紹一些laravel框架裡面容器管理方面的使用要點。文章很長,但是內容應該很有用,希望有需要的朋友能看到。php經驗有限,不到位的地方,歡迎幫忙指正。
1. laravel容器基本認識
laravel框架是有一個容器框架,框架應用程式的實例就是一個超大的容器,這個實例在bootstrap/app.php內進行初始化:
這個文件在每一次請求到達laravel框架都會執行,所創建的$app即是laravel框架的應用程式實例,它在整個請求生命周期都是唯一的。laravel提供了很多服務,包括認證,資料庫,緩存,消息隊列等等,$app作為一個容器管理工具,負責幾乎所有服務組件的實例化以及實例的生命周期管理。這種方式能夠很好地對代碼進行解耦,使得應用程式的業務代碼不必操心服務組件的對象從何而來,當需要一個服務類來完成某個功能的時候,僅需要通過容器解析出該類型的一個實例即可。從最終的使用方式來看,laravel容器對服務實例的管理主要包括以下幾個方面:
- 服務的綁定與解析
- 服務提供者的管理
- 別名的作用
- 依賴註入
弄清這幾個方面的思想, 以及laravel容器的實現機制,就能熟練掌握laravel容器的管理。
2. 如何在代碼中獲取到容器實例
laravel容器實例在整個請求生命周期中都是唯一的,且管理著所有的服務組件實例。那麼有哪些方式能夠拿到laravel容器的實例呢?常用的有以下幾種方式:
1) 通過app這個help函數:
$app = app();
app這個輔助函數定義在
文件裡面,這個文件定義了很多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:
laravel在實例化服務提供者的時候,會把laravel容器實例註入到這個$app上面。所以我們在服務提供者裡面,始終能通過$this->$app訪問到laravel容器實例,而不需要再使用app()函數或者App Facade了。
3. 直觀的認識laravel容器
一直在說容器,既然它是用來存取實例對象的時候,那麼它裡面應該至少有一個數組充當容器存儲功能的角色才行,所以我們可以通過列印的方式來直觀地看下laravel容器實例的結構:
<?php Route::get('/', function () { dd(app()); return ''; });
結果如下:
從這個結構可以看出,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的源碼中看到一些細節:
服務綁定時的第一個參數就是服務的綁定名稱。服務綁定完成後,容器會把這個服務的綁定記錄存儲到實例屬性bindings裡面:
這個bindings裡面的每條記錄,代表一個服務綁定。它的key值就是服務的綁定名稱,value值也是一個數組,這個數組的concrete屬性就是服務綁定時產生的匿名函數,也就是閉包;另外一個參數表示這個服務在多次解析的時候,是否只返回第一次解析得到的對象。這個參數在介紹服務綁定方法時會再繼續介紹。
接下來看看服務綁定的幾種方法及區別:
a. 通過bind方法
app()->bind('service', function(){ return new Service(); },true);
bind是laravel服務綁定的底層方法,它的簽名是:
第一個參數服務綁定名稱,第二個參數服務綁定的結果,第三個參數就表示這個服務是否在多次解析的時候,始終返回第一次解析出的實例。它的預設值是false,意味著這樣的服務在每次解析的時候都會返回一個新的實例。它的值與bindings裡面服務綁定記錄value數組裡面的share屬性是對應的。
b. 通過singleton方法
舉例略。它跟bind的區別在於,它始終是以shared=true的形式進行服務綁定,這是因為它的源碼是這樣的:
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). 首先,自定義的服務提供者都是放在下麵這個目錄的:
其實你放在哪都可以,不過得告訴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名稱的屬性數組吧:
在瞭解以上provider的機制後,這幾個數組的作用也就比較清晰了。其中serviceProviders用來存放所有已經註冊完畢的服務提供者:
loadedProviders跟serviceProviders的作用類似,只是存儲的記錄形式不同:
deferredProviders用來存儲所有的延遲註冊的服務提供者:
跟前面兩個不同的是,deferredProviders存儲的記錄的key值並不是服務提供者的類型名稱,而是服務提供者的provides返回數組裡面的名稱。並且如果一個服務提供者的provides裡面返回了多個服務綁定名稱的話,那麼deferredProviders裡面就會存多條記錄:
這樣是方便根據服務綁定名稱,找到對應的服務提供者,並完成註冊。當服務的解析的時候,會先完成延遲類型的服務提供者的註冊,註冊完畢,這個服務綁定名稱在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數組裡面存入一條記錄:
你看剛纔舉例中的別名就已經添加到這個數組裡面。這個數組裡面每條記錄的key值都是別名。但是value有可能是服務綁定名稱,也有可能是另外一個別名。這是因為別名是可以遞歸的。
2)別名支持遞歸
也就是說,可以對別名再指定別名:
app()->alias('alias_a', 'alias_b');
app()->alias('alias_b', 'alias_c');
3)別名如何應用於服務解析
在解析服務的時候,會先確定這個服務名稱是否為一個別名(只要看看在aliases數組裡是否存在記錄即可),如果不是別名,直接用這個服務名稱進行解析。如果這個服務名稱是一個別名,那麼就會通過調用的方式,找到最終的服務名稱:
如下所有的服務解析都是等價的:
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實例,這就是依賴註入的好處:
當一個類需要某個服務類型的實例時,不需要自己去創造這個服務的實例,只要告訴容器,它需要的實例類型即可,然後容器會根據這個類型, 解析出滿足該類型的服務。如何根據參數類型解析出該參數類型的服務實例呢?其實就是根據參數類型的類型名稱進行解析得到的,所以依賴註入能夠成功的前提是根據參數類型的名稱,能夠成功地解析到一個服務對象。以上之所以能夠通過Illuminate\Contracts\Foundation\Application 這個名稱解析到服務,那是因為在容器實例aliases數組裡面有一條Illuminate\Contracts\Foundation\Application 的別名記錄:
也就是說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