溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

laravel服務(wù)容器的概念是什么

發(fā)布時間:2022-02-15 10:38:05 來源:億速云 閱讀:132 作者:iii 欄目:編程語言

這篇文章主要介紹“l(fā)aravel服務(wù)容器的概念是什么”,在日常操作中,相信很多人在laravel服務(wù)容器的概念是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”laravel服務(wù)容器的概念是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

在laravel中,服務(wù)容器是一個用于管理類依賴以及實現(xiàn)依賴注入的強有力工具。當(dāng)應(yīng)用程序需要使用某一個服務(wù)時,服務(wù)容器會將服務(wù)解析出來,并同時自動解決服務(wù)之間的依賴,然后交給應(yīng)用程序使用。

本教程操作環(huán)境:windows7系統(tǒng)、Laravel6版、Dell G3電腦。

什么是服務(wù)容器

Laravel 服務(wù)容器是一個用于管理類依賴以及實現(xiàn)依賴注入的強有力工具。依賴注入這個名詞表面看起來花哨,實質(zhì)上是指:通過構(gòu)造函數(shù),或者某些情況下通過「setter」方法將類依賴「注入」到類中。

Laravel中的功能模塊比如 Route、Eloquent ORM、Request、Response等等等等,實際上都是與核心無關(guān)的類模塊提供的,這些類從注冊到實例化,最終被我們所使用,其實都是 laravel 的服務(wù)容器負(fù)責(zé)的。

服務(wù)容器中有兩個概念控制反轉(zhuǎn)(IOC)依賴注入(DI):

依賴注入和控制反轉(zhuǎn)是對同一件事情的不同描述,它們描述的角度不同。依賴注入是從應(yīng)用程序的角度在描述,應(yīng)用程序依賴容器創(chuàng)建并注入它所需要的外部資源。而控制反轉(zhuǎn)是從容器的角度在描述,容器控制應(yīng)用程序,由容器反向的向應(yīng)用程序注入應(yīng)用程序所需要的外部資源。

在Laravel中框架把自帶的各種服務(wù)綁定到服務(wù)容器,我們也可以綁定自定義服務(wù)到容器。當(dāng)應(yīng)用程序需要使用某一個服務(wù)時,服務(wù)容器會將服務(wù)解析出來,同時自動解決服務(wù)之間的依賴,然后交給應(yīng)用程序使用。

下面探討一下Laravel中的服務(wù)綁定和解析是如何實現(xiàn)的。

服務(wù)綁定

常用的綁定服務(wù)到容器的方法有instance, bind, singleton, alias。下面我們分別來看一下。

instance

將一個已存在的對象綁定到服務(wù)容器里,隨后通過名稱解析該服務(wù)時,容器將總返回這個綁定的實例。

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);

會把對象注冊到服務(wù)容器的$instnces屬性里

[
     'HelpSpot\Api' => $api//$api是API類的對象,這里簡寫了
 ]

bind

綁定服務(wù)到服務(wù)容器

有三種綁定方式:

1.綁定自身

$this->app->bind('HelpSpot\API', null);

2.綁定閉包

$this->app->bind('HelpSpot\API', function () {
    return new HelpSpot\API();
});//閉包直接提供類實現(xiàn)方式
$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});//閉包返回需要依賴注入的類

3. 綁定接口和實現(xiàn)

$this->app->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');

針對第一種情況,其實在bind方法內(nèi)部會在綁定服務(wù)之前通過getClosure()為服務(wù)生成閉包,我們來看一下bind方法源碼。

public function bind($abstract, $concrete = null, $shared = false)
{
    $abstract = $this->normalize($abstract);
    
    $concrete = $this->normalize($concrete);
    //如果$abstract為數(shù)組類似['Illuminate/ServiceName' => 'service_alias']
    //抽取別名"service_alias"并且注冊到$aliases[]中
    //注意:數(shù)組綁定別名的方式在5.4中被移除,別名綁定請使用下面的alias方法
    if (is_array($abstract)) {
        list($abstract, $alias) = $this->extractAlias($abstract);

        $this->alias($abstract, $alias);
    }

    $this->dropStaleInstances($abstract);

    if (is_null($concrete)) {
        $concrete = $abstract;
    }
    //如果只提供$abstract,則在這里為其生成concrete閉包
    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }

    $this->bindings[$abstract] = compact('concrete', 'shared');

    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}


