我有一些类需要将依赖项注入到它们的构造函数中。这允许我注入模拟(例如来自prophecy)进行测试。
我有兴趣使用容器来帮助配置和访问这些对象,我已经查看了Pimple(我也看了PHP-DI虽然我无法理解快速尝试解决问题。)
到目前为止一切顺利。 但是,我遇到的问题是应用程序(Drupal 7)是围绕数千个函数构建的,这些函数不属于可以注入依赖项的对象。
所以我需要这些函数才能从容器访问服务。此外,出于测试目的,我需要用模拟和新模拟替换服务。
所以模式就像:
<?php
/**
* Some controller class that uses an injected mailing service.
*/
class Supporter
{
protected $mailer;
public function __construct(MailingServiceInterface $mailer) {
$this->mailer = $mailer;
}
public function signUpForMalings($supporter_id) {
$email = $this->getSupporterEmail($supporter_id);
$this->mailer->signup($email);
}
}
然后在我使用的各种功能中添加:
<?php
/**
* A form submit handler called by the platform app,
* with a signature I can't touch.
*/
function my_form_submit($values) {
global $container;
if ($values['subscribe']) {
$supporter = $container->get('supporter');
$supporter->signUpForMailings($values['supporter_id']);
}
}
在其他地方我可能需要直接访问邮件...
<?php
/**
* example function requires mailer service.
*/
function is_signed_up($email) {
global $container;
return $container->get('mailer')->isSignedUp($email);
}
在其他地方调用这些函数的函数......
<?php
/**
* example function that uses both the above functions
*/
function sign_em_up($email, $supporter_id) {
if (!is_signed_up($email)) {
my_form_submit(['supporter_id'=>$supporter_id);
return TRUE;
}
}
让我们承认这些功能是一团糟 - 这是故意代表的问题。但是,我想说我想测试sign_em_up
函数:
<?php
public testSignUpNewPerson() {
$mock_mailer = createAMockMailer()
->thatWill()
->return(FALSE)
->whenFunctionCalled('isSignedUp', 'wilma@example.com');
// Somehow install the mock malier in the container.
$result = sign_em_up('wilma@example.com', 123);
$this->assertTrue($result);
}
// ... imagine other tests which also need to inject mocks.
虽然我认识到这是在各种全局函数中使用容器作为服务定位器,但我认为鉴于平台的性质,这是不可避免的。如果有更清洁的方式,请告诉我。
但我的主要问题是:
注入模拟存在问题,因为模拟需要针对各种测试进行更改。假设我换掉了邮件服务(在Pimple:$container->offsetUnset('mailer'); $container['mailer'] = $mock_mailer;
中),但是如果Pimple已经实例化了supporter
服务,那么该服务将拥有旧的,未模仿的邮件程序对象。这是包含软件或一般容器模式的限制,还是我做错了,还是因为老派以功能为中心的应用程序而变得一团糟?
答案 0 :(得分:0)
在没有任何其他建议的情况下,这就是我所追求的目标!
Pimple\Psr11\ServiceLocator
我使用的是Pimple,因此容器的工厂可能看起来像这样
<?php
use Pimple\Container;
use Pimple\Psr11\ServiceLocator;
$container = new Container();
$container['mailer'] = function ($c) { return new SomeMailer(); }
$container['supporters'] = function ($c) {
// Create a service locator for the 'Supporters' class.
$services = new ServiceLocator($c, ['mailer']);
return new Supporter($services);
}
然后,Supporter类现在不是存储对创建容器时从容器中提取的对象的引用,而是从ServiceLocator中获取它们:
<?php
use \Pimple\Psr11\ServiceLocator;
/**
* Some controller class that uses an injected mailing service.
*/
class Supporter
{
protected $services;
public function __construct(ServiceLocator $services) {
$this->services = $services;
}
// This is a convenience function.
public function __get($prop) {
if ($prop == 'mailer') {
return $this->services->get('mailer');
}
throw new \InvalidArgumentException("Unknown property '$prop'");
}
public function signUpForMalings($supporter_id) {
$email = $this->getSupporterEmail($supporter_id);
$this->mailer->signup($email);
}
}
在各种CMS功能中,我只使用global $container; $mailer = $container['mailer'];
,但这意味着在测试中我现在可以模拟任何服务,并且知道所有需要该服务的代码现在都将拥有我的模拟服务。 e.g。
<?php
class SomeTest extends \PHPUnit\Framework\TestCase
{
function testSupporterGetsMailed() {
global $container;
$supporter = $container['supporter'];
// e.g. mock the mailer component
$container->offsetUnset('mailer');
$container['mailer'] = $this->getMockedMailer();
// Do something with supporter.
$supporter->doSomething();
// ...
}
}