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

时间:2019-03-13 14:07:28

标签: php

我目前正在测试一个以前从未测试过的系统(实际上也没有考虑到测试)。我必须尽可能少地编辑源代码 。这是从头开始的指令,不是我自己的想法。理想情况下,我完全不需要编辑任何源代码即可实现我的目标。

我正在测试的功能使用内置功能file()。以前,我在测试用例中通过在与我要测试的函数相同的名称空间中创建一个具有相同名称的新函数来伪造内置函数,因为PHP首先会在相同的名称空间中进行搜索。 / p>

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
{
    ...
}

当我想伪造 entire 测试套件的方法时,这种方法就起作用了,但是现在我遇到了我想伪造 just 函数的情况。 > 单个测试。

有什么方法可以以编程方式在命名空间中定义函数?

1 个答案:

答案 0 :(得分: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(要执行的语句)
  • 使用反射来检索语句的代码,
  • 使用eval s在伪命名空间下评估这些语句。

演示

https://3v4l.org/LriLW