php 了解 IoC 容器和依赖注入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18562752/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 17:48:34  来源:igfitidea点击:

Understanding IoC Containers and Dependency Injection

phpoopinheritancedependency-injectioninversion-of-control

提问by echochamber

My understanding:

我的理解:

  • A dependency is when an instance of ClassA requires an instance of ClassB to instantiate a new instance of ClassA.
  • A dependency injection is when ClassA is passed an instance of ClassB, either through a parameter in ClassA's constructor or through a set~DependencyNameHere~(~DependencyNameHere~ $param) function. (This is one of the areas I'm not completely certain on).
  • An IoC container is a singleton Class(can only have 1 instance instantiated at any given time) where the specific way of instantiating objects of those class for this project can be registered. Here's a link to an example of what I'm trying to describe along with the class definition for the IoC container I've been using
  • 依赖是指 ClassA 的实例需要 ClassB 的实例来实例化 ClassA 的新实例。
  • 依赖注入是当 ClassA 被传递一个 ClassB 的实例时,通过 ClassA 的构造函数中的参数或通过 set~DependencyNameHere~(~DependencyNameHere~ $param) 函数。(这是我不能完全确定的领域之一)
  • IoC 容器是一个单例类(在任何给定时间只能实例化 1 个实例),其中可以注册为该项目实例化这些类的对象的特定方式。这是我试图描述的示例的链接以及我一直在使用的 IoC 容器的类定义

So at this point is where I start trying use the IoC container for more complicated scenarios. As of now it seems in order to use the IoC container, I am limited to a has-a relationship for pretty much any class I want to create that has dependencies it wants to define in the IoC container. What if I want to create a class that inherits a class, but only if the parent class has been created in a specific way it was registered in the IoC container.

所以在这一点上,我开始尝试将 IoC 容器用于更复杂的场景。到目前为止,似乎为了使用 IoC 容器,对于我想创建的几乎所有具有要在 IoC 容器中定义的依赖项的类,我都只能使用 has-a 关系。如果我想创建一个继承类的类,但前提是父类是以特定方式创建的,它在 IoC 容器中注册了怎么办。

So for example: I want to create a child class of mysqli, but I want to register this class in the IoC container to only instantiate with the parent class constructed in a way I've previously registered in the IoC container. I cannot think of a way to do this without duplicating code (and since this is a learning project I'm trying to keep it as 'pure' as possible). Here are some more examples of what I am trying to describe.

所以举个例子:我想创建一个mysqli的子类,但是我想在IoC容器中注册这个类,只用我之前在IoC容器中注册的方式构造的父类进行实例化。我想不出不复制代码的方法(因为这是一个学习项目,我试图让它尽可能“纯”)。以下是我要描述的更多示例。

So here are some of my questions:

