PHP对象定义是否已缓存?无法用反射删除方法

时间:2011-06-02 20:41:26

标签: php reflection

我正在处理一个对象,允许我们修改包含PHP对象的PHP文件。 (具体来说,它们是我们必须修改的Doctrine实体文件。)

无论如何,这里没有无聊的细节就是正在发生的事情。我首先找到类文件的位置,并包含它。然后,我使用下面的代码创建类的实例和类反射器。如您所见,当我实例化对象和反射器时,我还调用一个方法将类的文本从磁盘加载到一个字符串中,另一种方法是将该字符串按行分解为数组。

public function loadClass()
   if(!class_exists($this->class_name)) {
      $this->error = "Class name: '$this->class_name' was not found.";}
   else {
      //Class is found. Load it and populate properties
      $this->oClass          = new $this->class_name;
      $this->oRClass         = new \ReflectionClass($this->oClass);
      $this->source_file     = $this->oRClass->getFileName();
      $this->error           = "";
      $this->class_namespace = $this->oRClass->getNamespaceName();
      $this->getClassText();  //Load class code from source file
      $this->getClassArray(); //Load class text into array
   }
}

然后我使用一个名为“deleteMethod()”的函数来删除特定方法的PHP代码,如下所示:$this->deleteMethod("badMethod");。然后,此函数找到相关方法的起始行和结束行,删除行,将PHP类代码保存回磁盘,再次运行“loadClass()”以重新解析更新的对象,使其成为准备好进行更多编辑。以下是“deleteMethod()”功能的摘录。

$oMethod = $this->oRClass->getMethod($meth_name);       //Get a refection method object 
$oMethod->setAccessible(true);                          //In case method is private
$start_line = $oMethod->getStartLine() -1;              //Line method starts at
$length = $oMethod->getEndLine() - $start_line + 1      //Number of lines in method
array_splice($this->class_array, $start_line, $length); //Hack lines out of array
$this->class_text = implode("\n", $this->class_array);  //Convert array back to a string
$this->saveClassText();                              //Save updated code back to disk.
$this->loadClass();                                     //Reload and reparse for consistancy

问题是该对象在某处缓存。当我再次运行$this->deleteMethod("anotherBadMethod");函数时,它不再为下一个要删除的方法返回正确的开始/结束行。经过一些检查后,显而易见的是,当我尝试获取下一个方法的开始/结束行时,PHP仍在使用OLD类定义。它似乎并没有“看到”某些代码已从文件中删除,并且行号已更改。如您所见,每次运行loadClass()时,我都会实现对象和反射对象。是的,我尝试在实例化之前将它们设置为NULL。

我还验证了PHP正确地看到了类定义文件。这意味着即使文件被包含在内,反射getFileName();确实会看到该类定义在它应该的位置。

Soooo .... PHP是否缓存了我在内存中包含的类的定义?如果是这样,我该如何冲洗现金?有没有办法“取消定义”,那个类,并重新加载它?任何帮助将不胜感激。

4 个答案:

答案 0 :(得分:4)

这不可能像你说的那样。 PHP只能加载一次类定义。加载后并在内存中,“刷新”它的唯一方法是终止脚本并重新执行它。如果您尝试重新包含该文件,您显然会得到“类已定义错误”。无论你的脚本运行多长时间,一旦类定义在内存中,你就无法改变它(除非你使用第三方扩展)。

Reflection API适用于内存中的类定义,而不是磁盘上的类定义。

我认为你有三种选择:

  • 使用磁盘上的类定义。这意味着您不能使用反射,但必须使用标记生成器/解析器。
  • 篡改文件,以便在另一个命名空间中重新加载该类。这将极大地污染您的全局命名空间,其中包含许多其他一次性使用命名空间,因为一旦定义它们,它们就不能被卸下。
  • 执行在单独进程中加载​​/修改类的脚本。当进程启动时,它将加载类定义,并在终止时忘记它的所有内容,产生“刷新”效果你正在寻找。这显然是你最好的选择。

答案 1 :(得分:0)

您的loadClass()方法将类文件内容加载到数组中,但不是将类定义加载到PHP解释器中的方法。看起来您对class_exists()的调用正在触发自动加载器,而这实际上是将类加载到PHP中。

