用于函数驱动应用程序的PHPDI模式
我有一些类需要将依赖项注入到它们的构造函数中。这允许我为测试注入模拟(例如来自) 我对使用容器来帮助配置和访问这些对象感兴趣,我已经研究过了(我也研究过了,尽管我无法快速解决这些问题) 到目前为止一切都很好但是,我遇到的问题是,应用程序(Drupal 7)是围绕数千个函数构建的,这些函数不属于可以将依赖项注入的对象 因此,我需要这些函数才能从容器访问服务。此外,出于测试目的,我需要用mock和新mock替换服务 因此,模式如下所示:用于函数驱动应用程序的PHPDI模式,php,unit-testing,dependency-injection,containers,Php,Unit Testing,Dependency Injection,Containers,我有一些类需要将依赖项注入到它们的构造函数中。这允许我为测试注入模拟(例如来自) 我对使用容器来帮助配置和访问这些对象感兴趣,我已经研究过了(我也研究过了,尽管我无法快速解决这些问题) 到目前为止一切都很好但是,我遇到的问题是,应用程序(Drupal 7)是围绕数千个函数构建的,这些函数不属于可以将依赖项注入的对象 因此,我需要这些函数才能从容器访问服务。此外,出于测试目的,我需要用mock和新mock替换服务 因此,模式如下所示: <?php /** * Some controlle
<?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);
}
}
虽然我承认这是在各种全球功能中使用容器作为服务定位器,但鉴于平台的性质,我认为这是不可避免的。如果有更干净的方法,请告诉我
不过,我的主要问题是:
注入mock有一个问题,因为mock需要为各种测试进行更改。假设我调出了mailer服务(在Pimple中:
$container->offsetUnset('mailer');$container['mailer']=$mock_mailer;
),但是如果Pimple已经实例化了supporter
服务,那么该服务将拥有旧的、未修改的mailer对象。这是container软件或一般容器模式的局限性,还是我做得不对,还是因为旧的以功能为中心的应用程序造成了混乱?在没有任何其他建议的情况下,这就是我所追求的
容器使用Pimple\Psr11\ServiceLocator
我用的是粉刺,所以容器的工厂看起来像这样
<?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);
}
<?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;
}
}
<?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.
<?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);
}
<?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);
}
}
<?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();
// ...
}
}