[英]Inversion of Control with PHP
I just started using Dependency Injection for obvious reasons and without reading about Inversion of Control (IoC) quickly stumble with the issue of being verbose when instantiate some of my classes. 我刚开始使用依赖注入时出于显而易见的原因,并且在没有阅读控制反转(IoC)的情况下很快发现了在实例化我的一些类时出现冗长的问题。 So, reading about IoC I have a question that have not found an concrete answer.
所以,阅读IoC我有一个问题没有找到具体的答案。 When should class registration happen?
课程注册何时发生? in a bootstrap?
在引导程序? before execution?
执行前? How can I enforce the type of the dependencies?
如何强制执行依赖项的类型?
I am not using any frameworks. 我没有使用任何框架。 For the sake of learning I wrote my own container.
为了学习,我写了自己的容器。
This is a very lowbrow example of my container and some sample classes. 这是我的容器和一些示例类的一个非常低的例子。
class DepContainer
{
private static $registry = array();
public static function register($name, Closure $resolve)
{
self::$registry[$name] = $resolve;
}
public static function resolve($name)
{
if (self::registered($name)) {
$name = static::$registry[$name];
return $name();
}
throw new Exception('Nothing bro.');
}
public static function registered($name)
{
return array_key_exists($name, self::$registry);
}
}
class Bar
{
private $hello = 'hello world';
public function __construct()
{
# code...
}
public function out()
{
echo $this->hello . "\n";
}
}
class Foo
{
private $bar;
public function __construct()
{
$this->bar = DepContainer::resolve('Bar');
}
public function say()
{
$this->bar->out();
}
}
With these already in the app structure. 这些已经在app结构中。 The Dependecy Injection way I would do type hint the incoming parameters, but without it I can do:
我会做的Dependecy Injection方式提示输入参数,但没有它我可以做:
DepContainer::register('Bar', function(){
return new Bar();
});
$f = new Foo();
$f->say();
To me, makes sense in a bootsrap register all dependencies it would be the more clean way IMO. 对我来说,在一个bootsrap寄存器中有意义的所有依赖关系,它将是IMO更干净的方式。 At run time like a showed you I think is just as ugly as doing
new Foo(new Bar(...)...)
. 在运行时,像一个显示你,我认为就像做
new Foo(new Bar(...)...)
一样丑陋。
I will try to summarize a few things that you should know and ( hopefully ) will clarify some of your dilemmas . 我将尝试总结一些你应该知道的事情,并且( 希望 )会澄清你的一些困境。
Let's start from a basic example: 让我们从一个基本的例子开始:
class MySQLAdapter
{
public function __construct()
{
$this->pdo = new PDO();
}
}
class Logger
{
public function __construct()
{
$this->adapter = new MySqlAdapter();
}
}
$log = new Logger();
As you can see, we are instantiating Logger
which has two dependencies: MySQLAdapter
and PDO. 如您所见,我们正在实例化具有两个依赖项的
Logger
: MySQLAdapter
和PDO。
This process works like this: 这个过程是这样的:
The above code works, but if tomorrow we decided that we need to log our data in a file instead of a database, we will need to change the Logger
class and replace MySQLAdapter
with a brand new FileAdapter
. 上面的代码工作,但如果明天我们决定,我们需要登录一个文件,而不是一个数据库我们的数据,我们将需要更改
Logger
类和替换MySQLAdapter
了一个全新的FileAdapter
。
// not good
class Logger
{
public function __construct()
{
$this->adapter = new FileAdapter();
}
}
This is the problem that Dependency Injection tries to solve: do not modify a class because a dependency has changed . 这是依赖注入试图解决的问题: 不要修改类,因为依赖项已更改 。
Di reefers to the process of instantiating a class by giving it's constructor all the dependencies it needs to function properly. Di通过为它的构造函数提供正确运行所需的所有依赖项来实现一个类的过程。 If we apply Dependency Injection to our previous example, it will look like this:
如果我们将依赖注入应用于前面的示例,它将如下所示:
interface AdapterInterface
{
}
class FileAdapter implements AdapterInterface
{
public function __construct()
{
}
}
class MySQLAdapter implements AdapterInterface
{
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
}
class Logger
{
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
}
// log to mysql
$log = new Logger(
new MySQLAdapter(
new PDO()
)
);
As you can see, we don't instantiate anything in constructor, but we pass the instantiated class to constructor. 如您所见,我们不在构造函数中实例化任何内容,但是我们将实例化的类传递给构造函数。 This allows us to replace any dependency without modifying the class:
这允许我们在不修改类的情况下替换任何依赖:
// log to file
$log = new Logger(
new FileAdapter()
);
This helps us: 这有助于我们:
To easily maintain the code: As you already saw, we don't need to modify the class if one of its dependencies changed. 轻松维护代码:正如您已经看到的,如果其中一个依赖项发生更改,则无需修改该类。
Makes the code more testable: When you run your test suite against MySQLAdapter
you don't want to hit the database on each test, so the PDO object will be mocked in tests: 使代码更易测试:当您针对
MySQLAdapter
运行测试套件时,您不希望在每次测试时都访问数据库,因此PDO对象将在测试中被模拟 :
// test snippet $log = new Logger( new MySQLAdapter( $this->getMockClass('PDO', [...]) ) );
Q: How does Logger
knows that you give him a class that it needs and not some garbage ? 问:
Logger
如何知道你给他一个它需要的课而不是一些垃圾?
A: This is the interface ( AdapterInterface ) job, which is a contract between Logger
and other classes. 答:这是接口( AdapterInterface )作业,它是
Logger
和其他类之间的契约 。 Logger "knows" that any class that implements that particular interface will contain the methods it needs to do his job. Logger“知道”任何实现该特定接口的类都将包含完成其工作所需的方法。
You can look at this class ( ie: container ) as a central place where you store all your objects needed to run your application. 您可以将此类( 即:容器 )视为存储运行应用程序所需的所有对象的中心位置。 When you need one of them, you request the object from the container instead of instantiating yourself.
当您需要其中一个时,您从容器中请求对象而不是实例化自己。
You can look at DiC as a dog who was trained to get out, get the newspaper and bring it back to you. 你可以把DiC视为一只经过训练的狗出去,拿到报纸并把它带回给你。 The catch is that the dog was trained only with the front door opened.
问题是,只有在前门打开的情况下训练了狗。 Everything would be fine as long as the dog's dependencies will not change ( ie door opened ).
只要狗的依赖性不会改变( 即门打开 ),一切都会好的。 If one day the front door will be closed, the dog will not know how to get the newspaper.
如果有一天前门将关闭,狗将不知道如何获得报纸。
But if the dog would have an IoC container, he could find a way ... 但如果狗有一个IoC容器,他可以找到一种方法......
As you saw until now, the initialization process of the "classic" code was: 正如您所看到的那样,“经典”代码的初始化过程是:
IoC simply replicates the above process, but in reverse order: IoC只是复制上述过程,但顺序相反:
If you though that Dependency Injection is some kind of IoC, you are right. 如果您认为依赖注入是某种IoC,那么您是对的。 When we talked about Dependency Injection, we had this example:
当我们谈到依赖注入时,我们有了这个例子:
// log to mysql
$log = new Logger(
new MySQLAdapter(
new PDO()
)
);
At a first look someone could say that the instantiation process is: 初看起来有人可以说实例化过程是:
The thing is that the code will be interpreted from the middle to the left. 问题是代码将从中间向左侧解释。 So the order will be:
所以订单将是:
The IoC container simply automates this process. IoC容器只是自动执行此过程。 When you request
Logger
from the container, it uses PHP Reflection and type hinting to analyze its dependencies ( from constructor ), instantiate all of them, sends them to the requested class and gives you back a Logger
instance. 当您从容器中请求
Logger
,它使用PHP Reflection并键入提示来分析其依赖项( 来自构造函数 ),实例化所有这些,将它们发送到请求的类并返回Logger
实例。
NOTE: To find out what dependencies a class has, some IoC containers are using annotations instead of type hinting or a combination of both. 注意:要找出类具有哪些依赖项,一些IoC容器使用注释而不是类型提示或两者的组合。
So to answer your question: 所以回答你的问题:
If your container can resolve the dependencies by itself, but for various reasons you also need to manually add more dependencies, you would do that in the boot process, after you initialize the container. 如果您的容器可以自行解决依赖关系,但由于各种原因您还需要手动添加更多依赖关系,您可以在初始化容器后在引导过程中执行此操作。
NOTE: In the wild there are all kind of mixes between these two principles, but I tried to explain you what is the main idea behind each of them. 注意: 在野外 ,这两个原则之间有各种各样的混合,但我试着解释一下每个原则背后的主要思想是什么。 How your container will look depends only by you and don't be afraid to reinvent the wheel as long as you do it for educational purposes.
容器的外观如何仅取决于您,并且不要害怕重新发明轮子 ,只要您将其用于教育目的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.