-1

我有一个包含一些类的文件夹和另一个包含一些功能的文件夹。

通常每个文件一个类或函数,但并非总是如此。

在少数情况下,一个类可能会附带一个或两个函数,并且某些函数可能会组合在一起。

我正在阅读每个文件,并根据每个文件的详细评论构建一个很好的手册。

我在想如果能同时获取类或函数的代码也会很好。

但是我还没有找到这样做的方法。

正则表达式是不可能的,因为它们只能匹配简单的函数。

我找到了 PHP Tokenizer,但我不知道这有什么帮助。

谷歌也无济于事。

如果存在,我正在寻找一种纯 PHP 解决方案。

假设我有这样的代码:

class BaseClass {
   function __construct() {
       print "In BaseClass constructor\n";
   }
}

class SubClass extends BaseClass {
   function __construct() {
       parent::__construct();
       print "In SubClass constructor\n";
   }
}

class OtherSubClass extends BaseClass {
    // inherits BaseClass's constructor
}

function monkey() {
    return new BaseClass();
}

function weasel() {
    return new SubClass();
}

function dragon() {
    return new OtherSubClass();
}

我想解析它并获得一个包含 6 个条目的数组,每个类一个,每个函数一个。

4

2 回答 2

1

您需要的基本上是一个解析器,以便您可以挑选出感兴趣的结构。然后,您可以使用解析器收集的位置信息(如果设计得当)来确定文件中文本的边界以提取该结构,或者您“漂亮地打印”已解析结构的 AST 以获得您的工件。

NikiC 在这个SO question中描述了他在 PHP 中搜索并最终构建了一个这样的解析器。那里提供了其他解决方案,包括我的,但它不在 PHP 中。

您可能很难选择您想要的确切功能。假设您有一个文件,其中包含两个类 C1 和 C2,每个类都包含一个名为 M 的方法。现在要选择“正确的方法”,您需要有可用的完整路径 C1::M,并且您需要检查方法 M在正确的 C1 类中找到。您可以通过从 M 向上遍历分析树来做到这一点。如果您有特征,这可能会变得更难,因为方法 M 可能会在特征中定义,然后集成到类定义中。要真正做到这一点,您需要 PHP 的名称解析。

如果你走得那么远,你可能需要滥用 Hip Hop(PHP-to-C 编译器)来提取你想要的东西,假设它可能会以可用的形式构建 AST 和完整的符号表。(我不知道它是否真的这样做)。

于 2013-06-15T15:20:53.150 回答
0
<?php

/**moDO(Classes)(Parsers)(parse_php)

  @(Description)
    Parses php code and generates an array with the code of the classes and stand-alone functions found.

  @(Description){note warn}
    Curly brackets outside the code structures can break the parser!


  @(Syntax){quote}
    object `parse_php` ( string ~$path~ )


  @(Parameters)
  @(Parameters)($path)
    Path to the php file to be parsed.


  @(Return)
    Returns an object that contains a variable `parsed` that contains the resulting array.


  @(Examples)
  @(Examples)(1){info}
    `Example 1:` Basic usage example.

  @(Examples)(2){code php}
  $parser = new parse_php(__FILE__);
  print_r($parser->parsed);


  @(Changelog){list}
   (1.0) ~Initial release.~

*/

  /**
  * Parses php code and generates an array with the code of the classes and stand-alone functions found.
  * Note: Curly brackets outside the code structures can break the parser!
  * @syntax new parse_php($path);
  * @path string containing path to file to be parsed
  */
  class parse_php {
    public $parsed = false;

    /**
    * Validates the path parameter and starts the parsing.
    * Once parsing done it sets the result in the $parsed variable.
    * @path string representing valid absolute or relative path to a file.
    */
    function __construct($path) {
      if(is_file($path)) {
        $this->parsed = $this->load($path);
      }
    }

    /**
    * This loads prepares the contents for parsing.
    * It normalizes the line endings, builds lines array and looks up the structures. 
    * @path string representing valid absolute or relative path to a file.
    */
    private function load($path) {
      $file   = file_get_contents($path);
      $string = str_replace(Array("\r\n", "\r", "\n"), Array("\n", "\n", "\r\n"), $file);
      $array  = explode("\r\n", $string);

      preg_match_all('/((abstract[ ])?(function|class|interface)[ ]+'
                    .'[a-z_\x7f-\xff][a-z0-9_\x7f-\xff]+[ ]*(\((.+)?\)[ ]*)?)'
                    .'([ ]*(extends|implements)[ ]*[a-z_\x7f-\xff]'
                    .'[a-z0-9_\x7f-\xff]+[ ]?)?[ ]*(\r|\n|\r\n)*[ ]*(\{)/i'
                    , $string
                    , $matches);

      $filtered = Array();
      foreach($matches[0] AS $match) {
        list($first, $rest) = explode("\r\n", $match, 2);
        $filtered[] = $first;
      }

      return $this->parse($array, $filtered);
    }

    /**
    * The parser that loops the content lines and builds the result array.
    * It accounts for nesting and skipps all functions that belong to a class.
    * @lines array with the lines of the code file.
    * @matches array containing the classes and possible stand-alone functions to be looked up.
    */
    private function parse($lines, $matches) {
      $key        = false;
      $track      = false;
      $nesting    = 0;
      $structures = Array();

      foreach($lines AS $line) {
        if($key === false)
         $key = $this->array_value_in_string($line, $matches);

        if($key !== false) {
          if($nesting > 0)
           $track = true;

          $nesting = $nesting + substr_count($line, ' {');
          $nesting = $nesting - substr_count($line, ' }');

          $structures[$key][] = $line;

          if($track && $nesting == 0) {
            $track = false;
            $key   = false;
          }
        }
      }

      return array_values($structures);
    }

    /**
    * Checks if any of the (array)needles are found in the (string)haystack.
    * @syntax $this->array_value_in_string($string, $array);
    * @haystack string containing the haystack subject of the search.
    * @needles array containing the needles to be searched for.
    */
    private function array_value_in_string($haystack, $needles) {
      foreach($needles AS $key => $value) {
        if(stristr($haystack, $value))
         return $key;
      }
      return false;
    }
  }

  /**
  * Example execute self
  */
  header('Content-type: text/plain');
  $parser = new parse_php('test.php');
  print_r($parser->parsed);
于 2013-06-24T17:58:43.280 回答