laravelサービスコンテナとは何ですか

青灯夜游
リリース: 2022-02-14 16:31:39
オリジナル
2760 人が閲覧しました

laravel では、サービス コンテナはクラスの依存関係を管理し、依存関係の注入を実装するための強力なツールです。アプリケーションが特定のサービスを使用する必要がある場合、サービス コンテナはサービスを解決し、サービス間の依存関係を自動的に解決して、それをアプリケーションに渡して使用します。

laravelサービスコンテナとは何ですか

このチュートリアルの動作環境: Windows 7 システム、Laravel 6 バージョン、Dell G3 コンピューター。

サービスコンテナとは

Laravel サービスコンテナは、クラスの依存関係を管理し、依存関係の注入を実装するための強力なツールです。依存関係注入という用語は、表面的には派手に見えるかもしれませんが、本質的には、コンストラクター、場合によっては「セッター」メソッドを通じて、クラスの依存関係をクラスに「注入」することを意味します。

Laravel の Route、Eloquent ORM、Request、Response などの機能モジュールは、実際にはコアとは関係のないクラスモジュールによって提供されており、これらのクラスは登録からインスタンス化まで、最終的にはクラスモジュールとして提供されます。実際の使用はLaravelのサービスコンテナの責任です。

サービス コンテナには 2 つの概念があります 制御の反転 (IOC) 依存性注入 (DI) :

##依存性の注入と制御の反転は、同じものを異なる視点から説明したものです。依存関係の注入はアプリケーションの観点から説明されており、アプリケーションはコンテナーに依存して、必要な外部リソースを作成して注入します。制御の反転をコンテナの観点から説明すると、コンテナはアプリケーションを制御し、コンテナはアプリケーションが必要とする外部リソースをアプリケーションに逆注入します。

Laravel では、フレームワークがさまざまなサービスをサービス コンテナにバインドしますが、カスタム サービスをコンテナにバインドすることもできます。アプリケーションが特定のサービスを使用する必要がある場合、サービス コンテナはサービスを解決し、サービス間の依存関係を自動的に解決して、それをアプリケーションに渡して使用します。

サービスのバインディングと解析が Laravel でどのように実装されるかについて説明しましょう。

サービス バインディング

サービスをコンテナにバインドするために一般的に使用されるメソッドには、インスタンス、バインド、シングルトン、エイリアスなどがあります。個別に見てみましょう。

インスタンス

既存のオブジェクトをサービス コンテナにバインドします。その後サービスが名前によって解決されると、コンテナは常にバインドされたインスタンスを返します。

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);
ログイン後にコピー

オブジェクトはサービス コンテナの $instnces 属性に登録されます

[
     'HelpSpot\Api' => $api//$api是API类的对象,这里简写了
 ]
ログイン後にコピー

bind

サービスをサービスにバインドしますコンテナ

バインディング メソッドは 3 つあります:

1. バインド自体

$this->app->bind('HelpSpot\API', null);
ログイン後にコピー

2. クロージャをバインド

$this->app->bind('HelpSpot\API', function () {
    return new HelpSpot\API();
});//闭包直接提供类实现方式
$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});//闭包返回需要依赖注入的类
ログイン後にコピー

3. インターフェイスと実装をバインド

$this->app->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');
ログイン後にコピー

最初の状況を考慮して、実際には、bind メソッド内で、サービスをバインドする前に getClosure() を通じてサービスのクロージャが生成されます。bind メソッドのソース コードを見てみましょう。

public function bind($abstract, $concrete = null, $shared = false)
{
    $abstract = $this->normalize($abstract);
    
    $concrete = $this->normalize($concrete);
    //如果$abstract为数组类似['Illuminate/ServiceName' => 'service_alias']
    //抽取别名"service_alias"并且注册到$aliases[]中
    //注意:数组绑定别名的方式在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,即服务容器,会在回调时传递给这个变量
    return function ($c, $parameters = []) use ($abstract, $concrete) {
        $method = ($abstract == $concrete) ? 'build' : 'make';

        return $c->$method($concrete, $parameters);
    };
}
ログイン後にコピー

bind は、次のようにサービスをサービス コンテナの $bindings 属性に登録します。

$bindings = [
    'HelpSpot\API' =>  [//闭包绑定
        'concrete' => function ($app, $paramters = []) {
            return $app->build('HelpSpot\API');
        },
        'shared' => false//如果是singleton绑定,这个值为true
    ]        
    'Illuminate\Tests\Container\IContainerContractStub' => [//接口实现绑定
        'concrete' => 'Illuminate\Tests\Container\ContainerImplementationStub',
        'shared' => false
    ]
]
ログイン後にコピー

singleton

public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}
ログイン後にコピー

singleton メソッドは、バインド メソッドのバリアントでは、解析が 1 回だけ必要なクラスまたはインターフェイスをコンテナにバインドすると、サービスはその後のコンテナ呼び出しに対して同じインスタンスを返します

alias

サービスとサービス エイリアスをコンテナに登録する:

public function alias($abstract, $alias)
{
    $this->aliases[$alias] = $this->normalize($abstract);
}
ログイン後にコピー

