前言
老實說,第一次老大讓我看laravel框架手冊的那天早上,我是很絕望的,因為真的沒接觸過,對我這種渣渣來說,laravel的入門門檻確實有點高了,但還是得硬著頭皮看下去(雖然到現在我還有很多沒看懂,也沒用過)。
後面慢慢根據公司專案的程式碼對laravel也慢慢熟悉起來了,但還是停留在一些表面的功能,例如依賴注入,ORM操作,使用者認證這些和我專案業務邏輯相關的操作,然後對於一些架構基礎的,例如服務提供器,服務容器,中介軟體,Redis等這些一開始就要設定好的東西,我倒是沒實際操作過(因為老大一開始就做好了),所以看手冊還是有點懵。
所以有空的時候逛逛論壇,搜下Google就發現許多關於laravel核心架構的介紹,以及如何使用的網站(確實看完後再去看手冊就好理解多了),下面就根據一個我覺得不錯的網站上面的教學來記錄一下laravel核心架構的學習
網站地址:https://laraweb.net/ 這是一個日本的網站,我覺得挺適合新手的,內容用瀏覽器翻譯過來就ok了,畢竟日文直翻過來很好理解的
關於服務容器
手冊上是這樣介紹的:Laravel 服務容器是用於管理類的依賴和執行依賴注入的工具。依賴注入這個花俏名詞實質上是指:類的依賴項通過建構函式,或者某些情況下通過「setter」方法「注入」到類中。。。。。。(真的看不懂啥意思)
服務容器是用於管理類(服務)的例項化的機制。直接看看服務容器怎麼用
1.在服務容器中註冊類(bind)
$this->app->bind(`sender`,`MailSender`);
//$this->app成為服務容器。
2.從服務容器生成類(make)
$sender = $this->app->make(`sender`);
//從服務容器($this->app)建立一個sender類。
在這種情況下,將返回MailSender的例項。
這是服務容器最簡單的使用,下面是對服務容器的詳細介紹
(主要參考:https://www.cnblogs.com/lyzg/…)
laravel容器基本認識
一開始,index.php 檔案載入 Composer 生成定義的自動載入器,然後從 bootstrap/app.php 指令碼中檢索 Laravel 應用程式的例項。Laravel 本身採取的第一個動作是建立一個 application/ service container 的例項。
$app = new IlluminateFoundationApplication(
dirname(__DIR__)
);
這個檔案在每一次請求到達laravel框架都會執行,所建立的$app即是laravel框架的應用程式例項,它在整個請求生命週期都是唯一的。laravel提供了很多服務,包括認證,資料庫,快取,訊息佇列等等,$app作為一個容器管理工具,負責幾乎所有服務元件的例項化以及例項的生命週期管理。當需要一個服務類來完成某個功能的時候,僅需要通過容器解析出該型別的一個例項即可。從最終的使用方式來看,laravel容器對服務例項的管理主要包括以下幾個方面:
- 服務的繫結與解析
- 服務提供者的管理
- 別名的作用
- 依賴注入
先了解如何在程式碼中獲取到容器例項,再學習上面四個關鍵
如何在程式碼中獲取到容器例項
第一種是
$app = app();
//app這個輔助函式定義在vendorlaravelframeworksrcIlluminateFoundationhelper.php
裡面,,這個檔案定義了很多help函式,並且會通過composer自動載入到專案中。
所以,在參與http請求處理的任何程式碼位置都能夠訪問其中的函式,比如app()。
第二種是
Route::get(`/`, function () {
dd(App::basePath());
return ``;
});
//這個其實是用到Facade,中文直譯貌似叫門面,在config/app.php中,
有一節陣列aliases專門用來配置一些型別的別名,第一個就是`App` => IlluminateSupportFacadesApp::class,
具體的Google一下laravel有關門面的具體實現方式
第三種是
在服務提供者裡面直接使用$this->app。服務提供者後面還會介紹,現在只是引入。因為服務提供者類都是由laravel容器例項化的,這些類都繼承自IlluminateSupportServiceProvider,它定義了一個例項屬性$app:
abstract class ServiceProvider
{
protected $app;
laravel在例項化服務提供者的時候,會把laravel容器例項注入到這個$app上面。所以我們在服務提供者裡面,始終能通過$this->$app訪問到laravel容器例項,而不需要再使用app()函式或者App Facade了。
如何理解服務繫結與解析
淺義層面理解,容器既然用來儲存物件,那麼就要有一個物件存入跟物件取出的過程。這個物件存入跟物件取出的過程在laravel裡面稱為服務的繫結與解析。
app()->bind(`service`, `this is service1`);
app()->bind(`service2`, [
`hi` => function(){
//say hi
}
]);
class Service {
}
app()->bind(`service3`, function(){
return new Service();
});
還有一個單例繫結singleton,是bind的一種特殊情況(第三個引數為true),繫結到容器的物件只會被解析一次,之後的呼叫都返回相同的例項
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
在繫結的時候,我們可以直接繫結已經初始化好的資料(基本型別、陣列、物件例項),還可以用匿名函式來繫結。用匿名函式的好處在於,這個服務繫結到容器以後,並不會立即產生服務最終的物件,只有在這個服務解析的時候,匿名函式才會執行,此時才會產生這個服務對應的服務例項。
實際上,當我們使用singleton,bind方法以及陣列形式,(這三個方法是後面要介紹的繫結的方法),進行服務繫結的時候,如果繫結的服務形式,不是一個匿名函式,也會在laravel內部用一個匿名函式包裝起來,這樣的話, 不輪繫結什麼內容,都能做到前面介紹的懶初始化的功能,這對於容器的效能是有好處的。這個可以從bind的原始碼中看到一些細節:
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
看看bind的底層程式碼
public function bind($abstract, $concrete = null, $shared = false)
第一個引數服務繫結名稱,第二個引數服務繫結的結果(也就是閉包,得到例項),第三個引數就表示這個服務是否在多次解析的時候,始終返回第一次解析出的例項(也就是單例繫結singleton)。
服務繫結還可以通過陣列的方式:
app()[`service`] = function(){
return new Service();
};
繫結大概就這些,接下來看解析,也就是取出來用
$service= app()->make(`service`);
這個方法接收兩個引數,第一個是服務的繫結名稱和服務繫結名稱的別名,如果是別名,那麼就會根據服務繫結名稱的別名配置,找到最終的服務繫結名稱,然後進行解析;第二個引數是一個陣列,最終會傳遞給服務繫結產生的閉包。
看原始碼:
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
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.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$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.
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;
}
第一步:
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
該方法主要是區分,解析的物件是否有引數,如果有引數,還需要對引數做進一步的分析,因為傳入的引數,也可能是依賴注入的,所以還需要對傳入的引數進行解析;這個後面再分析。
第二步:
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
如果是繫結的單例,並且不需要上面的引數依賴。我們就可以直接返回 $this->instances[$abstract]。
第三步:
$concrete = $this->getConcrete($abstract);
...
/**
* Get the concrete type for a given abstract.
*
* @param string $abstract
* @return mixed $concrete
*/
protected function getConcrete($abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// If we don`t have a registered resolver or concrete for the type, we`ll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract][`concrete`];
}
return $abstract;
}
這一步主要是先從繫結的上下文找,是不是可以找到繫結類;如果沒有,則再從 $bindings[] 中找關聯的實現類;最後還沒有找到的話,就直接返回 $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);
}
...
/**
* Determine if the given concrete is buildable.
*
* @param mixed $concrete
* @param string $abstract
* @return bool
*/
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
如果之前找到的 $concrete 返回的是 $abstract 值,或者 $concrete 是個閉包,則執行 $this->build($concrete),否則,表示存在巢狀依賴的情況,則採用遞迴的方法執行 $this->make($concrete),直到所有的都解析完為止。
$this->build($concrete)
/**
* Instantiate a concrete instance of the given type.
*
* @param string $concrete
* @return mixed
*
* @throws IlluminateContractsContainerBindingResolutionException
*/
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 of Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$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)) {
// 將 build 過程的內容 pop,然後直接構造物件輸出。
array_pop($this->buildStack);
return new $concrete;
}
// 獲取建構函式的引數
$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
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
上面這一段有關解析make的介紹主要參考:
coding01:看 Laravel 原始碼瞭解 Container
這一篇就主要學習laravel的服務容器以及它的繫結和解析,雖然目前能力無法對框架原始碼每一個地方都弄懂,但通過這幾篇優秀的文章,我將其進行整理結合,這過程讓我更加理解laravel的一些核心內容,起碼別人問起來我多多少少能說出一些,這就是進步。
後面有關服務提供者,依賴注入,中介軟體等內容的學習將放在後續的部落格文章中,歡迎看看我的其他部落格文章:https://zgxxx.github.io/。
以上相關知識的引用已經註明出處,若有侵權,請聯絡我,感謝這些優秀文章的作者