【问题标题】:Determining what classes are defined in a PHP class file确定 PHP 类文件中定义了哪些类
【发布时间】:2010-10-30 01:25:41
【问题描述】:

鉴于我们项目中的每个 PHP 文件都包含一个类定义,我如何确定文件中定义了哪些类?

我知道我可以为 class 语句正则表达式文件,但我更愿意做一些更有效的事情。

【问题讨论】:

  • 获取每个文件的类名的目的是什么?最好的解决方案应该适合您的问题空间。就目前而言,我觉得可能有更好的解决方案,具体取决于您要做什么。
  • 已经有一段时间了,但仍然:您可以调用get_declared_classes,保存它,包含类文件,然后再次调用get_declared_classes。不同之处在于该文件。很简单。

标签: php class


【解决方案1】:

使用 PHP 的函数 get_declared_classes()。这将返回当前脚本中定义的类数组。

【讨论】:

  • 所以,我需要将它与包含之前的类列表进行比较......或者你能想到更有效的方法吗?
  • 如果您通过包含加载文件,这是我能想到的最有效的方法。
  • 如果你想找出文件中有哪些类,这不会做。您无法在包含之前和之后比较 get_declared_classes(),因为该文件可能已经包含/自动加载/需要。
  • 正确。这是假设它尚未包含在内。
  • include_once 如果文件已被包含,则返回 true,因此您可以在使用 get_declared_classes 之前检查文件是否已包含
【解决方案2】:

如果您只想检查文件而不加载它,请使用token_get_all():

<?php
header('Content-Type: text/plain');
$php_file = file_get_contents('c2.php');
$tokens = token_get_all($php_file);
$class_token = false;
foreach ($tokens as $token) {
  if (is_array($token)) {
    if ($token[0] == T_CLASS) {
       $class_token = true;
    } else if ($class_token && $token[0] == T_STRING) {
       echo "Found class: $token[1]\n";
       $class_token = false;
    }
  }       
}
?>

基本上,这是一个简单的有限状态机。在 PHP 中,tokens 的序列为:

  • T_CLASS: 'class' 关键字;
  • T_WHITESPACE: 'class' 后面的空格;
  • T_STRING: 类名。

因此,此代码将处理任何奇怪的间距或换行符,因为它使用 PHP 用于执行文件的相同解析器。如果token_get_all()不能解析,PHP也不能。

顺便说一句,您使用token_name() 将令牌编号转换为它的常量名称。

这是我的 c2.php:

<?php
class MyClass {
  public __construct() {
  }
}

class MyOtherClass {
  public __construct() {
  }
}
?>

输出:

Found class: MyClass
Found class: MyOtherClass

【讨论】:

  • 请注意,自 PHP 5.5 起,User::class 之类的内容是检索 FQCN 字符串(如“App\Model\User”)的有效语法。因此,您要么需要明确检查中间的 T_WHITESPACE(而不是其他任何东西),要么可以检查 T_CLASS 之前是否缺少 T_COLON。
【解决方案3】:

我正在从事的项目需要这样的东西,这是我编写的函数:

function file_get_php_classes($filepath) {
  $php_code = file_get_contents($filepath);
  $classes = get_php_classes($php_code);
  return $classes;
}

function get_php_classes($php_code) {
  $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;
}

【讨论】:

  • 并非所有标记都是数组,所以这可能会给出一些警告。
  • @hakre:不会,因为如果$tokens[$i] 是字符串,语法$tokens[i][0] 仍然是允许的。
  • 对,但我建议在这种情况下使用=== 进行比较。
  • 这里有一个带有命名空间的版本:stackoverflow.com/questions/22761554/…
  • 为了彻底,我想验证这里指定的语法是否与语言规范匹配。确实如此! github.com/php/php-langspec/blob/master/spec/…
【解决方案4】:

我已经扩展了 Venkat D 的答案,包括返回方法和搜索目录。 (这个特定的例子是为 CodeIgniter 构建的,它将返回 ./system/application/controller 文件中的所有方法 - 换句话说,您可以通过系统调用的每个公共 url。)

function file_get_php_classes($filepath,$onlypublic=true) {
    $php_code = file_get_contents($filepath);
    $classes = get_php_classes($php_code,$onlypublic);
    return $classes;
}

function get_php_classes($php_code,$onlypublic) {
    $classes = array();
    $methods=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];
            $methods[$class_name] = array();
        }
        if ($tokens[$i - 2][0] == T_FUNCTION
        && $tokens[$i - 1][0] == T_WHITESPACE
        && $tokens[$i][0] == T_STRING) {
            if ($onlypublic) {
                if ( !in_array($tokens[$i-4][0],array(T_PROTECTED, T_PRIVATE))) {
                    $method_name = $tokens[$i][1];
                    $methods[$class_name][] = $method_name;
                }
            } else {
                $method_name = $tokens[$i][1];
                $methods[$class_name][] = $method_name;
            }
        }
    }
    return $methods;
}

function mapSystemClasses($controllerdir="./system/application/controllers/",$onlypublic=true) {
    $result=array();
    $dh=opendir($controllerdir);
    while (($file = readdir($dh)) !== false) {
        if (substr($file,0,1)!=".") {
            if (filetype($controllerdir.$file)=="file") {
                $classes=file_get_php_classes($controllerdir.$file,$onlypublic);
                foreach($classes as $class=>$method) {
                    $result[]=array("file"=>$controllerdir.$file,"class"=>$class,"method"=>$method);

                }
            } else {
                $result=array_merge($result,mapSystemClasses($controllerdir.$file."/",$onlypublic));
            }
        }
    }
    closedir($dh);
    return $result;
}

