重建/获取php函数的代码

时间:2011-08-11 13:27:03

标签: php reflection

我可以通过它的名称以编程方式获取函数代码吗?

像:

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

有可能吗?

是否有任何php自我描述功能来重建功能/类代码?
 (意思是代替从源文件获取源代码)

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

在PHP中:
get_class_method(0函数)
typeof运算
ReflectionFunction(感谢Alin Purcaru)

6 个答案:

答案 0 :(得分:70)

扩展使用ReflectionFunction的建议,您可以使用以下内容:

$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);

答案 1 :(得分:17)

没有任何东西可以提供函数的实际代码。唯一接近可用的是ReflectionFunction类。对于具有ReflectionClass的类,它为您提供类成员(常量,变量和方法)及其可见性,但仍然没有实际代码。


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

注意:Reflection API记录不完整。

答案 2 :(得分:2)

我们通过不同的操作系统编程,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);

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

echo $body;

答案 3 :(得分:2)

谢谢你,最后的功能

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;   
}

答案 4 :(得分:0)

我也有类似的需求,在得知\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的语法需求,请参见here

用法:

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

0作为第二个arg将返回最外部作用域中第一个闭包的代码,而1将返回最外部作用域中的第二个闭包。该代码非常粗糙且原始,因此如果您想使用它,则必须将其整理一下。但是,它应该相当稳定且具有功能,因为我们已经知道所有语法都是有效的并且可以脱离基本语法规则。

答案 5 :(得分:0)

我将在此提要中添加另一种出色的代码。这对我有用。

请注意,您可以 yourClassName :: class 替换self :: class ,以帮助您的编辑器解析文件。

$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;
}