ホームページ  >  記事  >  PHPフレームワーク  >  Laravel: サービスコンテナのバインドと解析

Laravel: サービスコンテナのバインドと解析

藏色散人
藏色散人転載
2021-04-22 11:33:072464ブラウズ

次は、#laravel のチュートリアル コラムで、Laravel: サービス コンテナのバインドと解析について紹介します。

Laravel の育成: サービスコンテナのバインディングと分析

まえがき

正直に言うと、最初の朝上司に laravel フレームワークのマニュアルを読むように言われたとき、私はこう思いました。実際に出会ったことがないのでとても残念です 私のようなクズ人間にとって、Laravel への参入障壁は確かに少し高いですが、それでも頑張って読み進めなければなりません (まだ読み進めていませんが)かなり理解できましたが、まだ理解できません)。使用されていません)。

私は会社のプロジェクトコードに基づいたlaravelに徐々に慣れてきましたが、まだ依存関係の注入、ORM操作、ユーザー認証、プロジェクトのビジネスロジックに関連するその他の操作など、いくつかの表面的な機能にとどまっていました。サービスプロバイダー、サービスコンテナ、ミドルウェア、Redisなど、最初からセットアップが必要な基本的なアーキテクチャは、私が実際に運用したことがないので(上司が最初からやってくれているので)、マニュアルを読むのはまだ少しわかりにくいです。
そこで、暇なときはフォーラムを閲覧したり、Googleで検索したりして、laravelのコアアーキテクチャや使い方の紹介をたくさん見つけます(マニュアルを読んだ後に読むとより理解しやすいです)。これがガイドです このウェブサイトでの教えは、laravel のコアアーキテクチャの学習を記録するのに適していると思います
ウェブサイトのアドレス: https://laraweb.net/ これは日本語のウェブサイトです。初心者には非常に適していると思います。内容はブラウザを使って翻訳していますが、やっぱり日本語から翻訳するとわかりやすいので大丈夫です

サービスコンテナについて

マニュアルではこのように紹介されています: Laravelサービスコンテナを使用しますクラスの依存関係を管理し、依存関係の注入を実行するツール。依存関係の注入という派手な用語は、本質的に、クラスの依存関係がコンストラクター、または場合によっては「セッター」メソッドを通じてクラスに「注入」されることを意味します。 。 。 。 。 。 (意味がよくわかりません)

サービスコンテナとは、クラス(サービス)のインスタンス化を管理するための仕組みです。サービスコンテナの使い方を直接見る

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/...)

Basic laravelコンテナの理解

最初に、index.phpファイルはComposerビルドによって定義されたオートローダーをロードし、次にbootstrap/app.phpスクリプトからLaravelアプリケーションのインスタンスを取得します。 Laravel 自体によって実行される最初のアクションは、アプリケーション/サービス コンテナーのインスタンスを作成することです。

$app = new Illuminate\Foundation\Application(
    dirname(__DIR__)
);
このファイルは、リクエストが laravel フレームワークに到達するたびに実行されます. 作成される $app は、リクエストのライフサイクル全体で一意の、laravel フレームワークのアプリケーション インスタンスです。 Laravel は、認証、データベース、キャッシュ、メッセージ キューなどを含む多くのサービスを提供します。 $app はコンテナ管理ツールとして、ほぼすべてのサービス コンポーネントのインスタンス化とインスタンスのライフサイクル管理を担当します。特定の機能を完了するためにサービス クラスが必要な場合、コンテナを通じてこのタイプのインスタンスを解決するだけで済みます。最終的な使用の観点から見ると、laravel コンテナによるサービス インスタンスの管理には主に次の側面が含まれます:

    #サービスのバインドと解析
  • #サービスプロバイダーの管理
  • エイリアスの役割
  • 依存性注入
  • まず理解する 取得方法コード内でコンテナ インスタンスを指定し、上記の 4 つのキーを学習します。
コード内でコンテナ インスタンスを取得する方法

1 つ目は

$app = app();
//app这个辅助函数定义在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php
里面,,这个文件定义了很多help函数,并且会通过composer自动加载到项目中。
所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()。

2 つ目は

Route::get('/', function () {
    dd(App::basePath());
    return '';
});
//这个其实是用到Facade,中文直译貌似叫门面,在config/app.php中,
有一节数组aliases专门用来配置一些类型的别名,第一个就是'App' => Illuminate\Support\Facades\App::class,
具体的Google一下laravel有关门面的具体实现方式
3 番目の方法は

# $this->app をサービス プロバイダーで直接使用します。サービスプロバイダーについては後ほど紹介しますが、今は紹介したばかりです。サービスプロバイダクラスはlaravelコンテナによってインスタンス化されるため、これらのクラスはインスタンス属性を定義するIlluminate\Support\ServiceProviderを継承します $app:

abstract class ServiceProvider
{
    protected $app;
laravel サービスプロバイダをインスタンス化するときに、laravelコンテナインスタンスを注入しますこの$app。したがって、サービスプロバイダーでは、app() 関数や App Facade を使用せずに、$this->$app を通じて常に laravel コンテナインスタンスにアクセスできます。

サービスのバインディングと解析を理解する方法

単純に言えば、コンテナはオブジェクトを保存するために使用されるため、オブジェクトの保存とオブジェクトの削除のプロセスが必要になります。このオブジェクトを保存および取得するプロセスは、Laravel ではサービスのバインドと解析と呼ばれます。

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

app()->bind('service2', [
    'hi' => function(){
        //say hi
    }
]);

class Service {

}

app()->bind('service3', function(){
    return new Service();
});

また、バインドの特殊なケースであるシングルトン バインディング シングルトンもあります (3 番目のパラメーターは true)。コンテナーにバインドされたオブジェクトは 1 回だけ解析され、後続の呼び出しでは同じものが返されます。例###
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/。
  以上相关知识的引用已经注明出处,若有侵权,请联系我,感谢这些优秀文章的作者

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

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。