从源代码中获取特定用户定义函数或类的代码

时间:2013-06-15 10:07:02

标签: php regex parsing

我有一个包含一些类的文件夹,另一个包含一些函数。

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

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

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

我认为抓住类或函数的代码会很好。

但我还没有找到办法。

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

我找到了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个条目的数组,每个类一个,每个函数一个。

2 个答案:

答案 0 :(得分:1)

您所需要的基本上是一个解析器,以便您可以选择感兴趣的结构。然后,您可以使用位置信息,例如解析器收集(如果设计得很好),确定文件中文本的边界以提取该结构,或者“解压缩”已解析结构的AST以获取工件。

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

您可能在选择所需的确切功能时遇到一些麻烦。想象一下,你有一个带有两个类C1和C2的文件,每个类都包含一个名为M.的方法。现在要选择“正确的方法”,你需要有完整的路径C1 :: M可用,你需要检查方法M在正确的C1类中找到。您可以通过从M向上走分析树来执行此操作。如果您有特征,这可能会变得更难,因为方法M可能在特征中定义,然后集成到类定义中。要做到这一点真的,你需要PHP的名称解析。

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

答案 1 :(得分: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);