Laravel修煉:服務容器綁定與解析

前言

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

關於服務容器

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

  1.在服務容器中註冊類(bind)laravel

$this->app->bind('sender','MailSender');
//$this->app成爲服務容器。

  2.從服務容器生成類(make)git

$sender = $this->app->make('sender');
//從服務容器($this->app)建立一個sender類。
在這種狀況下,將返回MailSender的實例。

  這是服務容器最簡單的使用,下面是對服務容器的詳細介紹
(主要參考:https://www.cnblogs.com/lyzg/...github

laravel容器基本認識

  一開始,index.php 文件加載 Composer 生成定義的自動加載器,而後從 bootstrap/app.php 腳本中檢索 Laravel 應用程序的實例。Laravel 自己採起的第一個動做是建立一個 application/ service container 的實例。web

$app = new Illuminate\Foundation\Application(
    dirname(__DIR__)
);

  這個文件在每一次請求到達laravel框架都會執行,所建立的$app便是laravel框架的應用程序實例,它在整個請求生命週期都是惟一的。laravel提供了不少服務,包括認證,數據庫,緩存,消息隊列等等,$app做爲一個容器管理工具,負責幾乎全部服務組件的實例化以及實例的生命週期管理。當須要一個服務類來完成某個功能的時候,僅須要經過容器解析出該類型的一個實例便可。從最終的使用方式來看,laravel容器對服務實例的管理主要包括如下幾個方面:數據庫

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

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

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

第一種是數組

$app = app();
//app這個輔助函數定義在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php
裏面,,這個文件定義了不少help函數,而且會經過composer自動加載到項目中。
因此,在參與http請求處理的任何代碼位置都可以訪問其中的函數,好比app()。

第二種是瀏覽器

Route::get('/', function () {
    dd(App::basePath());
    return '';
});
//這個實際上是用到Facade,中文直譯貌似叫門面,在config/app.php中,
有一節數組aliases專門用來配置一些類型的別名,第一個就是'App' => Illuminate\Support\Facades\App::class,
具體的Google一下laravel有關門面的具體實現方式

第三種是

  在服務提供者裏面直接使用$this->app。服務提供者後面還會介紹,如今只是引入。由於服務提供者類都是由laravel容器實例化的,這些類都繼承自Illuminate\Support\ServiceProvider,它定義了一個實例屬性$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 \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->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/。  以上相關知識的引用已經註明出處,如有侵權,請聯繫我,感謝這些優秀文章的做者

相關文章
相關標籤/搜索