2018-10-18

<h2>前言</h2>

<p>? 老實說,第一次老大讓我看laravel框架手冊的那天早上,我是很絕望的,因為真的沒接觸過,對我這種渣渣來說,laravel的入門門檻確實有點高了,但還是得硬著頭皮看下去(雖然到現在我還有很多沒看懂,也沒用過)。<br>? 后面慢慢根據公司項目的代碼對laravel也慢慢熟悉起來了,但還是停留在一些表面的功能,例如依賴注入,ORM操作,用戶認證這些和我項目業務邏輯相關的操作,然后對于一些架構基礎的,例如服務提供器,服務容器,中間件,Redis等這些一開始就要設置好的東西,我倒是沒實際操作過(因為老大一開始就做好了),所以看手冊還是有點懵。<br>? 所以有空的時候逛逛論壇,搜下Google就發現許多關于laravel核心架構的介紹,以及如何使用的網站(確實看完后再去看手冊就好理解多了),下面就根據一個我覺得不錯的網站上面的教學來記錄一下laravel核心架構的學習<br>網站地址:<a rel="nofollow noreferrer">https://laraweb.net/</a> 這是一個日本的網站,我覺得挺適合新手的,內容用瀏覽器翻譯過來就ok了,畢竟日文直翻過來很好理解的</p>

<h2>關于服務容器</h2>

<p>? 手冊上是這樣介紹的:Laravel 服務容器是用于管理類的依賴和執行依賴注入的工具。依賴注入這個花俏名詞實質上是指:類的依賴項通過構造函數,或者某些情況下通過「setter」方法「注入」到類中。。。。。。(真的看不懂啥意思)<br>? 服務容器是用于管理類(服務)的實例化的機制。直接看看服務容器怎么用</p>

<p>? 1.在服務容器中注冊類(bind)</p>

```

$this-&gt;app-&gt;bind('sender','MailSender');

//$this-&gt;app成為服務容器。

```

<p>? 2.從服務容器生成類(make)</p>

```

$sender = $this-&gt;app-&gt;make('sender');

//從服務容器($this-&gt;app)創建一個sender類。

在這種情況下,將返回MailSender的實例。

```