据我所知,不可能“重新加载”已经加载到脚本中的类定义。尝试这样做会使您的脚本崩溃并发生致命错误。

我认为你有两个选择:

  1. 编辑并保存类文件后​​,让脚本重定向到自身,然后退出。这将重新启动脚本并加载新编辑的类版本。最初发布到脚本的任何参数都必须在重定向时进入查询字符串。
  2. 使用runkit(PECL扩展名)从类中删除方法而不重新加载任何内容。

答案 2 :(得分:0)

唐!我最近遇到了同样的问题。我最终采用的方法是将类的文本加载到字符串文件中,就像您所做的那样。但是创建一个“更改缓冲区”来保存您想要进行的所有更改。为此,我使用了私有类变量数组。

private $del_text_buffer   = array();  //Array holding text to delete

一旦完成了要在缓冲区中进行的所有更改,请立即进行所有更改并将修改后的文件写回磁盘。此方法的缺点是您必须在进行更改后手动保存对象文件,而不是在每次更改后自动保存。但好的一面是它有效。

所以,这是新的loadClass()函数。如您所见,它有点简化。我们不再调用填充'class_text'和'class_array'属性的方法。这是因为现在只有在实例化对象时才会填充一次。它们基本上成为代码的只读参考副本。更改将排队等候并在一次运行中完成。

public function loadClass() {
    if(!class_exists($this->class_name)) {
        $this->error = "Class name: '$this->class_name' was not found.";
    } else 
    {//Class is found. Load it and populate properties
        $this->oClass          = new $this->class_name;
        $this->oRClass         = new \ReflectionClass($this->oClass);
        $this->source_file     = $this->oRClass->getFileName();
        $this->error           = "";
        $this->class_namespace = $this->oRClass->getNamespaceName();
        $this->class_text      = file_get_contents($this->source_file);
        $this->class_array     = explode("\n", $this->class_text);
    }
}
我还添加了一个名为$ array_extract的函数,它抽出一个指定的数组元素块,并将其作为字符串返回。
private function array_extract($haystack, $start_index, $end_index){
    $result = false;
    if(is_array($haystack)) {
        $extract = array();
        $n = 0;
        for($i=$start_index; $i<=$end_index; $i++) {
            $extract[$n] = $haystack[$i];
            $n++;
        }
        $extract = implode("\n", $extract);
        $result = $extract;
    }
    return $result;
}
现在要删除一个方法,例如,使用反射来查找它的起始行和结束行,将这些行传递给array_extract().,它将这些行的内容作为字符串返回。然后将该字符串推送到删除缓冲区,该缓冲区包含要进行的所有删除操作。
public function deleteMethod($meth_name = "") {
    $result = false;
    $oMethod = $this->oRClass->getMethod($meth_name);
    //Find where method is located, and hack it out
    $start_index  = $oMethod->getStartLine() -1;
    $end_index    = $oMethod->getEndLine() -1;
    $del_string = $this->array_extract($this->class_array, $start_index, $end_index);
    array_push($this->del_text_buffer, $del_string);
    $this->num_changes++;
    //--- Delete the comment attached to the text, if any
    $comment_txt = $oMethod->getDocComment();
    if($comment_txt != "") {
        array_push($this->del_text_buffer, $comment_txt);
        $this->num_changes++;
    }
}
因此,deleteMethod()实际上并未修改$class_text$class_array变量。但是,它会找到将被删除的文本,并将其推送到数组中以供以后处理。

当对象保存回磁盘时,实际上会处理更改,如saveObject()函数中所示。必须在处理结束时仅调用此函数一次。我想把它放到析构函数中。

public function saveObject() {
    if($this->num_changes) {
        $num_recs = count($this->del_text_buffer);
        for($i=0; $iclass_text = str_replace($this->del_text_buffer[$i], "", $this->class_text, $changes_made);
        }
        $result = file_put_contents($this->source_file, $this->class_text);
        $this->num_changes = 0;
    }
}

答案 3 :(得分:0)

我今天也遇到了这个问题,我偶然发现了一段时间,直到我遇到这个帖子,因此认为这是不可能的。但是,有一个很好的解决方法:只需按照定义它们的相反顺序编辑文件中的函数/方法。这样,开始/结束行总是正确的。您只需要为将来检查代码的人员记录这一点。