本文面向php語言的laravel框架的用戶,介紹一些laravel框架裡面容器管理方面的使用要點。文章很長,但是內容應該很有用,希望有需要的朋友能看到。php經驗有限,不到位的地方,歡迎幫忙指正。
laravel框架是有一個容器框架,框架應用程序的實例就是一個超大的容器,這個實例在bootstrap/app.php內進行初始化:
$app = app();
app這個輔助函數定義在
<?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:
<?php Route::get('/', function () { dd(app()); return ''; });
結果如下:
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的源碼中看到一些細節:
app()->bind('service', function(){ return new Service(); },true);
bind是laravel服務綁定的底層方法,它的簽名是:
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方法。
以上就是服務綁定與解析的主要內容,涉及的要點較多,希望描述的比較清楚。
前面介紹了服務的綁定。那麼服務的綁定應該在哪個位置處理呢?雖然說,能夠拿到容器實例的地方,就都能進行服務的綁定;但是我們使用服務的綁定的目的,是為了在合適的位置解析出服務實例並使用,如果服務綁定的位置過於隨意,那麼就很難保證在解析的位置能夠准確的解析出服務實例。因為服務能夠解析的前提是服務綁定的代碼先與服務解析的代碼執行;所以,服務綁定通常會在應用程序初始化的時候進行,這樣才能保證業務代碼中(通常是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). 首先,自定義的服務提供者都是放在下面這個目錄的:
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方法裡面,注冊的所有服務綁定名稱即可。
延遲注冊的服務提供者的機制是:
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名稱的屬性數組吧:
app()->singleton('service1', function(){ new CasServerManager(); });
那麼可以通過容器方法alias方法指定別名:
app()->alias('service1', 'alias_a');
這個方法的第一個參數是服務綁定名稱,第二個參數是別名。這個方法調用後,就會在容器實例屬性aliases數組裡面存入一條記錄:
app()->alias('alias_a', 'alias_b'); app()->alias('alias_b', 'alias_c');
app('alias_c'); app('alias_b'); app('alias_a'); app('service1');
4)另外一種指定別名的方式
可以在服務綁定的時候,進行別名的指定。只要按照如下的方式進行綁定即可:
app()->singleton(['service1' => 'alias'], function(){ new CasServerManager(); });
也就是把服務綁定名稱換成數組形式而已。數組記錄的key值就是服務名稱,value值就是別名。
<?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實例,這就是依賴注入的好處:
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); } } }
依賴注入更多地用在接口編程當中,就像上面的舉例類似。再看一個自定義的例子:
<?php interface Inter{ public function method(); } class InterImpl implements Inter{ public function method(){ // } } class Service{ protected $inter; public function __construct(Inter $inter) { $this->inter = $inter; } } app()->singleton(Inter::class,InterImpl::class); app()->singleton(Service::class); Route::get('/', function () { dd(app(Service::class)); return ''; });
按接口進行編程,像Service這種業務類,只需要聲明自己需要一個Inter類型的實例即可。接口的好處在於解耦,將來要更換一種Inter的實現,不需要改Service的代碼,只需要在實例化Service的時候,傳入另外一個Inter的實例即可。有了依賴注入以後,也不用改Service實例化的代碼,只要把Inter這個服務類型,重新做一個綁定,綁定到另外一個實現即可。
app()->singleton(Inter::class,InterImpl2::class);
還有兩個小點,也值的介紹一下。
1) 容器實例的instance方法
這個方法其實也是完成綁定的作用,但是它跟前面介紹的三種綁定方法不同,它是把一個已經存在的實例,綁定到容器:
$service = new Service(); app()->instance('service',$service);
這是它的源碼:
public function instance($abstract, $instance) { $abstract = $this->normalize($abstract); // First, we will extract the alias from the abstract if it is an array so we // are using the correct name when binding the type. If we get an alias it // will be registered with the container so we can resolve it out later. if (is_array($abstract)) { list($abstract, $alias) = $this->extractAlias($abstract); $this->alias($abstract, $alias); } 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. $bound = $this->bound($abstract); $this->instances[$abstract] = $instance; if ($bound) { $this->rebound($abstract); } }
從這個代碼可以看到,instance方法,會直接把外部實例化好的對象,直接存儲到容器的instances裡面。如果這個服務綁定名稱存在bindings記錄,那麼還會做一下重新綁定的操作。也就是說,通過intance方法綁定,是直接綁定服務實例,而原來的bind方法其實只是綁定了一個閉包函數,服務實例要到解析的時候才會創建。
2) 容器實例的share方法
容器實例的singleton方法,綁定的服務在解析的時候,始終返回第一次解析的對象。還有一個方式也能做到這個效果,那就是使用share方法包裝服務綁定的匿名函數:
$this->app['cas'] = $this->app->share(function() { $config = $this->app['config']->get('cas'); return new CasManager($config); });
當我們使用app('cas')解析的時候,始終拿到的都是第一次解析創建的那個CasManager對象。這個跟share方法的實現有關系:
從源碼看出,share方法把服務綁定的閉包再包裝了一下,返回一個新的閉包,並且在這個閉包裡面,加了一個靜態$object變量,它會存儲原始閉包第一次解析調用後的結果,並在後續解析中直接返回,從而保證這個服務的實例只有一個。
全文完,感謝閱讀~