从文件中获取类名

问题描述 投票:0回答:12

我有一个 php 文件,其中只包含一个类。我如何通过知道文件名来知道有什么类?我知道我可以用正则表达式匹配做一些事情,但是有标准的 php 方法吗? (该文件已包含在尝试找出类名的页面中)。

php reflection
12个回答
65
投票

这个问题有多种可能的解决方案,每种方案都有各自的优点和缺点。它们都在这里,您可以自行决定想要哪一个。

分词器

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

优点

  • 不必完全解析文件
  • 快速(仅读取文件的开头)
  • 误报的可能性很小甚至没有

缺点

  • 最长的解决方案

代码

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

正则表达式

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

优点

  • 不必完全解析文件
  • 快速(仅读取文件的开头)

缺点

  • 误报的可能性很高(例如:
    echo "class Foo {";

代码

$fp = fopen($file, 'r');
$class = $buffer = '';
$i = 0;
while (!$class) {
    if (feof($fp)) break;

    $buffer .= fread($fp, 512);
    if (preg_match('/class\s+(\w+)(.*)?\{/', $buffer, $matches)) {
        $class = $matches[1];
        break;
    }
}

注意:正则表达式可能可以改进,但单独的正则表达式不能完美地做到这一点。

获取已声明类的列表

此方法使用

get_declared_classes()
并查找包含后定义的第一个类。

优点

  • 最短解决方案
  • 没有误报的可能性

缺点

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

代码

$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 foo\bar {
    class hello { }
}

...或替代语法:

namespace foo\bar;
class hello { }

您应该得到以下结果:

var_dump($namespace); // \foo\bar
var_dump($class);     // hello

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


8
投票

您只需包含文件并获取最后声明的类即可让 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

如果您不喜欢命令行脚本,您可以像这个问题中那样进行操作,但是该代码不反映命名空间。


6
投票

我修改了

Nette\Reflection\AnnotationsParser
,因此它返回文件中定义的命名空间+类名的数组

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

5
投票
$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] ...)

5
投票

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

/**
 * get the full name (name \ namespace) of a class from its file path
 * result example: (string) "I\Am\The\Namespace\Of\This\Class"
 *
 * @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];
}

2
投票

您可以通过两种方式做到这一点:

  • 复杂的解决方案:打开文件并通过正则表达式提取类名(如
    /class ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/
  • 简单的解决方案:用包含的类名命名所有php文件(例如:文件中的类
    TestFoo
    TestFoo.php
    
        


2
投票

array_diff



2
投票

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

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



1
投票

<?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



0
投票

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



-1
投票

/** * @param string $filepath * @return string|null */ public function getClassFromFile(string $filepath): ?string { static $classes = []; if (empty($filepath = $filepath ? realpath($filepath) : null)) { return null; } elseif (array_key_exists($filepath, $classes)) { return $classes[$filepath]; } $basename = substr(basename($filepath), 0, -4); require_once($filepath); foreach (array_reverse(get_declared_classes()) as $classname) { if (! strcasecmp($basename, basename('/'.str_replace('\\', '/', $classname)))) { $reflector = new \ReflectionClass($classname); if ($reflector->getFileName() == $filepath) { return ($classes[$filepath] = $classname); } } } return ($classes[$filepath] = null); }

您可以 echo $class_name。但这需要一个包含单个文件的目录。

但是在 PHP 中,每个文件都有一个类是标准做法。所以Main.class.php将包含Main类。如果您是唯一的编码者,您也许可以使用该标准。

© www.soinside.com 2019 - 2024. All rights reserved.