是否可以以编程方式在给定的命名空间中定义新函数?

问题描述 投票:2回答:1

我目前正在推出一个以前从未进行过测试的系统(也没有真正考虑过测试)。我必须尽可能少地编辑源代码。这是一个高度的指令,而不是我自己的想法。理想情况下,我完成目标而不编辑任何源代码。

我正在测试的函数使用内置函数file()。以前,在我的测试用例中,我伪造了内置函数,在与我正在测试的函数相同的命名空间中创建一个具有相同名称的新函数,因为PHP将首先在同一名称空间中进行搜索。

namespace My\Function\Namespace

class MyClass
{
    public function theMethodImTesting()
    {
        file(...);
        ...
    }
}
namespace My\Function\Namespace

function file()
{
    \\ fake stuff for testing
}

namespace My\Testsuite\Namespace

class MyTestsuite
{
    ...
}

当我想要伪造整个测试套件的方法时,这已经奏效了,但现在我遇到了一个案例,我只想在一次测试中伪造该函数。

有没有办法在命名空间内以编程方式定义函数?

php
1个回答
3
投票

您可以使用内置的PHP功能执行此操作。

警告

这有点尴尬(并且可能不适用于所有情况),因此除非您不能使用评论中正确推荐的任何内容,否则我不建议这样做(虽然我不熟悉它们,所以我可以不确定。

然而它应该做的工作,尽管看起来有多丑(是的,它确实利用了普遍讨厌的eval,但是因为它是用于测试目的,所以它绝不应该处理非受控输入)。

你只需要定义一次(并且应该在它自己的文件中)的东西

现在这已经不在了,这就是它。在某处添加以下代码,它定义了fake函数,然后在特定名称空间下定义了所需的所有(实际)假函数(例如file):

namespace Fake\BuiltIn\Functions;

/**
 * Executes the given statements using fake built-in functions.
 *
 * @param callable $statements Statements to execute.
 * @return mixed Whatever $statements returns.
 * @throws \ReflectionException
 */
function fake(callable $statements)
{
  $function = new \ReflectionFunction($statements);

  $start_line = $function->getStartLine();
  $end_line = $function->getEndLine();
  $function_source = implode('',
    array_slice(file($function->getFileName()), $start_line - 1, $end_line - $start_line + 1));

  if (preg_match('/(?<={).*(?=})/s', $function_source, $matches)) {
    $function_body = $matches[0];
    $namespace = __NAMESPACE__;

    return eval("
      namespace $namespace;
      $function_body
    ");
  }

  throw new \RuntimeException('Failed to execute statements.');
}

// Below are all the fake functions

function strlen($string) {
  return 'fake result';
}

用法

然后,每当您需要使用伪函数调用一大块代码时,您将替换:

function myTestFunction() {
  // some code
  $length = strlen($mystring);
  // some code
}

有:

use function Fake\BuiltIn\Functions\fake;

function myTestFunction() {
  fake(function () {
    // some code
    $length = strlen($mystring);
    // some code
  });
}

简而言之,您只需在块之前添加fake function () {并使用下面的}关闭它。这需要按要求进行最少的编辑。

说明

基本上,eval似乎是在运行时在给定命名空间的上下文中评估特定代码块的唯一内置方法(除非您可以在其自己的命名空间中调用该块,显然)。

fake功能:

  • 收到callable(要执行的陈述),
  • 使用反射来检索语句的代码,
  • 使用evals来评估假名称空间下的这些语句。

演示

https://3v4l.org/LriLW

© www.soinside.com 2019 - 2024. All rights reserved.