从文件中获取类名



我有一个php文件,其中只包含一个类。我怎么知道文件名是什么类?我知道我可以做一些与regexp匹配,但有一个标准的php方式?(该文件已经包含在试图找出类名的页面中)。

这个问题有多种可能的解决方案,每种都有其优点和缺点。这些都在这里,这取决于你决定你想要哪个。

记号赋予器

此方法使用标记器并读取文件的部分内容,直到找到类定义。

  • 不需要解析整个文件
  • 快速(只读取文件的开头)
  • 几乎没有假阳性的机会
<

缺点/strong>

    最长
  • 解决方案

$fp = fopen($file, 'r');
$class = $buffer = '';
$i = 0;
while (!$class) {
    if (feof($fp)) break;
    $buffer .= fread($fp, 512);
    $tokens = token_get_all($buffer);
    if (strpos($buffer, '{') === false) continue;
    for (;$i<count($tokens);$i++) {
        if ($tokens[$i][0] === T_CLASS) {
            for ($j=$i+1;$j<count($tokens);$j++) {
                if ($tokens[$j] === '{') {
                    $class = $tokens[$i+2][1];
                }
            }
        }
    }
}
正则表达式

使用正则表达式解析文件的开头,直到找到类定义。

  • 不需要解析整个文件
  • 快速(只读取文件的开头)
<

缺点/strong>

  • 假阳性几率高(例如:echo "class Foo {";)

$fp = fopen($file, 'r');
$class = $buffer = '';
$i = 0;
while (!$class) {
    if (feof($fp)) break;
    $buffer .= fread($fp, 512);
    if (preg_match('/classs+(w+)(.*)?{/', $buffer, $matches)) {
        $class = $matches[1];
        break;
    }
}

注意:正则表达式可能可以改进,但没有一个正则表达式可以单独完成这一点。

获取已声明类的列表

该方法使用get_declared_classes()并查找include后定义的第一个类。

  • 最短解
  • 无假阳性机会
<

缺点/strong>

  • 必须加载整个文件
  • 必须在内存中加载整个类列表两次
  • 必须在内存中加载类定义

$classes = get_declared_classes();
include 'test2.php';
$diff = array_diff(get_declared_classes(), $classes);
$class = reset($diff);

注意:你不能简单地做end()别人建议。如果类中包含另一个类,则会得到错误的结果。


这是Tokenizer解决方案,修改为包含$namespace变量,其中包含类名称空间,如果适用:

$fp = fopen($file, 'r');
$class = $namespace = $buffer = '';
$i = 0;
while (!$class) {
    if (feof($fp)) break;
    $buffer .= fread($fp, 512);
    $tokens = token_get_all($buffer);
    if (strpos($buffer, '{') === false) continue;
    for (;$i<count($tokens);$i++) {
        if ($tokens[$i][0] === T_NAMESPACE) {
            for ($j=$i+1;$j<count($tokens); $j++) {
                if ($tokens[$j][0] === T_STRING) {
                     $namespace .= '\'.$tokens[$j][1];
                } else if ($tokens[$j] === '{' || $tokens[$j] === ';') {
                     break;
                }
            }
        }
        if ($tokens[$i][0] === T_CLASS) {
            for ($j=$i+1;$j<count($tokens);$j++) {
                if ($tokens[$j] === '{') {
                    $class = $tokens[$i+2][1];
                }
            }
        }
    }
}

假设你有这样一个类:

namespace foobar {
    class hello { }
}

…或者另一种语法:

namespace foobar;
class hello { }

您应该得到以下结果:

var_dump($namespace); // foobar
var_dump($class);     // hello

您还可以使用上面的方法来检测文件声明的名称空间,而不管它是否包含类。

您可以让PHP只包含文件并获得最后声明的类来完成这项工作:

$file = 'class.php'; # contains class Foo
include($file);
$classes = get_declared_classes();
$class = end($classes);
echo $class; # Foo

如果需要将其隔离,请将其封装到命令行脚本中并通过shell_exec执行:

$file = 'class.php'; # contains class Foo
$class = shell_exec("php -r "include('$file'); echo end(get_declared_classes());"");    
echo $class; # Foo

如果您不喜欢命令行脚本,您可以像在这个问题中那样做,但是代码不反映名称空间

我修改了NetteReflectionAnnotationsParser,所以它返回一个在文件

中定义的namespace+classname数组
$parser = new PhpParser();
$parser->extractPhpClasses('src/Path/To/File.php');

class PhpParser
{
    public function extractPhpClasses(string $path)
    {
        $code = file_get_contents($path);
        $tokens = @token_get_all($code);
        $namespace = $class = $classLevel = $level = NULL;
        $classes = [];
        while (list(, $token) = each($tokens)) {
            switch (is_array($token) ? $token[0] : $token) {
                case T_NAMESPACE:
                    $namespace = ltrim($this->fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\', '\');
                    break;
                case T_CLASS:
                case T_INTERFACE:
                    if ($name = $this->fetch($tokens, T_STRING)) {
                        $classes[] = $namespace . $name;
                    }
                    break;
            }
        }
        return $classes;
    }
    private function fetch(&$tokens, $take)
    {
        $res = NULL;
        while ($token = current($tokens)) {
            list($token, $s) = is_array($token) ? $token : [$token, $token];
            if (in_array($token, (array) $take, TRUE)) {
                $res .= $s;
            } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], TRUE)) {
                break;
            }
            next($tokens);
        }
        return $res;
    }
}
$st = get_declared_classes();
include "classes.php"; //one or more classes in file, contains class class1, class2, etc...
$res = array_values(array_diff_key(get_declared_classes(),$st));
print_r($res); # Array ([0] => class1 [1] => class2 [2] ...)

感谢来自Stackoverflow和Github的一些人,我能够写出这个惊人的完全工作的解决方案:

/**
 * get the full name (name  namespace) of a class from its file path
 * result example: (string) "IAmTheNamespaceOfThisClass"
 *
 * @param $filePathName
 *
 * @return  string
 */
public function getClassFullNameFromFile($filePathName)
{
    return $this->getClassNamespaceFromFile($filePathName) . '\' . $this->getClassNameFromFile($filePathName);
}

/**
 * build and return an object of a class from its file path
 *
 * @param $filePathName
 *
 * @return  mixed
 */
public function getClassObjectFromFile($filePathName)
{
    $classString = $this->getClassFullNameFromFile($filePathName);
    $object = new $classString;
    return $object;
}


/**
 * get the class namespace form file path using token
 *
 * @param $filePathName
 *
 * @return  null|string
 */
protected function getClassNamespaceFromFile($filePathName)
{
    $src = file_get_contents($filePathName);
    $tokens = token_get_all($src);
    $count = count($tokens);
    $i = 0;
    $namespace = '';
    $namespace_ok = false;
    while ($i < $count) {
        $token = $tokens[$i];
        if (is_array($token) && $token[0] === T_NAMESPACE) {
            // Found namespace declaration
            while (++$i < $count) {
                if ($tokens[$i] === ';') {
                    $namespace_ok = true;
                    $namespace = trim($namespace);
                    break;
                }
                $namespace .= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i];
            }
            break;
        }
        $i++;
    }
    if (!$namespace_ok) {
        return null;
    } else {
        return $namespace;
    }
}
/**
 * get the class name form file path using token
 *
 * @param $filePathName
 *
 * @return  mixed
 */
protected function getClassNameFromFile($filePathName)
{
    $php_code = file_get_contents($filePathName);
    $classes = array();
    $tokens = token_get_all($php_code);
    $count = count($tokens);
    for ($i = 2; $i < $count; $i++) {
        if ($tokens[$i - 2][0] == T_CLASS
            && $tokens[$i - 1][0] == T_WHITESPACE
            && $tokens[$i][0] == T_STRING
        ) {
            $class_name = $tokens[$i][1];
            $classes[] = $class_name;
        }
    }
    return $classes[0];
}

有两种方法:

  • 复杂解决方案:打开文件,通过regex提取类名(如/class ([a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*)/)
  • 简单的解决方案:命名所有的php文件与类名包含(例如:类TestFoo在文件TestFoo.phpTestFoo.class.php)

您可以在使用get_declared_classes包含文件之前获得所有声明的类。在包含它之后做同样的事情,并将两者与array_diff之类的东西进行比较,您就有了新添加的类。

此示例返回所有类。如果你正在寻找一个特定类的派生类,使用is_subclass_of

$php_code = file_get_contents ( $file );
    $classes = array ();
    $namespace="";
    $tokens = token_get_all ( $php_code );
    $count = count ( $tokens );
    for($i = 0; $i < $count; $i ++)
    {
        if ($tokens[$i][0]===T_NAMESPACE)
        {
            for ($j=$i+1;$j<$count;++$j)
            {
                if ($tokens[$j][0]===T_STRING)
                    $namespace.="\".$tokens[$j][1];
                elseif ($tokens[$j]==='{' or $tokens[$j]===';')
                    break;
            }
        }
        if ($tokens[$i][0]===T_CLASS)
        {
            for ($j=$i+1;$j<$count;++$j)
                if ($tokens[$j]==='{')
                {
                    $classes[]=$namespace."\".$tokens[$i+2][1];
                }
        }
    }
    return $classes;

我花了很多时间来寻找解决这个问题的方法。从@netcoder的解决方案可以明显看出,到目前为止,所有的解决方案都有很多缺点。

所以我决定这样做。由于大多数PHP类的类名与文件名相同,我们可以从文件名中获得类名。根据您的项目,您还可以有一个命名约定。注:这个假设类没有命名空间

<?php
    $path = '/path/to/a/class/file.php';
    include $path;
    /*get filename without extension which is the classname*/
    $classname = pathinfo(basename($path), PATHINFO_FILENAME);
    /* you can do all this*/
    $classObj = new $classname();
    /*dough the file name is classname you can still*/
    get_class($classObj); //still return classname

让我添加一个PHP 8兼容的解决方案。这将相应地扫描文件并返回所有FQCN:

$file      = 'whatever.php';
$classes   = [];
$namespace = '';
$tokens    = PhpToken::tokenize(file_get_contents($file));
for ($i = 0; $i < count($tokens); $i++) {
    if ($tokens[$i]->getTokenName() === 'T_NAMESPACE') {
        for ($j = $i + 1; $j < count($tokens); $j++) {
            if ($tokens[$j]->getTokenName() === 'T_NAME_QUALIFIED') {
                $namespace = $tokens[$j]->text;
                break;
            }
        }
    }
    if ($tokens[$i]->getTokenName() === 'T_CLASS') {
        for ($j = $i + 1; $j < count($tokens); $j++) {
            if ($tokens[$j]->getTokenName() === 'T_WHITESPACE') {
                continue;
            }
            if ($tokens[$j]->getTokenName() === 'T_STRING') {
                $classes[] = $namespace . '\' . $tokens[$j]->text;
            } else {
                break;
            }
        }
    }
}
// Contains all FQCNs found in a file.
$classes;

您可以使用自动加载功能。

function __autoload($class_name) {
    include "special_directory/" .$class_name . '.php';
}

你可以回显$class_name。但这需要一个包含单个文件的目录。

但是在PHP中每个文件中有一个类是标准做法。所以Main。class。php将包含Main类。如果您是编码人员,则可以使用该标准。

相关内容

  • 没有找到相关文章

最新更新