我可以通过编程方式获得函数名的源代码吗?
比如:
function blah($a, $b) { return $a*$b; } echo getFunctionCode("blah");
有可能吗?
是否有任何 PHP 自描述函数来重建函数/类代码?(我的意思是不要直接从源文件获取源代码。)
在 Java 中存在: http://java.sun.com/developer/technicalArticles/ALT/Reflection/
没有任何东西可以给你函数的实际代码。唯一接近这一点的是 反射函数类。对于类,你有 反射类,它给你类成员(常量、变量和方法)和它们的可见性,但是仍然没有实际的代码。
解决方案 (它确实涉及到读取源文件) : 使用 反射函数: : export查找声明函数的文件名和行间隔,然后从这些行上的文件中读取内容。使用字符串处理来获取第一个 {和最后一个 }之间的内容。
{
}
注意: 反射 API 的文档很少。从 PHP 7.4开始就不推荐使用 ReflectionFunction::export
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只有关于 台词开始和结束的信息之后,我觉得有必要编写一些代码来提取一个闭包的代码,或者更有可能是一个短的闭包,当多个闭包可能存在于同一行中,甚至是嵌套的(安全总比遗憾好)。需要注意的一点是,您必须知道它是否是第一个、第二个等等闭包,如果它们作为参数列表或数组传递,您可能会这样做。
\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 的语法需求,请参见 给你。
Source::readFunctionTokens()
\token_get_all()
用法:
$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; }