所以这里是我的一些问题:

  • Is what I'm trying to do above possible without breaking some principle of OOP? I know in c++ I could use dynamic memory and a copy constructor to accomplish it, but I haven't been able to find that sort of functionality in php. (I will admit that I have very little experience using any of the other magic methods besides __construct, but from reading and __clone if I understood correctly, I couldn't use in the constructor it to make the child class being instantiated a clone of an instance of the parent class).
  • Where should all my dependency class definitions go in relation to the IoC? (Should my IoC.php just have a bunch of require_once('dependencyClassDefinition.php') at the top? My gut reaction is that there is a better way, but I haven't come up with one yet)
  • What file should I be registering my objects in? Currently doing all the calls to IoC::register() in the IoC.php file after the class definition.
  • Do I need to register a dependency in the IoC before I register a class that needs that dependency? Since I'm not invoking the anonymous function until I actually instantiate an object registered in the IoC, I'm guessing not, but its still a concern.
  • Is there anything else I'm overlooking that I should be doing or using? I'm trying to take it one step at a time, but I also don't want to know that my code will be reusable and, most importantly, that somebody who knows nothing about my project can read it and understand it.
  • 在不违反 OOP 的某些原则的情况下,我上面尝试做的事情是否可行?我知道在 C++ 中我可以使用动态内存和复制构造函数来完成它,但我一直无法在 php 中找到那种功能。(我承认,除了 __construct 之外,我几乎没有使用任何其他魔法方法的经验,但是如果我理解正确的话,从阅读和 __clone 来看,我无法在构造函数中使用它来使子类被实例化为父类的实例)。
  • 与 IoC 相关的所有依赖类定义应该放在哪里?(我的 IoC.php 应该在顶部有一堆 require_once('dependencyClassDefinition.php') 吗?我的直觉反应是有更好的方法,但我还没有想出一个)
  • 我应该在哪个文件中注册我的对象?当前在类定义之后对 IoC.php 文件中的 IoC::register() 进行所有调用。
  • 在注册需要依赖项的类之前,是否需要在 IoC 中注册依赖项?因为在我实际实例化在 IoC 中注册的对象之前我不会调用匿名函数,所以我猜不是,但它仍然是一个问题。
  • 还有什么我应该做或使用的东西吗?我试图一步一步来,但我也不想知道我的代码是可重用的,最重要的是,对我的项目一无所知的人可以阅读并理解它。

回答by lafor

Put simply (because it's not a problem limited to OOP world only), a dependencyis a situation where component A needs (depends on) component B to do the stuff it's supposed to do. The word is also used to describe the depended-on component in this scenario. To put this in OOP/PHP terms, consider the following example with the obligatory car analogy:

简单地说(因为它不是仅限于 OOP 世界的问题),依赖是一种情况,其中组件 A 需要(依赖于)组件 B 来做它应该做的事情。该词还用于描述此场景中的依赖组件。用 OOP/PHP 术语来说,请考虑以下带有强制性汽车类比的示例:

class Car {

    public function start() {
        $engine = new Engine();
        $engine->vroom();
    }

}

Cardependson Engine. Engineis Car's dependency. This piece of code is pretty bad though, because:

Car依赖EngineEngineisCar依赖。不过这段代码很糟糕,因为:

  • the dependency is implicit; you don't know it's there until you inspect the Car's code
  • the classes are tightly coupled; you can't substitute the Enginewith MockEnginefor testing purposes or TurboEnginethat extends the original one without modifying the Car.
  • It looks kind of silly for a car to be able to build an engine for itself, doesn't it?
  • 依赖是隐含的;你不知道它在那里,直到你检查了Car代码
  • 类是紧密耦合的;您不能出于测试目的替换EnginewithMockEngineTurboEngine在不修改Car.
  • 汽车能够为自己制造发动机看起来有点傻,不是吗?

Dependency injectionis a way of solving all these problems by making the fact that Carneeds Engineexplicit and explicitly providing it with one:

依赖注入是一种解决所有这些问题的方法,它使Car需要Engine明确并明确地提供一个事实:

class Car {

    protected $engine;

    public function __construct(Engine $engine) {
        $this->engine = $engine;
    }

    public function start() {
        $this->engine->vroom();
    }

}

$engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
$car = new Car($engine);

The above is an example of constructor injection, in which the dependency (the depended-on object) is provided to the dependent (consumer) through the class constructor. Another way would be exposing a setEnginemethod in the Carclass and using it to inject an instance of Engine. This is known as setter injectionand is useful mostly for dependencies that are supposed to be swapped at run-time.

上面是构造函数注入的一个例子,其中依赖(被依赖的对象)通过类构造函数提供给被依赖者(消费者)。另一种setEngine方法是在Car类中公开一个方法并使用它来注入Engine. 这被称为setter 注入,主要用于应该在运行时交换的依赖项。

Any non-trivial project consists of a bunch of interdependent components and it gets easy to lose track on what gets injected where pretty quickly. A dependency injection containeris an object that knows how to instantiate and configure other objects, knows what their relationship with other objects in the project are and does the dependency injection for you. This lets you centralize the management of all your project's (inter)dependencies and, more importantly, makes it possible to change/mock one or more of them without having to edit a bunch of places in your code.

任何重要的项目都由一堆相互依赖的组件组成,并且很容易很快就会忘记什么被注入到哪里。一个依赖注入容器是一个知道如何实例化和配置其他对象的对象,知道他们与项目的其他对象的关系是并执行依赖注入你。这使您可以集中管理所有项目的(相互)依赖项,更重要的是,可以更改/模拟其中一个或多个,而无需编辑代码中的一堆位置。

Let's ditch the car analogy and look at what OP's trying to achieve as an example. Let's say we have a Databaseobject depending on mysqliobject. Let's say we want to use a really primitive dependency indection container class DICthat exposes two methods: register($name, $callback)to register a way of creating an object under the given name and resolve($name)to get the object from that name. Our container setup would look something like this:

让我们抛开汽车类比,以 OP 试图实现的目标为例。假设我们有一个Database取决于mysqli对象的对象。假设我们想要使用一个真正原始的依赖检测容器类DIC,它公开了两个方法:register($name, $callback)注册一种以给定名称创建对象的方法,以及resolve($name)从该名称获取对象。我们的容器设置看起来像这样:

$dic = new DIC();
$dic->register('mysqli', function() {
    return new mysqli('somehost','username','password');
});
$dic->register('database', function() use($dic) {
    return new Database($dic->resolve('mysqli'));
});

Notice we're telling our container to grab an instance of mysqlifrom itselfto assemble an instance of Database. Then to get a Databaseinstance with its dependency automatically injected, we would simply:

请注意,我们告诉容器mysqli从自身获取 的实例以组装 的实例Database。然后要获得一个Database自动注入其依赖项的实例,我们只需:

$database = $dic->resolve('database');

That's the gist of it. A somewhat more sophisticated but still relatively simple and easy to grasp PHP DI/IoC container is Pimple. Check its documentation for more examples.

这就是它的要点。一个更复杂但仍然相对简单且易于掌握的 PHP DI/IoC 容器是Pimple。查看其文档以获取更多示例。



Regarding OP's code and questions:

关于 OP 的代码和问题:

  • Don't use static class or a singleton for your container (or for anything else for that matter); they're both evil. Check out Pimple instead.
  • Decide whether you want your mysqliWrapperclass extendmysqlor dependon it.
  • By calling IoCfrom within mysqliWrapperyou're swapping one dependency for another. Your objects shouldn't be aware of or use the container; otherwise it's not DIC anymore it's Service Locator (anti)pattern.
  • You don't need to requirea class file before registering it in the container since you don't know if you're going to use an object of that class at all. Do all your container setup in one place. If you don't use an autoloader, you can requireinside the anonymous function you register with the container.
  • 不要为你的容器(或其他任何事情)使用静态类或单例;他们都是邪恶的。改为查看 Pimple。
  • 决定你是希望你的mysqliWrapper扩展mysql还是依赖它。
  • 通过IoC从内部调用,mysqliWrapper您将一种依赖项交换为另一种依赖项。你的对象不应该知道或使用容器;否则它不再是 DIC,而是服务定位器(反)模式。
  • require将类文件注册到容器中之前,您不需要它,因为您根本不知道是否要使用该类的对象。在一处完成所有容器设置。如果您不使用自动加载器,则可以require在向容器注册的匿名函数内。


Additional resources:

其他资源: