Php 包含已注入依赖项的对象的对象的DI容器

Php 包含已注入依赖项的对象的对象的DI容器,php,unit-testing,phpunit,php-5.3,Php,Unit Testing,Phpunit,Php 5.3,作为我的DI容器,我一直在勇敢地重构小型类,以依赖DI注入,消除我可以看到的硬编码依赖项,这些依赖项很容易被删除 我的这项任务的方法非常简单,但我不知道它是否合适,因为除了上个月在这里学到的知识外,我在DI和单元测试方面的经验很少 我创建了一个类,ContainerFactory,它是pimple的子类,并且在该子类中创建了一些方法,这些方法只返回特定对象的容器 构造函数根据类型调用适当的创建者方法: function __construct($type=null, $mode = null){

作为我的DI容器,我一直在勇敢地重构小型类,以依赖DI注入,消除我可以看到的硬编码依赖项,这些依赖项很容易被删除

我的这项任务的方法非常简单,但我不知道它是否合适,因为除了上个月在这里学到的知识外,我在DI和单元测试方面的经验很少

我创建了一个类,ContainerFactory,它是pimple的子类,并且在该子类中创建了一些方法,这些方法只返回特定对象的容器

构造函数根据类型调用适当的创建者方法:

function __construct($type=null, $mode = null){

 if(isset($type)){  
    switch ($type) {
      case 'DataFactory':
         $this->buildDataFactoryContainer($mode);     
        break;
      case 'DbConnect':
         $this->buildDbConnectContainer($mode);  
        break;
     default:
        return false;
    }
  }
}
容器对象创建的方法签名如下所示:

public function buildDataFactoryContainer($mode=null)
我可以在调用这个容器时将$mode设置为test,并让它加载测试值,而不是实际的运行时设置。我希望避免为测试编写单独的容器类,我认为这是一种简单的方法,不必到处都有与测试相关的代码

相反,我可以将ContainerFactory子类化,即:
ContainerFactoryTesting扩展了ContainerFactory
并在其中进行重写,而不是将测试代码与应用程序代码混合,并将方法签名与$mode=null混杂在一起,但这不是本文的重点。接下来,要为特定对象创建容器,我只需执行以下操作:

 // returns container with DataFactory dependencies, holds $db and $logger objects.
 $dataFactoryContainer = new ContainerFactory('DataFactory');

// returns container with test settings.
$dataFactoryTestContainer = new ContainerFactory('DataFactory','test');

// returns container with DbConnect dependencies, holds dbconfig and $logger objects.
$dbConnectContainer = new ContainerFactory('DbConnect');
我刚刚遇到一个问题,让我怀疑我所建立的战略是有缺陷的

综上所述,DataFactory包含保存数据库连接的$db对象。 我现在正在重构这个dbclass,以删除它对$registry对象的依赖关系, 但当我添加需要$dbConnectContainer的$db对象时,我将如何创建$dataFactoryContainer

例如,在datafactory容器中,我添加了一个dbconnect实例,但它现在需要一个传递给它的容器

我意识到我的英语不是很好,希望我解释得足够好,让一个普通人能理解

我的问题有两个,你们如何以一种简单的方式为依赖项创建对象,这些依赖项本身包含依赖项

和。。如何分离容器配置以创建用于测试目的的对象


与往常一样,任何评论或相关帖子的链接都会受到欢迎。

您不应该为所有内容创建单独的容器,而应该使用单个容器。您可以创建一个容器“扩展”,它基本上只是在您的容器上设置服务

下面是我建议设置的一个广泛示例。当您只有一个容器时,递归地解析依赖项是很简单的。如您所见,
security.authentication\u提供程序取决于
db
,后者取决于
db.config

由于闭包的惰性,很容易定义服务,然后在以后定义它们的配置。此外,还可以很容易地覆盖它们,正如您在TestExtension中看到的那样

可以将您的服务定义拆分为多个单独的扩展,以使它们更具可重用性。这就是它所做的(它使用粉刺)

我希望这能回答你的问题

扩展接口

class ExtensionInterface
{
    function register(Pimple $container);
}
AppExtension

class AppExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = $container->share(function ($container) {
            return new Mailer($container['mailer.config']);
        });

        $container['db'] = $container->share(function ($container) {
            return new DatabaseConnection($container['db.config']);
        });

        $container['security.authentication_provider'] = $container->share(function ($container) {
            return new DatabaseAuthenticationProvider($container['db']);
        });
    }
}
class ConfigExtension
{
    private $config;

    public function __construct($configFile)
    {
        $this->config = json_decode(file_get_contents($configFile), true);
    }

    public function register(Pimple $container)
    {
        foreach ($this->config as $name => $value) {
            $container[$name] = $container->protect($value);
        }
    }
}
{
    "mailer.config": {
        "username": "something",
        "password": "secret",
        "method":   "smtp"
    },
    "db.config": {
        "username": "root",
        "password": "secret"
    }
}
class TestExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = function () {
            return new MockMailer();
        };
    }
}
$factory = new ContainerFactory();
$container = $factory->create();
ConfigExtension

class AppExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = $container->share(function ($container) {
            return new Mailer($container['mailer.config']);
        });

        $container['db'] = $container->share(function ($container) {
            return new DatabaseConnection($container['db.config']);
        });

        $container['security.authentication_provider'] = $container->share(function ($container) {
            return new DatabaseAuthenticationProvider($container['db']);
        });
    }
}
class ConfigExtension
{
    private $config;

    public function __construct($configFile)
    {
        $this->config = json_decode(file_get_contents($configFile), true);
    }

    public function register(Pimple $container)
    {
        foreach ($this->config as $name => $value) {
            $container[$name] = $container->protect($value);
        }
    }
}
{
    "mailer.config": {
        "username": "something",
        "password": "secret",
        "method":   "smtp"
    },
    "db.config": {
        "username": "root",
        "password": "secret"
    }
}
class TestExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = function () {
            return new MockMailer();
        };
    }
}
$factory = new ContainerFactory();
$container = $factory->create();
config.json

class AppExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = $container->share(function ($container) {
            return new Mailer($container['mailer.config']);
        });

        $container['db'] = $container->share(function ($container) {
            return new DatabaseConnection($container['db.config']);
        });

        $container['security.authentication_provider'] = $container->share(function ($container) {
            return new DatabaseAuthenticationProvider($container['db']);
        });
    }
}
class ConfigExtension
{
    private $config;

    public function __construct($configFile)
    {
        $this->config = json_decode(file_get_contents($configFile), true);
    }

    public function register(Pimple $container)
    {
        foreach ($this->config as $name => $value) {
            $container[$name] = $container->protect($value);
        }
    }
}
{
    "mailer.config": {
        "username": "something",
        "password": "secret",
        "method":   "smtp"
    },
    "db.config": {
        "username": "root",
        "password": "secret"
    }
}
class TestExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = function () {
            return new MockMailer();
        };
    }
}
$factory = new ContainerFactory();
$container = $factory->create();
TestExtension

class AppExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = $container->share(function ($container) {
            return new Mailer($container['mailer.config']);
        });

        $container['db'] = $container->share(function ($container) {
            return new DatabaseConnection($container['db.config']);
        });

        $container['security.authentication_provider'] = $container->share(function ($container) {
            return new DatabaseAuthenticationProvider($container['db']);
        });
    }
}
class ConfigExtension
{
    private $config;

    public function __construct($configFile)
    {
        $this->config = json_decode(file_get_contents($configFile), true);
    }