<p>? 這是服務容器最簡單的使用,下面是對服務容器的詳細介紹<br>(主要參考:<a rel="nofollow noreferrer">https://www.cnblogs.com/lyzg/...</a>)</p>

<h3>laravel容器基本認識</h3>

<p>? 一開始,index.php 文件加載 Composer 生成定義的自動加載器,然后從 bootstrap/app.php 腳本中檢索 Laravel 應用程序的實例。Laravel 本身采取的第一個動作是創建一個 application/ service container 的實例。</p>

```

$app = new Illuminate\Foundation\Application(

? ? dirname(__DIR__)

);

```

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

<ul>

<li><strong>服務的綁定與解析</strong></li>

<li><strong>服務提供者的管理</strong></li>

<li><strong>別名的作用</strong></li>

<li><strong>依賴注入</strong></li>

</ul>

<p>先了解如何在代碼中獲取到容器實例,再學習上面四個關鍵</p>

<h4>如何在代碼中獲取到容器實例</h4>

<p>第一種是</p>

```

$app = app();

//app這個輔助函數定義在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php

里面,,這個文件定義了很多help函數,并且會通過composer自動加載到項目中。

所以,在參與http請求處理的任何代碼位置都能夠訪問其中的函數,比如app()。

```

<p>第二種是</p>

```

Route::get('/', function () {

? ? dd(App::basePath());

? ? return '';

});

//這個其實是用到Facade,中文直譯貌似叫門面,在config/app.php中,

有一節數組aliases專門用來配置一些類型的別名,第一個就是'App' =&gt; Illuminate\Support\Facades\App::class,

具體的Google一下laravel有關門面的具體實現方式

```

<p>第三種是</p>

<p>? 在服務提供者里面直接使用$this-&gt;app。服務提供者后面還會介紹,現在只是引入。因為服務提供者類都是由laravel容器實例化的,這些類都繼承自Illuminate\Support\ServiceProvider,它定義了一個實例屬性$app:</p>

```

abstract class ServiceProvider

{

? ? protected $app;

```

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

<h4>如何理解服務綁定與解析</h4>

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

```

app()-&gt;bind('service', 'this is service1');

app()-&gt;bind('service2', [

? ? 'hi' =&gt; function(){

? ? ? ? //say hi

? ? }

]);

class Service {

}

app()-&gt;bind('service3', function(){

? ? return new Service();

});

```

<p>? 還有一個單例綁定singleton,是bind的一種特殊情況(第三個參數為true),綁定到容器的對象只會被解析一次,之后的調用都返回相同的實例</p>

```

public function singleton($abstract, $concrete = null)

{

$this-&gt;bind($abstract, $concrete, true);

}

```

<p>? 在綁定的時候,我們可以直接綁定已經初始化好的數據(基本類型、數組、對象實例),還可以用匿名函數來綁定。用匿名函數的好處在于,這個服務綁定到容器以后,并不會立即產生服務最終的對象,只有在這個服務解析的時候,匿名函數才會執行,此時才會產生這個服務對應的服務實例。<br>? 實際上,當我們使用singleton,bind方法以及數組形式,(這三個方法是后面要介紹的綁定的方法),進行服務綁定的時候,如果綁定的服務形式,不是一個匿名函數,也會在laravel內部用一個匿名函數包裝起來,這樣的話, 不輪綁定什么內容,都能做到前面介紹的懶初始化的功能,這對于容器的性能是有好處的。這個可以從bind的源碼中看到一些細節:</p>

```

if (! $concrete instanceof Closure) {

? ? $concrete = $this-&gt;getClosure($abstract, $concrete);

}

```

<p>看看bind的底層代碼</p>

```

public function bind($abstract, $concrete = null, $shared = false)

```

<p>? 第一個參數服務綁定名稱,第二個參數服務綁定的結果(也就是閉包,得到實例),第三個參數就表示這個服務是否在多次解析的時候,始終返回第一次解析出的實例(也就是單例綁定singleton)。</p>

<p>? 服務綁定還可以通過數組的方式:</p>

```

app()['service'] = function(){

? ? return new Service();

};

```

<p>綁定大概就這些,接下來看解析,也就是取出來用</p>

```

$service= app()-&gt;make('service');

```

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

<p>看源碼:</p>

```

/**

* Resolve the given type from the container.

*

* @param? string? $abstract

* @param? array? $parameters

* @return mixed

*/

public function make($abstract, array $parameters = [])

{

? ? return $this-&gt;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-&gt;getAlias($abstract);

? ? $needsContextualBuild = ! empty($parameters) || ! is_null(

? ? ? ? $this-&gt;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-&gt;instances[$abstract]) &amp;&amp; ! $needsContextualBuild) {

? ? ? ? return $this-&gt;instances[$abstract];

? ? }

? ? $this-&gt;with[] = $parameters;

? ? $concrete = $this-&gt;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-&gt;isBuildable($concrete, $abstract)) {

? ? ? ? $object = $this-&gt;build($concrete);

? ? } else {

? ? ? ? $object = $this-&gt;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-&gt;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-&gt;isShared($abstract) &amp;&amp; ! $needsContextualBuild) {

? ? ? ? $this-&gt;instances[$abstract] = $object;

? ? }

? ? $this-&gt;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-&gt;resolved[$abstract] = true;

? ? array_pop($this-&gt;with);

? ? return $object;

}

```

<p>第一步:</p>

```

$needsContextualBuild = ! empty($parameters) || ! is_null(

? ? $this-&gt;getContextualConcrete($abstract)

);

```

<p>? 該方法主要是區分,解析的對象是否有參數,如果有參數,還需要對參數做進一步的分析,因為傳入的參數,也可能是依賴注入的,所以還需要對傳入的參數進行解析;這個后面再分析。</p>

<p>第二步:</p>

```

if (isset($this-&gt;instances[$abstract]) &amp;&amp; ! $needsContextualBuild) {

? ? return $this-&gt;instances[$abstract];

}

```

<p>? 如果是綁定的單例,并且不需要上面的參數依賴。我們就可以直接返回 $this-&gt;instances[$abstract]。</p>

<p>第三步:</p>

```

$concrete = $this-&gt;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-&gt;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-&gt;bindings[$abstract])) {

? ? ? ? return $this-&gt;bindings[$abstract]['concrete'];

? ? }

? ? return $abstract;

}

```

<p>? 這一步主要是先從綁定的上下文找,是不是可以找到綁定類;如果沒有,則再從 $bindings[] 中找關聯的實現類;最后還沒有找到的話,就直接返回 $abstract 本身。</p>

```

// 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-&gt;isBuildable($concrete, $abstract)) {

? ? $object = $this-&gt;build($concrete);

} else {

? ? $object = $this-&gt;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;

}

```

<p>? 如果之前找到的 $concrete 返回的是 $abstract 值,或者 $concrete 是個閉包,則執行 $this-&gt;build($concrete),否則,表示存在嵌套依賴的情況,則采用遞歸的方法執行 $this-&gt;make($concrete),直到所有的都解析完為止。</p>

<p><em>$this-&gt;build($concrete)</em></p>

```

/**

* Instantiate a concrete instance of the given type.

*

* @param? string? $concrete

* @return mixed

*

* @throws \Illuminate\Contracts\Container\BindingResolutionException

*/

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-&gt;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-&gt;isInstantiable()) {

? ? ? ? return $this-&gt;notInstantiable($concrete);

? ? }

? ? $this-&gt;buildStack[] = $concrete;

? ? // 獲取構造函數

? ? $constructor = $reflector-&gt;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-&gt;buildStack);

? ? ? ? return new $concrete;

? ? }

? ? // 獲取構造函數的參數

? ? $dependencies = $constructor-&gt;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-&gt;resolveDependencies(

? ? ? ? $dependencies

? ? );

? ? array_pop($this-&gt;buildStack);

? ? return $reflector-&gt;newInstanceArgs($instances);

}

```

<p>上面這一段有關解析make的介紹主要參考:<br><a rel="nofollow noreferrer">coding01:看 Laravel 源代碼了解 Container</a></p>

<p>? 這一篇就主要學習laravel的服務容器以及它的綁定和解析,雖然目前能力無法對框架源碼每一個地方都弄懂,但通過這幾篇優秀的文章,我將其進行整理結合,這過程讓我更加理解laravel的一些核心內容,起碼別人問起來我多多少少能說出一些,這就是進步。</p>

<p>? 后面有關服務提供者,依賴注入,中間件等內容的學習將放在后續的博客文章中,歡迎看看我的其他博客文章:<a rel="nofollow noreferrer">https://zgxxx.github.io/</a>。<br>? 以上相關知識的引用已經注明出處,若有侵權,請聯系我,感謝這些優秀文章的作者</p>

原文地址:https://zgxxx.github.io/2018/10/14/20181013/

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,782評論 18 139
  • 本文面向php語言的laravel框架的用戶,介紹一些laravel框架里面容器管理方面的使用要點。文章很長,但是...
    spacexxxx閱讀 1,174評論 0 1
  • 由于本人在學習laravel框架,在框架里面發現了很多的匿名函數,由于本來對這個沒有了解,所以理解不了對應的框架邏...
    小山丘321閱讀 683評論 0 0
  • 每天早晨走路半小時上班,堅持了有快4個月了,身體感覺越來越健康,走路變成了一件很愉快的事情。 久不見面的朋友見面時...
    感覺教練_PCC閱讀 1,023評論 0 2
  • 以我數百次大大小小的經驗來看,要想完全不害怕考試根本就是不可能的(除非在內心深處你壓根不把它當做考試)。...
    有頭腦閱讀 445評論 0 0