Define the same PHP function in many namespaces.

huangapple go评论103阅读模式
英文:

Define the same PHP function in many namespaces

问题

为了在单元测试中创建全局函数的模拟版本,我将其模拟版本定义在SUT类的命名空间中,如此处所解释的那样。

考虑一个全局函数 foo()Bar 命名空间和 Baz 命名空间中都被使用的情况。如果我想在两个地方使用相同的 foo() 模拟版本,似乎我被迫要为 foo() 进行两次声明。

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

这段代码不符合DRY原则。更好的方式是只声明一次foo模拟版本。

  1. /* foo-mock.php */
  2. function foo(){
  3. return 'foo mock';
  4. }

然后按需要导入每个命名空间,如以下伪代码所示:

  1. /* namespace-mocks.php */
  2. namespace Bar{
  3. import 'foo-mock.php';
  4. }
  5. namespace Baz{
  6. import 'foo-mock.php';
  7. }

使用include导入,例如

  1. namespace Baz;
  2. include 'foo-mock.php';

不会导致模拟在Baz命名空间中被声明。是否有办法在多个命名空间中声明一个函数而不必多次版本化它?

英文:

In order to create a mock version of a global function for unit testing, I define its mock version in the namespace of the SUT class, as is explained here

Consider a case of global function foo() which is used in namespace Bar and in namespace Baz. If I want to use the same mock version of foo() in both places, it seems I am forced to make two declarations of the mock for foo()

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

This code does not conform to the DRY principal.
It would be preferred to have one declaration of the foo mock

  1. /* foo-mock.php */
  2. function foo(){
  3. return 'foo mock';
  4. }

and then import into each namespace as needed like the following pseudo code:

  1. /* namespace-mocks.php */
  2. namespace Bar{
  3. import 'foo-mock.php';
  4. }
  5. namespace Baz{
  6. import 'foo-mock.php';
  7. }

Importing using include, e.g.

  1. namespace Baz;
  2. include 'foo-mock.php'

does not cause the mock to be declared in the Baz namespace. Is there any way to declare a function in more than one namespace without having more than one version of it?

答案1

得分: 1

如果您需要对一个本地函数进行抽象化,那么请为其创建一个契约(contract),以便您可以使用依赖注入来提供其提供的服务:

  1. interface FooInterface
  2. {
  3. public function get(): string;
  4. }

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

  1. class NativeFoo implements FooInterface
  2. {
  3. public function get(): string
  4. {
  5. return \foo();
  6. }
  7. }

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

  1. class A
  2. {
  3. protected FooInterface $foo;
  4. public function __construct(FooInterface $foo = null)
  5. {
  6. $this->foo = $foo ?? new NativeFoo();
  7. }
  8. public function whatever()
  9. {
  10. $foo = $this->foo->get();
  11. }
  12. }

因此,在构造函数中有一个可选参数,如果您不提供值,将会得到本地的foo函数作为默认值。您只需执行以下操作:

  1. $a = new A();
  2. $a->whatever();

但如果您想要覆盖它,可以这样做:

  1. $foo = new SomeOtherFoo();
  2. $a = new A($foo);
  3. $a->whatever();

然后,在您的测试中,您可以构建一个显式的模拟:

  1. class MockFoo implements FooInterface
  2. {
  3. public function get(): string
  4. {
  5. return 'some test value';
  6. }
  7. }

并传递该实例:

  1. $foo = new MockFoo();
  2. $a = new A($foo);
  3. $a->whatever();

或者使用PHPUnit模拟构建器:

  1. $foo = $this->getMockBuilder(FooInterface::class)->... // 具体操作

如果您想要一个更简单的版本,您可以只使用一个可调用对象:

  1. class A
  2. {
  3. protected $foo;
  4. public function __construct(callable $foo = null)
  5. {
  6. $this->foo = $foo ?? 'foo';
  7. }
  8. public function whatever()
  9. {
  10. $foo = call_user_func($this->foo);
  11. }
  12. }
  13. // 默认用法
  14. $a = new A();
  15. $a->whatever();
  16. // 测试用法
  17. $a = new A(fn() => 'some other value');
  18. $a->whatever();