alias メソッドは、上記のバインド メソッドで役立ち、サービス エイリアスとサービス クラスの対応関係を $ に登録します。サービスコンテナのaliases属性。

例:

$this->app->alias('\Illuminate\ServiceName', 'service_alias');
ログイン後にコピー

サービスをバインドした後、使用時に

$this->app->make('service_alias');
ログイン後にコピー

を通じてサービス オブジェクトを解析できるため、これらを長く記述する必要がなくなります。クラス名が変更され、make メソッドの使用感が大幅に向上しました。

サービス解析

make: サービス コンテナからサービス オブジェクトを解析します。このメソッドは、解析するクラス名またはインターフェイス名を受け取りますパラメータとして

/**
 * 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));
    // 如果服务是通过instance()方式绑定的,就直接解析返回绑定的service
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }
    // 获取$abstract接口对应的$concrete(接口的实现)
    $concrete = $this->getConcrete($abstract);
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete, $parameters);
    } else {
        //如果时接口实现这种绑定方式,通过接口拿到实现后需要再make一次才能
        //满足isBuildable的条件 ($abstract === $concrete)
        $object = $this->make($concrete, $parameters);
    }
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }
    //如果服务是以singleton方式注册进来的则,把构建好的服务对象放到$instances里,
    //避免下次使用时重新构建
    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之前没有注册类实现到服务容器里,则服务容器会认为$abstract本身就是接口的类实现
    if (! isset($this->bindings[$abstract])) {
        return $abstract;
    }
    return $this->bindings[$abstract]['concrete'];
}
protected function isBuildable($concrete, $abstract)
{        
    return $concrete === $abstract || $concrete instanceof Closure;
}
ログイン後にコピー

makeメソッドを整理してみると、buildメソッドの役割は解析されたサービスオブジェクトを構築することであることが分かりましたので、オブジェクトを構築する具体的な処理を見てみましょう。 (PHP クラスのリフレクションは、サービスの依存関係注入を実装する構築プロセス中に使用されます)

public function build($concrete, array $parameters = [])
{
    // 如果是闭包直接执行闭包并返回(对应闭包绑定)
    if ($concrete instanceof Closure) {
        return $concrete($this, $parameters);
    }
    
    // 使用反射ReflectionClass来对实现类进行反向工程
    $reflector = new ReflectionClass($concrete);
    // 如果不能实例化,这应该是接口或抽象类,再或者就是构造函数是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;
    // 获取构造函数
    $constructor = $reflector->getConstructor();
    // 如果构造函数是空,说明没有任何依赖,直接new返回
    if (is_null($constructor)) {
        array_pop($this->buildStack);
        return new $concrete;
    }
    
    // 获取构造函数的依赖(形参),返回一组ReflectionParameter对象组成的数组表示每一个参数
    $dependencies = $constructor->getParameters();
    $parameters = $this->keyParametersByArgument(
        $dependencies, $parameters
    );
    // 构建构造函数需要的依赖
    $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参数)已提供
        // $parameter->name返回参数名
        if (array_key_exists($parameter->name, $primitives)) {
            $dependencies[] = $primitives[$parameter->name];
        } 
        elseif (is_null($dependency)) {
             // 参数的ReflectionClass为null,说明是基本类型,如'int','string'
            $dependencies[] = $this->resolveNonClass($parameter);
        } else {
             // 参数是一个类的对象, 则用resolveClass去把对象解析出来
            $dependencies[] = $this->resolveClass($parameter);
        }
    }
    return $dependencies;
}
//解析出依赖类的对象
protected function resolveClass(ReflectionParameter $parameter)
{
    try {
        // $parameter->getClass()->name返回的是类名(参数在typehint里声明的类型)
        // 然后递归继续make(在make时发现依赖类还有其他依赖,那么会继续make依赖的依赖
        // 直到所有依赖都被解决了build才结束)
        return $this->make($parameter->getClass()->name);
    } catch (BindingResolutionException $e) {
        if ($parameter->isOptional()) {
            return $parameter->getDefaultValue();
        }
        throw $e;
    }
}
ログイン後にコピー

サービス コンテナは、laravel の核心であり、依存関係注入を通じてオブジェクト間の相互依存関係を解決できます。制御反転の場合、特定の動作は外部で定義されます (Route、Eloquent、これらは外部モジュールであり、独自の動作仕様を定義します。サービス コンテナは、登録から使用のためのインスタンス化まで、これらのクラスを担当します)。

クラスをコンテナーによって抽出するには、まずクラスをコンテナーに登録する必要があります。 laravelではこのコンテナをサービスコンテナと呼んでいますので、サービスが必要な場合には、まずサービスを登録してコンテナにバインドし、サービスを提供し、コンテナにサービスをバインドするのがサービスプロバイダ(ServiceProvider)です。サービスプロバイダーは、主に register (登録) と boot (ブート、初期化) の 2 つの部分に分かれています。

[関連する推奨事項:

laravel ビデオチュートリアル]

以上がlaravelサービスコンテナとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート