在多个命名空间中定义相同的PHP函数

b5buobof  于 2023-06-21  发布在  PHP
关注(0)|答案(2)|浏览(132)

为了创建用于单元测试的全局函数的模拟版本,我在SUT类的名称空间中定义了它的模拟版本,如here所解释的
考虑在命名空间Bar和命名空间Baz中使用的全局函数foo()的情况。如果我想在这两个地方使用相同的foo()模拟版本,似乎我必须为foo()做两个模拟声明

/* bar-namespace-mocks.php */
namespace Bar;
function foo(){
   return 'foo mock';
}
/* baz-namespace-mocks.php */
namespace Baz;
function foo(){
   return 'foo mock';
}

此代码不符合DRY principal。最好有一个foo mock的声明

/* foo-mock.php */
function foo(){
   return 'foo mock';
}

然后根据需要导入到每个命名空间,如以下伪代码:

/* namespace-mocks.php */
namespace Bar{
  import 'foo-mock.php';
} 
namespace Baz{
  import 'foo-mock.php';
}

使用include导入,例如

namespace Baz;
include 'foo-mock.php'

不会导致在Baz命名空间中声明mock。有没有办法在多个命名空间中声明一个函数,而不需要多个版本?

7dl7o3gd

7dl7o3gd1#

如果你需要抽象出一个原生函数,那么为它创建一个合约,这样你就可以对它提供的服务使用依赖注入:

interface FooInterface
{
    public function get(): string;
}

然后提供一个使用本机函数的默认实现:

class NativeFoo implements FooInterface
{
    public function get(): string
    {
        return \foo();
    }
}

然后你的主类可能看起来像这样:

class A
{
    protected FooInterface $foo;

    public function __construct(FooInterface $foo = null)
    {
        $this->foo = $foo ?? new NativeFoo();
    }

    public function whatever()
    {
        $foo = $this->foo->get();
    }
}

所以,你在构造函数中有一个可选参数,如果你不提供一个值,你将获得原生的foo函数作为默认值。你只需要做:

$a = new A();
$a->whatever();

但是如果你想覆盖它,你可以这样做:

$foo = new SomeOtherFoo();
$a = new A($foo);
$a->whatever();

然后,在你的测试中,你可以构建一个显式的mock:

class MockFoo implements FooInterface
{
    public function get(): string
    {
        return 'some test value';
    }
}

并在此示例中通过:

$foo = new MockFoo();
$a = new A($foo);
$a->whatever();

或者使用PHPUnit模拟构建器:

$foo = $this->getMockBuilder(FooInterface::class)->... // whatever

如果你想要一个更简单的版本,你可以只使用一个callable:

class A
{
    protected $foo;

    public function __construct(callable $foo = null)
    {
        $this->foo = $foo ?? 'foo';
    }

    public function whatever()
    {
        $foo = call_user_func($this->foo);
    }
}

// default usage
$a = new A();
$a->whatever();

// test usage
$a = new A(fn() => 'some other value');
$a->whatever();
pkln4tw6

pkln4tw62#

虽然我完全同意Alex Howansky的回答,但如果你真的执意要使用普通函数,那么你总是可以:

namespace Mocks {
    function foo() {
        return 'foo';
    }
}

namespace Bar {
  function foo() {
      return \Mocks\foo();
  }
}

namespace Baz {
  function foo() {
      return \Mocks\foo();
  }
}

namespace asdf {
    var_dump(\Bar\foo(), \Baz\foo());
}

输出:

string(3) "foo"
string(3) "foo"

虽然在这一点上,我们真的只是在重新发明类的路上,但更糟。在某个时候,你会想知道如何命名一个变量,这是不可能的,所以你会在这一点上滚动到类。
类、接口和traits/继承是解决这个问题最合适的工具。

相关问题