重构/获取 PHP 函数的源代码

我可以通过编程方式获得函数名的源代码吗?

比如:

function blah($a, $b) { return $a*$b; }
echo getFunctionCode("blah");

有可能吗?

是否有任何 PHP 自描述函数来重建函数/类代码?(我的意思是不要直接从源文件获取源代码。)

在 Java 中存在: http://java.sun.com/developer/technicalArticles/ALT/Reflection/

26170 次浏览

没有任何东西可以给你函数的实际代码。唯一接近这一点的是 反射函数类。对于类,你有 反射类,它给你类成员(常量、变量和方法)和它们的可见性,但是仍然没有实际的代码。


解决方案 (它确实涉及到读取源文件) :
使用 反射函数: : export查找声明函数的文件名和行间隔,然后从这些行上的文件中读取内容。使用字符串处理来获取第一个 {和最后一个 }之间的内容。

注意: 反射 API 的文档很少。从 PHP 7.4开始就不推荐使用 ReflectionFunction::export

继续扩展使用反射函数的建议,您可以使用如下内容:

$func = new ReflectionFunction('myfunction');
$filename = $func->getFileName();
$start_line = $func->getStartLine() - 1; // it's actually - 1, otherwise you wont get the function() block
$end_line = $func->getEndLine();
$length = $end_line - $start_line;


$source = file($filename);
$body = implode("", array_slice($source, $start_line, $length));
print_r($body);

我们通过不同的操作系统编程,gnu/linux,windows,mac... 由于这个原因,我们在代码中有不同的回车,为了解决这个问题,我分叉了 Brandon Horsley 的答案,并准备检查不同的 CR,从一个类的方法而不是函数中获得代码:

$cn = 'class_example';
$method = 'method_example';


$func = new ReflectionMethod($cn, $method);


$f = $func->getFileName();
$start_line = $func->getStartLine() - 1;
$end_line = $func->getEndLine();
$length = $end_line - $start_line;


$source = file($f);
$source = implode('', array_slice($source, 0, count($source)));
// $source = preg_split("/(\n|\r\n|\r)/", $source);
$source = preg_split("/".PHP_EOL."/", $source);


$body = '';
for($i=$start_line; $i<$end_line; $i++)
$body.="{$source[$i]}\n";


echo $body;

谢谢,最终功能

function get_function($method,$class=null){


if (!empty($class)) $func = new ReflectionMethod($class, $method);
else $func = new ReflectionFunction($method);


$f = $func->getFileName();
$start_line = $func->getStartLine() - 1;
$end_line = $func->getEndLine();
$length = $end_line - $start_line;


$source = file($f);
$source = implode('', array_slice($source, 0, count($source)));
$source = preg_split("/".PHP_EOL."/", $source);


$body = '';
for($i=$start_line; $i<$end_line; $i++)
$body.="{$source[$i]}\n";


return $body;
}

我也有类似的需求,在了解到 \ReflectionFunction只有关于 台词开始和结束的信息之后,我觉得有必要编写一些代码来提取一个闭包的代码,或者更有可能是一个短的闭包,当多个闭包可能存在于同一行中,甚至是嵌套的(安全总比遗憾好)。需要注意的一点是,您必须知道它是否是第一个、第二个等等闭包,如果它们作为参数列表或数组传递,您可能会这样做。

在我的例子中,我有非常具体的愿望,但是也许获得闭包代码的一般解决方案对其他人会有用,所以我将把它放在这里..。

<?php
namespace Phluid\Transpiler;


use ReflectionFunction;


final class Source
{
private const OPEN_NEST_CHARS = ['(', '[', '{'];
private const CLOSE_NEST_CHARS = [')', ']', '}'];
private const END_EXPRESSION_CHARS = [';', ','];


public static function doesCharBeginNest($char)
{
return \in_array($char, self::OPEN_NEST_CHARS);
}


public static function doesCharEndExpression($char)
{
return \in_array($char, self::END_EXPRESSION_CHARS);
}


public static function doesCharEndNest($char)
{
return \in_array($char, self::CLOSE_NEST_CHARS);
}


public static function readFunctionTokens(ReflectionFunction $fn, int $index = 0): array
{
$file = \file($fn->getFileName());
$tokens = \token_get_all(\implode('', $file));
$functionTokens = [];
$line = 0;


$readFunctionExpression = function ($i, &$functionTokens) use ($tokens, &$readFunctionExpression) {
$start = $i;
$nest = 0;


for (; $i < \count($tokens); ++$i) {
$token = $tokens[$i];


if (\is_string($token)) {
if (self::doesCharBeginNest($token)) {
++$nest;
} elseif (self::doesCharEndNest($token)) {
if ($nest === 0) {
return $i + 1;
}


--$nest;
} elseif (self::doesCharEndExpression($token)) {
if ($nest === 0) {
return $i + 1;
}
}
} elseif ($i !== $start && ($token[0] === \T_FN || $token[0] === \T_FUNCTION)) {
return $readFunctionExpression($i, $functionTokens);
}


$functionTokens[] = $token;
}


return $i;
};


for ($i = 0; $i < \count($tokens); ++$i) {
$token = $tokens[$i];
$line = $token[2] ?? $line;


if ($line < $fn->getStartLine()) {
continue;
} elseif ($line > $fn->getEndLine()) {
break;
}


if (\is_array($token)) {
if ($token[0] === \T_FN || $token[0] === \T_FUNCTION) {
$functionTokens = [];
$i = $readFunctionExpression($i, $functionTokens);


if ($index === 0) {
break;
}


--$index;
}
}
}


return $functionTokens;
}
}

Source::readFunctionTokens()方法将返回与 PHP 自己的 \token_get_all()函数类似的输出,只是从闭包的开始到结束过滤到只有代码。因此,它是字符串和数组的混合,这取决于 PHP 的语法需求,请参见 给你

用法:

$fn = [fn() => fn() => $i = 0, function () { return 1; }];
$tokens = Source::readFunctionTokens(new \ReflectionFunction($fn[1]), 1);

0作为第二个参数将返回最外部作用域中第一个闭包的代码,而1将返回最外部作用域中的第二个闭包。这段代码非常粗糙,所以如果你想使用它的话,感觉有必要把它整理一下。它的 应该是相当稳定和能力,虽然,因为我们已经知道所有的语法是有效的,可以脱离基本的语法规则。

我将在这个 feed 中添加另一种风格的伟大代码。

注意,您可以使用 Your ClassName: : class替换 self: : 类来帮助编辑器解析文件。

$methods = get_class_methods(self::class);


foreach ($methods as $method) {
    

    

$func = new ReflectionMethod(self::class, $method);


$f = $func->getFileName();


$start_line = $func->getStartLine() - 1;


$end_line = $func->getEndLine();


$length = $end_line - $start_line;


$source = file_get_contents($f);


$source = preg_split('/' . PHP_EOL . '/', $source);


$body = implode(PHP_EOL, array_slice($source, $start_line, $length));


echo $body . PHP_EOL . PHP_EOL;
}