Кэшированные определения объектов PHP?Методы удаления проблем с отражением - PullRequest
3 голосов
/ 03 июня 2011

Я работаю над объектом, чтобы позволить нам изменять файлы 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 ]

4 голосов
/ 03 июня 2011

Это не так, как вы выразились. PHP может загрузить определение класса только один раз. После того, как оно загружено и находится в памяти, единственный способ «обновить» - это завершить сценарий и повторно выполнить его.Если вы попытаетесь повторно включить файл, вы, очевидно, получите « уже определенная ошибка класса ».Независимо от того, как долго выполняется ваш скрипт, когда определение класса находится в памяти, вы ничего не можете в нем изменить (если не используете стороннее расширение).

Reflection API работает с определением класса в памятиа не тот, что на диске.

Я думаю, что у вас есть три варианта:

  • Работа с определением класса на диске. Это означает, что вы не можетеиспользуйте отражение, но вместо этого необходимо использовать токенизатор / анализатор.
  • Подделать файл так, чтобы класс был перезагружен в другом пространстве имен. Это сильно загрязнит ваше глобальное пространство имен многими другими способами.- как только пространства имен, поскольку после определения они не могут быть выгружены.
  • Выполнить сценарий, который загружает / изменяет класс в отдельном процессе. Когда процесс запускается, он загружаетсяопределение класса и забудет все о нем, когда оно завершится, создавая эффект «обновления», который вы ищете.Это, очевидно, ваша лучшая ставка.
0 голосов
/ 26 октября 2016

У меня тоже была эта проблема сегодня, и я какое-то время спотыкался о ней, пока не наткнулся на эту тему и, следовательно, не счел ее невозможной. Однако есть хороший обходной путь: просто отредактируйте функции / методы в вашем файле в порядке, обратном тому, в котором они были определены. Таким образом, строки начала / конца всегда корректны. Вам просто нужно хорошо документировать это для будущих людей, которые изучат ваш код.

0 голосов
/ 06 июня 2011

Дон!У меня была такая же проблема недавно.Подход, который я в итоге выбрал, заключался в загрузке текста класса в строковый файл, как вы уже сделали.Но создайте «буфер изменений» для хранения всех изменений, которые вы хотите внести.Для этого я использовал массив переменных закрытого класса.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.Но скорее он находит текст, который будет удален, и помещает его в массив для последующей обработки.

Изменения фактически обрабатываются при сохранении объекта обратно на диск, как вы можете видеть в * 1016.* функция.Эта функция должна вызываться только один раз в конце обработки.Я думаю о том, чтобы поместить это в деструктор.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 голосов
/ 03 июня 2011

Ваш метод loadClass() загружает содержимое файла класса в массив, но это не то, что загружает определение класса в интерпретатор PHP. Похоже, ваш вызов class_exists() запускает автозагрузчик, и это то, что фактически загружает класс в PHP.

Насколько я знаю, невозможно "перезагрузить" определение класса, которое уже было загружено в сценарий. Попытка сделать это приведет к сбою вашего скрипта с фатальной ошибкой.

Я думаю, у вас есть два варианта:

  1. После редактирования и сохранения файла класса ваш скрипт будет перенаправлен на себя, а затем завершится. Это снова запустит скрипт и загрузит только что отредактированную версию вашего класса. Любые параметры, которые были изначально вставлены в ваш скрипт, должны будут указываться в строке запроса при перенаправлении.
  2. Используйте runkit (расширение PECL), чтобы удалить метод из класса без перезагрузки.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...