protected function getClosure($abstract, $concrete)
{
    // $c 就是$container,即服務(wù)容器,會在回調(diào)時傳遞給這個變量
    return function ($c, $parameters = []) use ($abstract, $concrete) {
        $method = ($abstract == $concrete) ? 'build' : 'make';

        return $c->$method($concrete, $parameters);
    };
}

bind把服務(wù)注冊到服務(wù)容器的$bindings屬性里類似這樣:

$bindings = [
    'HelpSpot\API' =>  [//閉包綁定
        'concrete' => function ($app, $paramters = []) {
            return $app->build('HelpSpot\API');
        },
        'shared' => false//如果是singleton綁定,這個值為true
    ]        
    'Illuminate\Tests\Container\IContainerContractStub' => [//接口實現(xiàn)綁定
        'concrete' => 'Illuminate\Tests\Container\ContainerImplementationStub',
        'shared' => false
    ]
]

singleton

public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

singleton 方法是bind方法的變種,綁定一個只需要解析一次的類或接口到容器,然后接下來對于容器的調(diào)用該服務(wù)將會返回同一個實例

alias

把服務(wù)和服務(wù)別名注冊到容器:

public function alias($abstract, $alias)
{
    $this->aliases[$alias] = $this->normalize($abstract);
}

alias 方法在上面講bind方法里有用到過,它會把把服務(wù)別名和服務(wù)類的對應(yīng)關(guān)系注冊到服務(wù)容器的$aliases屬性里。

例如:

$this->app->alias('\Illuminate\ServiceName', 'service_alias');

綁定完服務(wù)后在使用時就可以通過

$this->app->make('service_alias');

將服務(wù)對象解析出來,這樣make的時候就不用寫那些比較長的類名稱了,對make方法的使用體驗上有很大提升。

服務(wù)解析

make: 從服務(wù)容器中解析出服務(wù)對象,該方法接收你想要解析的類名或接口名作為參數(shù)

/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array   $parameters
 * @return mixed
 */
public function make($abstract, array $parameters = [])
{
    //getAlias方法會假定$abstract是綁定的別名,從$aliases找到映射的真實類型名
    //如果沒有映射則$abstract即為真實類型名,將$abstract原樣返回
    $abstract = $this->getAlias($this->normalize($abstract));
    // 如果服務(wù)是通過instance()方式綁定的,就直接解析返回綁定的service
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }
    // 獲取$abstract接口對應(yīng)的$concrete(接口的實現(xiàn))
    $concrete = $this->getConcrete($abstract);
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete, $parameters);
    } else {
        //如果時接口實現(xiàn)這種綁定方式,通過接口拿到實現(xiàn)后需要再make一次才能
        //滿足isBuildable的條件 ($abstract === $concrete)
        $object = $this->make($concrete, $parameters);
    }
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }
    //如果服務(wù)是以singleton方式注冊進(jìn)來的則,把構(gòu)建好的服務(wù)對象放到$instances里,
    //避免下次使用時重新構(gòu)建
    if ($this->isShared($abstract)) {
        $this->instances[$abstract] = $object;
    }
    $this->fireResolvingCallbacks($abstract, $object);
    $this->resolved[$abstract] = true;
    return $object;
}
protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }
    // 如果是$abstract之前沒有注冊類實現(xiàn)到服務(wù)容器里,則服務(wù)容器會認(rèn)為$abstract本身就是接口的類實現(xiàn)
    if (! isset($this->bindings[$abstract])) {
        return $abstract;
    }
    return $this->bindings[$abstract]['concrete'];
}
protected function isBuildable($concrete, $abstract)
{        
    return $concrete === $abstract || $concrete instanceof Closure;
}

通過對make方法的梳理我們發(fā)現(xiàn),build方法的職能是構(gòu)建解析出來的服務(wù)的對象的,下面看一下構(gòu)建對象的具體流程。(構(gòu)建過程中用到了PHP類的反射來實現(xiàn)服務(wù)的依賴注入)

