2011-06-02 96 views
3

我正在研究一个对象以允许我们修改包含PHP对象的PHP文件。 (具体而言,它们是我们必须修改的Doctrine实体文件。)PHP对象定义缓存?使用反射删除方法时遇到问题

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

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 

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

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

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

+0

我不明白你为什么使用反射而不是真实物体 – dynamic 2011-06-02 20:44:26

回答

4

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

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

我认为你从这里三个选项:

与磁盘上的类定义
  • 工作。这意味着你不能使用反射,而必须使用标记器/解析器。
  • 篡改该文件,以便该类重新加载到另一个名称空间中。这将大大地污染你的全局命名空间,因为一旦它们被定义,它们就不能被卸载。
  • 执行在单独的进程中加载​​/修改类的脚本。当这个过程开始时,它会加载类定义,并在它终止时忘记它,产生你正在寻找的“刷新”效果。这显然是你最好的选择。
+1

正如你所提到的,根本问题似乎是反射在内存中的对象副本上工作,而不是在磁盘上。所以我改变了我使用的方法。我没有做出改变并将其保存到磁盘,而是将所有更改排队,一次完成所有更改,并将文件写入磁盘一次。 – 2011-06-06 16:00:22

0

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

据我所知,无法“重新载入”已经加载到脚本中的类定义。试图这样做会导致您的脚本崩溃致命错误。

我认为你有两个选择:

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

+0

回答你自己的问题是可以的。提到你在第三人中是......哼哼......尴尬。 – Tivie 2013-05-13 00:38:53

0

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