英文:

If you need to abstract away a native function, then make a contract for it, so that you can use dependency injection for the service it provides:

  1. interface FooInterface
  2. {
  3. public function get(): string;
  4. }

Then provide a default implementation that uses the native function:

  1. class NativeFoo implements FooInterface
  2. {
  3. public function get(): string
  4. {
  5. return \foo();
  6. }
  7. }

Then your main class might look something like:

  1. class A
  2. {
  3. protected FooInterface $foo;
  4. public function __construct(FooInterface $foo = null)
  5. {
  6. $this->foo = $foo ?? new NativeFoo();
  7. }
  8. public function whatever()
  9. {
  10. $foo = $this->foo->get();
  11. }
  12. }

So, you've got an optional argument in the constructor, and if you don't provide a value, you'll get the native foo function as a default. You'd just do:

  1. $a = new A();
  2. $a->whatever();

But if you want to override that, you can do:

  1. $foo = new SomeOtherFoo();
  2. $a = new A($foo);
  3. $a->whatever();

Then, in your test, you can build an explicit mock:

  1. class MockFoo implements FooInterface
  2. {
  3. public function get(): string
  4. {
  5. return 'some test value';
  6. }
  7. }

And pass in instance of that:

  1. $foo = new MockFoo();
  2. $a = new A($foo);
  3. $a->whatever();

Or use the PHPUnit mock builder:

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

If you want a simpler version, you could just use a callable:

  1. class A
  2. {
  3. protected $foo;
  4. public function __construct(callable $foo = null)
  5. {
  6. $this->foo = $foo ?? 'foo';
  7. }
  8. public function whatever()
  9. {
  10. $foo = call_user_func($this->foo);
  11. }
  12. }
  13. // default usage
  14. $a = new A();
  15. $a->whatever();
  16. // test usage
  17. $a = new A(fn() => 'some other value');
  18. $a->whatever();

答案2

得分: 0

I'll provide the translation of the code portion you provided:

  1. 在我全心全意同意Alex Howansky的答案的同时,如果你真的执意要使用纯函数,那么你总是可以:
  2. ```lang-php
  3. namespace Mocks {
  4. function foo() {
  5. return 'foo';
  6. }
  7. }
  8. namespace Bar {
  9. function foo() {
  10. return \Mocks\foo();
  11. }
  12. }
  13. namespace Baz {
  14. function foo() {
  15. return \Mocks\foo();
  16. }
  17. }
  18. namespace asdf {
  19. var_dump(\Bar\foo(), \Baz\foo());
  20. }

输出:

  1. string(3) "foo"
  2. string(3) "foo"

尽管在这一点上,我们实际上只是在重新发明类,但更糟糕。 在某个时候,你会想知道如何给变量命名空间,这是不可能的,所以你将在那一点上将其整合到类中。

类、接口、特性/继承将是最合适的解决这个问题的方式。

  1. <details>
  2. <summary>英文:</summary>
  3. While I whole-heartedly agree with Alex Howansky&#39;s answer, if you&#39;re truly hell-bent on using plain functions, then you can always:
  4. ```lang-php
  5. namespace Mocks {
  6. function foo() {
  7. return &#39;foo&#39;;
  8. }
  9. }
  10. namespace Bar {
  11. function foo() {
  12. return \Mocks\foo();
  13. }
  14. }
  15. namespace Baz {
  16. function foo() {
  17. return \Mocks\foo();
  18. }
  19. }
  20. namespace asdf {
  21. var_dump(\Bar\foo(), \Baz\foo());
  22. }

Output:

  1. string(3) &quot;foo&quot;
  2. string(3) &quot;foo&quot;

Though at this point we're really just on our way to reinventing classes, but worse. At some point you're going to wonder how to namespace a variable, and that is simply not possible, so you'll be rolling this into classes at that point.

Classes, interfaces, and traits/inheritance would be most properly leveraged to solve this problem.

huangapple
  • 本文由 发表于 2023年6月15日 01:45:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76476271.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定