public function build($concrete, array $parameters = [])
{
    // 如果是閉包直接執(zhí)行閉包并返回(對應(yīng)閉包綁定)
    if ($concrete instanceof Closure) {
        return $concrete($this, $parameters);
    }
    
    // 使用反射ReflectionClass來對實現(xiàn)類進(jìn)行反向工程
    $reflector = new ReflectionClass($concrete);
    // 如果不能實例化,這應(yīng)該是接口或抽象類,再或者就是構(gòu)造函數(shù)是private的
    if (! $reflector->isInstantiable()) {
        if (! empty($this->buildStack)) {
            $previous = implode(', ', $this->buildStack);
            $message = "Target [$concrete] is not instantiable while building [$previous].";
        } else {
            $message = "Target [$concrete] is not instantiable.";
        }
        throw new BindingResolutionException($message);
    }
    $this->buildStack[] = $concrete;
    // 獲取構(gòu)造函數(shù)
    $constructor = $reflector->getConstructor();
    // 如果構(gòu)造函數(shù)是空,說明沒有任何依賴,直接new返回
    if (is_null($constructor)) {
        array_pop($this->buildStack);
        return new $concrete;
    }
    
    // 獲取構(gòu)造函數(shù)的依賴(形參),返回一組ReflectionParameter對象組成的數(shù)組表示每一個參數(shù)
    $dependencies = $constructor->getParameters();
    $parameters = $this->keyParametersByArgument(
        $dependencies, $parameters
    );
    // 構(gòu)建構(gòu)造函數(shù)需要的依賴
    $instances = $this->getDependencies(
        $dependencies, $parameters
    );
    array_pop($this->buildStack);
    return $reflector->newInstanceArgs($instances);
}
//獲取依賴
protected function getDependencies(array $parameters, array $primitives = [])
{
    $dependencies = [];
    foreach ($parameters as $parameter) {
        $dependency = $parameter->getClass();
        // 某一依賴值在$primitives中(即build方法的$parameters參數(shù))已提供
        // $parameter->name返回參數(shù)名
        if (array_key_exists($parameter->name, $primitives)) {
            $dependencies[] = $primitives[$parameter->name];
        } 
        elseif (is_null($dependency)) {
             // 參數(shù)的ReflectionClass為null,說明是基本類型,如'int','string'
            $dependencies[] = $this->resolveNonClass($parameter);
        } else {
             // 參數(shù)是一個類的對象, 則用resolveClass去把對象解析出來
            $dependencies[] = $this->resolveClass($parameter);
        }
    }
    return $dependencies;
}
//解析出依賴類的對象
protected function resolveClass(ReflectionParameter $parameter)
{
    try {
        // $parameter->getClass()->name返回的是類名(參數(shù)在typehint里聲明的類型)
        // 然后遞歸繼續(xù)make(在make時發(fā)現(xiàn)依賴類還有其他依賴,那么會繼續(xù)make依賴的依賴
        // 直到所有依賴都被解決了build才結(jié)束)
        return $this->make($parameter->getClass()->name);
    } catch (BindingResolutionException $e) {
        if ($parameter->isOptional()) {
            return $parameter->getDefaultValue();
        }
        throw $e;
    }
}

服務(wù)容器就是laravel的核心, 它通過依賴注入很好的替我們解決對象之間的相互依賴關(guān)系,而又通過控制反轉(zhuǎn)讓外部來來定義具體的行為(Route, Eloquent這些都是外部模塊,它們自己定義了行為規(guī)范,這些類從注冊到實例化給你使用才是服務(wù)容器負(fù)責(zé)的)。

一個類要被容器所能夠提取,必須要先注冊至這個容器。既然 laravel 稱這個容器叫做服務(wù)容器,那么我們需要某個服務(wù),就得先注冊、綁定這個服務(wù)到容器,那么提供服務(wù)并綁定服務(wù)至容器的東西,就是服務(wù)提供器(ServiceProvider)。服務(wù)提供者主要分為兩個部分,register(注冊) 和 boot(引導(dǎo)、初始化)

到此,關(guān)于“l(fā)aravel服務(wù)容器的概念是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI