用于函数驱动应用程序的PHPDI模式

用于函数驱动应用程序的PHPDI模式,php,unit-testing,dependency-injection,containers,Php,Unit Testing,Dependency Injection,Containers,我有一些类需要将依赖项注入到它们的构造函数中。这允许我为测试注入模拟(例如来自) 我对使用容器来帮助配置和访问这些对象感兴趣,我已经研究过了(我也研究过了,尽管我无法快速解决这些问题) 到目前为止一切都很好但是,我遇到的问题是,应用程序(Drupal 7)是围绕数千个函数构建的,这些函数不属于可以将依赖项注入的对象 因此,我需要这些函数才能从容器访问服务。此外,出于测试目的,我需要用mock和新mock替换服务 因此,模式如下所示: <?php /** * Some controlle

我有一些类需要将依赖项注入到它们的构造函数中。这允许我为测试注入模拟(例如来自)

我对使用容器来帮助配置和访问这些对象感兴趣,我已经研究过了(我也研究过了,尽管我无法快速解决这些问题)

到目前为止一切都很好但是,我遇到的问题是,应用程序(Drupal 7)是围绕数千个函数构建的,这些函数不属于可以将依赖项注入的对象

因此,我需要这些函数才能从容器访问服务。此外,出于测试目的,我需要用mock和新mock替换服务

因此,模式如下所示:

<?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();

      // ...
   }
}