我正在处理一个对象,以允许我们修改包含PHP对象的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
问题是对象需要缓存在某个地方。当我再次运行 $this->deleteMethod("anotherBadMethod");
函数时,它不再返回要删除的下一个方法的正确开始/结束行。经过一些检查,很明显正在发生的事情是,当我尝试获取要删除的下一个方法的开始/结束行时,PHP 仍在使用 OLD 类定义。它似乎没有"看到"某些代码已从文件中删除,并且行号已更改。如您所见,每次我们运行loadClass()
时,我都会实例化对象和反射对象。是的,我尝试在实例化它们之前将它们设置为 NULL。
我还验证了 PHP 是否正确地看到了类定义文件。这意味着即使包含该文件,反射getFileName();
也会看到类已定义在应有的位置。
所以。。。。PHP 是否缓存了我包含在内存中的类的定义?如果是这样,我该如何冲刷现金?有没有办法"取消定义"该类并重新加载它?任何帮助将不胜感激。
这不可能按照你说的方式。PHP 只能加载一次类定义。加载并在内存中后,"刷新"它的唯一方法是终止脚本并重新执行它。如果您尝试重新包含该文件,显然会得到">类已定义错误"。无论您的脚本运行多长时间,一旦类定义在内存中,就无法在其中更改任何内容(除非您使用第三方扩展(。
反射 API 适用于内存中的类定义,而不是磁盘上的类定义。
我认为您从这里有三种选择:
- 使用磁盘上的类定义。这意味着您不能使用反射,而必须改用分词器/解析器。
- 篡改文件,以便在另一个命名空间中重新加载类。这将极大地污染您的全局命名空间,因为一旦定义了它们,就无法卸载它们。
- 执行在单独的进程中加载/修改类的脚本。当进程开始时,它将加载类定义,并在终止时忘记所有内容,从而产生您正在寻找的"刷新"效果。这显然是你最好的选择。
您的 loadClass()
方法将类文件内容加载到数组中,但它不是将类定义加载到 PHP 解释器中的原因。看起来您对class_exists()
的调用正在触发自动加载器,这就是实际将类加载到 PHP 中的原因。
据我所知,不可能"重新加载"已经在脚本中加载的类定义。尝试这样做会使脚本崩溃并出现致命错误。
我认为您有两种选择:
- 编辑并保存类文件后,让脚本重定向到自身,然后退出。这将重新启动脚本并加载新编辑的类版本。重定向时,最初 POST 到脚本的任何参数都必须进入查询字符串。
- 使用 runkit(PECL 扩展(从类中删除该方法,而无需重新加载任何内容。
Don!我最近遇到了同样的问题。我最终采取的方法是将类的文本加载到字符串文件中,就像您所做的那样。但是创建一个"更改缓冲区"来保存要进行的所有更改。为此,我使用了一个私有类变量数组。 private $del_text_buffer = array(); //Array holding text to delete
在缓冲区中完成要进行的所有更改后,请一次进行所有更改,然后将修改后的文件写回磁盘。此方法的缺点是您必须在进行更改后手动保存对象文件,而不是在每次更改后自动保存。但好的一面是它有效。
所以,这是新的loadClass()
函数。如您所见,它有所简化。我们不再调用方法来填充"class_text"和"class_array"属性。这是因为现在它们只在实例化对象时填充一次。它们实质上成为代码的只读引用副本。更改将排队并在一次运行中进行。
我还添加了一个名为 $array_extract 的函数,它提取指定的数组元素块,并将其作为字符串返回。 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);
}
}
例如,现在要删除一个方法,您可以使用反射来查找它的开始行和结束行,将这些行传递给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;
}
}
我今天也遇到了这个问题,我偶然发现了一段时间,直到我遇到这个线程,因此认为这是不可能的。 但是,有一个很好的解决方法:只需按照定义它们的相反顺序编辑文件中的函数/方法。 这样,开始/结束行始终正确。 你只需要很好地记录这一点,以便将来检查你的代码的人。