    public function register(Pimple $container)
    {
        foreach ($this->config as $name => $value) {
            $container[$name] = $container->protect($value);
        }
    }
}
{
    "mailer.config": {
        "username": "something",
        "password": "secret",
        "method":   "smtp"
    },
    "db.config": {
        "username": "root",
        "password": "secret"
    }
}
class TestExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = function () {
            return new MockMailer();
        };
    }
}
$factory = new ContainerFactory();
$container = $factory->create();
集装箱工厂

class ContainerFactory
{
    public function create($configDir)
    {
        $container = new Pimple();

        $extension = new AppExtension();
        $extension->register($container);

        $extension = new ConfigExtension($configDir.'/config.json');
        $extension->register($container);

        return $container;
    }
}
应用程序

class AppExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = $container->share(function ($container) {
            return new Mailer($container['mailer.config']);
        });

        $container['db'] = $container->share(function ($container) {
            return new DatabaseConnection($container['db.config']);
        });

        $container['security.authentication_provider'] = $container->share(function ($container) {
            return new DatabaseAuthenticationProvider($container['db']);
        });
    }
}
class ConfigExtension
{
    private $config;

    public function __construct($configFile)
    {
        $this->config = json_decode(file_get_contents($configFile), true);
    }

    public function register(Pimple $container)
    {
        foreach ($this->config as $name => $value) {
            $container[$name] = $container->protect($value);
        }
    }
}
{
    "mailer.config": {
        "username": "something",
        "password": "secret",
        "method":   "smtp"
    },
    "db.config": {
        "username": "root",
        "password": "secret"
    }
}
class TestExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = function () {
            return new MockMailer();
        };
    }
}
$factory = new ContainerFactory();
$container = $factory->create();
测试引导

$factory = new ContainerFactory();
$container = $factory->create();

$extension = new TestExtension();
$extension->register($container);

通过在datafactoryContainer创建方法中创建db对象,我多少解决了这个问题:$dbConnect=isset($dbConnect)$dbConnect:dbConnect::getInstance(新的ContainerFactory('dbConnect');但我很确定这并没有正确使用DI容器。您应该将DataFactory注入到您展示的构造函数中。带开关块的代码不太像DI。这是我的名字:)我建议你阅读Misko Heverys的文章,这是如何编写干净的可测试代码的极好指南@Ondřej Mirtes-仅供参考,您的用户名在世界大部分地区都无法使用!(咧嘴笑)。至于上面的开关,当我把它放在一起的时候,感觉真的不对。我在下面给出了答案,并且再次阅读了丘疹文档页面,这让我完全改变了我的方法。现在,对象都在一个容器中通过闭包初始化,而不是在多个容器中进行“硬编码”。由于pimple中的一个小错误,仅在windows上我无法理解正确的使用场景。在展示了Igor之后,丘疹在几分钟内就被修补好了,我现在按照它应该被使用的方式使用它。Igor!这是一个多么小的世界啊。我一直在寻找通过电子邮件与你联系的方式,而现在你来回答我的问题!奇怪的谦虚!我一直在你的github帐户上,在那里登陆,同时寻找关于丘疹的信息。我做了一次心理检查,再次访问silex,因为它看起来非常有趣。文档中给出了一个示例,当我试图用伪类重新创建它时,该示例生成了一个致命错误。我确实找到了用protected()包装的解决方案,但我仍然不清楚关于粉刺的一个或两个概念,我真的希望我能收到你的电子邮件,但这总比什么都没有好。谢谢你花时间回复我。非常感谢Wiedler先生,您的这篇文章将对现有的丘疹文档做一个很好的补充。可用的文档一点也不差,但测试的覆盖并不清楚,在另一个对象中使用具有依赖关系的对象也不清楚。把所有东西联系在一起的集装箱工厂真是锦上添花。它几乎回答了我所有的“如何让丘疹与这个应用程序相处”的问题。我的2美分和荣誉给你。附言:你今天提出的承诺对我来说非常有效。谢谢。是的,再来一些关于粉刺的文档就好了。Silex docs涵盖了很多内容:几乎所有内容都来自于那个家伙