简体   繁体   English

用PHP反转控制

[英]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. 如您所见,我们正在实例化具有两个依赖项的LoggerMySQLAdapter和PDO。

This process works like this: 这个过程是这样的:

  • We created Logger 我们创建了Logger
    • Logger creates MySQLAdapter Logger创建MySQLAdapter
      • MySQLAdapter creates PDO MySQLAdapter创建PDO

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 . 这是依赖注入试图解决的问题: 不要修改类,因为依赖项已更改

Dependency Injection 依赖注入

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: 这有助于我们:

  1. To easily maintain the code: As you already saw, we don't need to modify the class if one of its dependencies changed. 轻松维护代码:正如您已经看到的,如果其中一个依赖项发生更改,则无需修改该类。

  2. 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“知道”任何实现该特定接口的类都将包含完成其工作所需的方法。

Dependency Injection Container: 依赖注入容器:

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容器,他可以找到一种方法......

Inversion of Control 控制反转

As you saw until now, the initialization process of the "classic" code was: 正如您所看到的那样,“经典”代码的初始化过程是:

  • We created Logger 我们创建了Logger
    • Logger creates MySQLAdapter Logger创建MySQLAdapter
      • MySQLAdapter creates PDO MySQLAdapter创建PDO

IoC simply replicates the above process, but in reverse order: IoC只是复制上述过程,但顺序相反:

  • Create PDO 创建PDO
    • Create MySQLAdapter and give him PDO 创建MySQLAdapter并给他PDO
      • Create Logger and give him MySQLAdapter 创建Logger并给他MySQLAdapter

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: 初看起来有人可以说实例化过程是:

  • Create Logger 创建记录器
  • Create MySQLAdapter 创建MySQLAdapter
  • Create PDO` 创建PDO

The thing is that the code will be interpreted from the middle to the left. 问题是代码将从中间向左侧解释。 So the order will be: 所以订单将是:

  • Create PDO 创建PDO
    • Create MySQLAdapted and give him PDO 创建MySQLAdapted并给他PDO
      • Create Logger and give him MySQLAdapter 创建Logger并给他MySQLAdapter

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 the container can resolve the dependencies by itself, you would only need to instantiate the container during the boot process of your application. 如果容器可以自己解析依赖关系,则只需要在应用程序的引导过程中实例化容器。 ( see Inversion of Control container ) 参见控制容器的反转
  • If the container can't resolve the dependencies by itself, you would need to manually provision the container with the objects needed to run your application. 如果容器本身无法解析依赖关系,则需要手动为容器配置运行应用程序所需的对象。 This provisioning usually happens during the boot process. 此配置通常在引导过程中发生。 (see Dependency Injection Container) (参见依赖注入容器)

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.

相关问题 PHP比较反转 - PHP Comparison Inversion PHP中的依赖反转原理 - Dependency Inversion Principle in PHP PHP preg inversion - PHP preg inversion 服务定位器、依赖注入(和容器)和控制反转 - Service Locator, Dependency Injection (and Container) and Inversion of Control php 中的依赖倒置问题。 (存储库模式) - Dependency inversion issue in php. (Repository Pattern) 使用Inversion of Control设计多文件类型的配置装入程序类 - Design a multi file type config loader class with Inversion of Control 当运行时类类型未知时,将工厂方法与控制反转一起使用 - Using a factory method with Inversion of control when class type is not known at runtime Laravel控件反转,类型提示构造函数会自动创建实例 - Laravel inversion of control , Type hinting constructor causes creation of instance automatically 如果我在Laravel 5项目中使用andersao / l5-repository,我会打破控制原理的反转吗? - Will I break the inversion of control principle if I use andersao/l5-repository in my Laravel 5 project? 控制反转:您是否曾经不得不决定哪种依赖注入框架最适合项目? 您选择了哪一个?为什么? - Inversion of Control: Have you ever had to decide what Dependency Injection framework is best for a project? Which one did you pick and why?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM