Задать вопрос
@IvanN777

Утечка памяти при парсинге SAX методом(XML), что я зделал не так?

class XmlSaxClass {
    
   private $file_path;
   private $encoding;
   
   private $output   = array();
   private $element   = null;
   
   private $full_path_elements = array();
   private $necessary_elements = array();
   private $branch_elements = array();
   private $deep;
   
   public function __construct($file_path, $encoding = 'UTF-8'){
      $this -> encoding = $encoding;
      $this-> file_path = $file_path;
   }
   
   public function execute($necessary_elements, $full_path_elements){

        $this -> necessary_elements = $necessary_elements;
        $this -> full_path_elements = $full_path_elements;
       
        $parser = xml_parser_create($this -> encoding); 
        xml_set_object($parser, $this);
        xml_set_element_handler($parser, 'startElements', 'endElements');
        xml_set_character_data_handler($parser, "characterData");
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
        
        $handle = fopen($this-> file_path, "r");
        $base_memory_usage = memory_get_usage();
        while ($data = fread($handle, 4096)) {
           xml_parse($parser, $data, feof($handle));
           echo("<br>");
           print(memory_get_usage() - $base_memory_usage);
        }
        xml_parser_free($parser);
        
        return $this -> output;
   }
   
   private function startElements($parser, $name, $attrs){
      $this -> deep++;
       
      if(!empty($name)) {
            if($this -> deep == 1){
                if ($name == $this -> full_path_elements[0]){
                    $this -> branch_elements[0] = $name;
                }
            }
            else{
                $this -> cut_elements_in_branch_if_down();
                $this -> push_element_to_branch($name);
            }
            
            $branch_elements_count = count($this -> branch_elements);
            
            if($branch_elements_count == count($this -> full_path_elements)){
                if($this -> deep == $branch_elements_count){
                    $this -> output[] = array();
                }
                elseif (in_array($name, $this -> necessary_elements)){
                    $this -> element = $name;
                }
            }
      }
   }
   
   private function endElements($parser, $name){
      $this -> deep --;
      if(!empty($name)) {
         $this -> element = null;
      }
   }
   
   private function characterData($parser, $data){
      if(!empty($data)) {
         if (in_array($this -> element, $this -> necessary_elements)) {
            $this ->output[count($this -> output)-1][$this -> element] = trim($data);
         }
      }
   }
   
   private function cut_elements_in_branch_if_down(){
       if (count($this -> branch_elements) > $this -> deep -1 ){
           $this -> branch_elements = array_slice($this -> branch_elements, 0,  $this -> deep -1); 
       }
   }
   
   private function push_element_to_branch($name){
       if (count($this -> branch_elements) == (($this -> deep)-1)){
           if (isset($this -> full_path_elements[$this -> deep-1] ) && 
                   $name == ($this -> full_path_elements[($this -> deep)-1])){
               $this -> branch_elements[($this -> deep)-1] = $name;
           }
       }
   }

}


Здесь небольшой простой скрипт. В методе execute происходит разбор xml документа и беруться листья текущей ветки.
Первый параметр список атрибутов, второй это полный путь к нужной ветке xml.
$sax_parser = new \XmlSaxClass($file_path);
    $response = $sax_parser->execute(array(
        'learner',
        'teacher'
    ), array(
        'school',
        'class'
    ));

Поидее я написал методом SAX, но происходит утечка памяти и 1г обработать не может.
Возможно я неправильно понимаю метод SAX?
В теории он разбирается частями не загружая все дерево DOM.
Но видимо я что-то зделал не так.
Скрипт нормально работает на маленьких значениях, на больших валится.
Все echo удаляю конечно, здесь только для примера потери памяти.
Вот очень простой xml
<?xml version="1.0" encoding="UTF-8"?>
<school>
    <class>
        <learner>Ученик1</learner>
    </class>

    <class>
        <learner>Ученик2</learner>
    </class>

    <class>
        <learner>Ученик3</learner>
       <teacher>Училка</teacher>
    </class>
</school>
  • Вопрос задан
  • 146 просмотров
Подписаться 1 Оценить Комментировать
Решения вопроса 1
onqu
@onqu
weasy
Судя по всему, массив $output разрастается очень сильно, памяти не хватает. Решить можно разными путями:

1. сделать цикл в методе execute с yield если php >= 5.5, но нужно будет следить за консистентностью массива $output, чтобы удалять отданные данные и дописывать новые
2. обернуть в ArrayIterator, но также нужно будет писать другой код
3. передавать в execute callback функцию, которая будет вызываться в методе endElements и если $name будет равен 'class', то есть при закрытии обертки

private function endElements($parser, $name){
        $this -> deep --;
        if(!empty($name)) {
            $this -> element = null;
        }

        // $this->wrapper == 'class'
        if ($name === $this->wrapper && !empty($this->output)) {
                // отдать данные в коллбэк
                call_my_callback($this->output);
                // очистить буфер
                $this->output = [];
        }
    }


ps. может возникнуть ошибка в методе characterData, в этот метод данные могут быть дописаны, например:
fread вернул данные, которые оканчиваются на "<learner>Уче", как итог в данные парсером запишется "Уче", при следующем вызове в метод прилетит "ник1", и перезапишет "Уче".

нужно делать что-то типа:

private function characterData($parser, $data){
        if(!empty($data)) {
            if (in_array($this->element, $this->necessary_elements)) {
                if (!isset($this->output[$this->element])) {
                    $this->output[$this->element] = '';
                }
                $this->output[$this->element] .= trim($data);
            }
        }
    }
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы