【问题标题】:Get class name from file从文件中获取类名
【发布时间】:2011-08-22 20:08:44
【问题描述】:

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

【问题讨论】:

标签: php reflection


【解决方案1】:

这个问题有多种可能的解决方案,每种都有其优点和缺点。他们在这里,由您决定要哪一个。

分词器

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

优势

  • 不必完全解析文件
  • 快速(仅读取文件开头)
  • 几乎没有误报的机会

缺点

  • 最长解

代码

$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

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

【讨论】:

  • 不错的答案。我会去分词器,那里似乎不可能出现误报。
  • 如何同时包含类的命名空间?如,返回以命名空间为前缀的 FULL 类名?
  • 使用 Tokenizer 解决方案时出现问题,fread($fp, 512) 返回带有“未完成”注释的代码,将此字符串传递给token_get_all 将导致 PHP 警告“未终止的注释起始行 x...”如果您在代码中使用 cmets,这会使 Tokenizer 解决方案无用。
  • @netcoder, if (strpos($buffer, '{') === false) continue; 如果一个类有一个带有内联 phpdoc cmets 的 docblock (/** bla bla {@see SomeClass} {@link http://example.com}) 将导致误报。所以你需要在检查之前过滤掉它们(T_DOC_COMMENT)。
  • @netcoder For PHP 5.5+ 代码标记器解决方案在使用::class 语句时给出误报,因为它具有T_CLASS 标记代码。示例:print_r(token_get_all('&lt;?php $a=\stdClass::class;'));
【解决方案2】:
$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] ...)

【讨论】:

  • 这是一个非常聪明的答案!
【解决方案3】:

您可以通过仅包含文件并获取最后声明的类来让 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

如果你不喜欢命令行脚本,你可以像 this question 那样做,但是该代码不反映命名空间。

【讨论】:

  • 我不能,在我的文件运行之前自动包含该文件,而不是最后一个包含的文件。
  • @Dani:我添加了一个如何通过命令行执行此操作的示例,但是,当一个类依赖于另一个类时,您可能会遇到问题等 - 但是它应该向您展示这个想法。
  • 请注意,这种方法需要所有父类和实现的接口都可以使用自动加载器。它还在类声明之外执行 PHP 代码(如果存在此类代码),这可能是不希望的。
【解决方案4】:

我修改了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】:

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

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

    【讨论】:

    【解决方案6】:

    我花了很多时间寻找解决这个问题的方法。 从@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
    

    【讨论】:

      【解决方案7】:

      此示例返回所有类。如果您正在寻找派生自特定类的类,请使用 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;
      

      【讨论】:

      • is_subclass_of 的好提示,这将减少您正在寻找的内容的噪音
      【解决方案8】:

      感谢 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];
      }
      

      【讨论】:

      【解决方案9】:

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

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

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

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

      【讨论】:

      • 这是逆过程:从类名生成文件路径,Dani 需要知道文件中的类名。
      • 它不是相反的,因为你不直接用类名调用它,但这仅在文件名和类名相同时才有效(现在我在 PHP 的网站上阅读)例如, MyClass.php 包含 MyClass 类。所以我认为,在这种情况下,命名约定策略是最有效和最快的策略。
      • 我同意你的命名约定,但我个人认为 __autoload 在这里没用,应该这样做。
      【解决方案10】:

      您可以在使用get_declared_classes 包含文件之前获取所有声明的类。包含后执行相同的操作,并将两者与array_diff 之类的内容进行比较,您就有了新添加的类。

      【讨论】:

      • 我不能...我的文件只有在包含文件后才会执行,我无法更改它。
      • 我认为第一句后的点必须是逗号。如果它是一个点,则您的第一句话将无效,因为它被视为问题的答案。
      【解决方案11】:

      让我也添加一个与 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;
      
      

      【讨论】:

        猜你喜欢
        • 2014-07-24
        • 2010-09-30
        • 2010-11-05
        • 2016-05-19
        • 1970-01-01
        • 2017-07-17
        相关资源
        最近更新 更多