【讨论】:

    【解决方案5】:

    您可以像这样忽略抽象类(注意 T_ABSTRACT 标记):

    function get_php_classes($php_code)
    {
        $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 && !($tokens[$i - 3] && $i - 4 >= 0 && $tokens[$i - 4][0] == T_ABSTRACT))
            {
                $class_name = $tokens[$i][1];
                $classes[] = $class_name;
            }
        }
        return $classes;
    }
    

    【讨论】:

    • 这将引发非法偏移,因为计数从 2 开始,负数不是合法的数组键。 (2-4 = -2)... 如果在测试 T_ABSTRACT 之前添加条件 $i-4>=0,则可以更正此问题。
    【解决方案6】:

    我需要使用命名空间从文件中解析类,所以我修改了代码。如果有人也需要,这里是:

    public function getPhpClasses($phpcode) {
        $classes = array();
    
        $namespace = 0;  
        $tokens = token_get_all($phpcode); 
        $count = count($tokens); 
        $dlm = false;
        for ($i = 2; $i < $count; $i++) { 
            if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] == "phpnamespace" || $tokens[$i - 2][1] == "namespace")) || 
                ($dlm && $tokens[$i - 1][0] == T_NS_SEPARATOR && $tokens[$i][0] == T_STRING)) { 
                if (!$dlm) $namespace = 0; 
                if (isset($tokens[$i][1])) {
                    $namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1];
                    $dlm = true; 
                }   
            }       
            elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) {
                $dlm = false; 
            } 
            if (($tokens[$i - 2][0] == T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] == "phpclass")) 
                    && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) {
                $class_name = $tokens[$i][1]; 
                if (!isset($classes[$namespace])) $classes[$namespace] = array();
                $classes[$namespace][] = $class_name;
            }
        } 
        return $classes;
    }
    

    【讨论】:

    • 无法让您的 sn-p 处理具有多个命名空间的文件
    【解决方案7】:

    我的 sn-p 也是。可以解析具有多个类、接口、数组和命名空间的文件。 返回由命名空间划分的类+类型(类、接口、抽象)的数组。

    <?php    
        /**
         * 
         * Looks what classes and namespaces are defined in that file and returns the first found
         * @param String $file Path to file
         * @return Returns NULL if none is found or an array with namespaces and classes found in file
         */
        function classes_in_file($file)
        {
    
            $classes = $nsPos = $final = array();
            $foundNS = FALSE;
            $ii = 0;
    
            if (!file_exists($file)) return NULL;
    
            $er = error_reporting();
            error_reporting(E_ALL ^ E_NOTICE);
    
            $php_code = file_get_contents($file);
            $tokens = token_get_all($php_code);
            $count = count($tokens);
    
            for ($i = 0; $i < $count; $i++) 
            {
                if(!$foundNS && $tokens[$i][0] == T_NAMESPACE)
                {
                    $nsPos[$ii]['start'] = $i;
                    $foundNS = TRUE;
                }
                elseif( $foundNS && ($tokens[$i] == ';' || $tokens[$i] == '{') )
                {
                    $nsPos[$ii]['end']= $i;
                    $ii++;
                    $foundNS = FALSE;
                }
                elseif ($i-2 >= 0 && $tokens[$i - 2][0] == T_CLASS && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) 
                {
                    if($i-4 >=0 && $tokens[$i - 4][0] == T_ABSTRACT)
                    {
                        $classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'ABSTRACT CLASS');
                    }
                    else
                    {
                        $classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'CLASS');
                    }
                }
                elseif ($i-2 >= 0 && $tokens[$i - 2][0] == T_INTERFACE && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING)
                {
                    $classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'INTERFACE');
                }
            }
            error_reporting($er);
            if (empty($classes)) return NULL;
    
            if(!empty($nsPos))
            {
                foreach($nsPos as $k => $p)
                {
                    $ns = '';
                    for($i = $p['start'] + 1; $i < $p['end']; $i++)
                        $ns .= $tokens[$i][1];
    
                    $ns = trim($ns);
                    $final[$k] = array('namespace' => $ns, 'classes' => $classes[$k+1]);
                }
                $classes = $final;
            }
            return $classes;
        }
    

    输出类似这样的东西...

    array
      'namespace' => string 'test\foo' (length=8)
      'classes' => 
        array
          0 => 
            array
              'name' => string 'bar' (length=3)
              'type' => string 'CLASS' (length=5)
          1 => 
            array
              'name' => string 'baz' (length=3)
              'type' => string 'INTERFACE' (length=9)
    array
      'namespace' => string 'this\is\a\really\big\namespace\for\testing\dont\you\think' (length=57)
      'classes' => 
        array
          0 => 
            array
              'name' => string 'yes_it_is' (length=9)
              'type' => string 'CLASS' (length=5)
          1 => 
            array
              'name' => string 'damn_too_big' (length=12)
              'type' => string 'ABSTRACT CLASS' (length=14)
          2 => 
            array
              'name' => string 'fogo' (length=6)
              'type' => string 'INTERFACE' (length=9)
    

    可能会帮助某人!

    【讨论】:

      【解决方案8】:

      或者您可以轻松使用 Nette\Reflection 的 AnnotationsParser(可使用 composer 安装):

      use Nette\Reflection\AnnotationsParser;
      $classes = AnnotationsParser::parsePhp(file_get_contents($fileName));
      var_dump($classes);
      

      输出将是这样的:

      array(1) {
        ["Your\Class\Name"] =>
        array(...) {
            // property => comment
        },
        ["Your\Class\Second"] =>
        array(...) {
            // property => comment
        },
      }
      

      parsePhp() method 基本上与其他答案中的示例类似,但您不必自己声明或测试解析。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-12-26
        • 1970-01-01
        • 2011-02-19
        • 2010-10-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多