unset не уничтожает класс полностью, если он передан куда либо по ссылке

Gas

может по одной?
а зачем вообще этого хотеть в приложениях, которые должны отрабатывать за десятые/сотые доли секунды, где по окончании освобождаются все ресурсы и вызываются необходимые деструкторы.
Если вопрос в контексте консольных долгоживущих скриптов, то прежде чем делать - нужно хорошо подумать.
 

whirlwind

TDD infected, paranoid
Забудьте уже о GC до 5.3. Эту проблему языковыми средствами не решить. Единственный вариант уже озвучивали

PHP:
interface IDirtyObject {
   function manualDestruct();
}
implements и вперед развязывать циклические сцылки

Вопрос только один - когда 5.3 в RC ? Ждем уже полгода.
 

berkut

Новичок
whirlwind
топик стартер не про циклические ссылки. он хочет другого, а циклические ссылки просто под руку тоже попались
 

Fally

Новичок
не могу понять, неужели автору топика так тяжело всё ручками очистить, к примеру в деструкторе класса хранящего в себе ссылки а также понять что в PHP5 объекты всегда передаются по ссылке...
 

berkut

Новичок
Fally приведи пример, очень понятливый, как вот так просто с деструктором уничтожаемого, решить 1 пример топикстартера
 

Fally

Новичок
PHP:
class A {
    private $_classB;
    private $_classC;
    private $_classD;

    public function __construct(B $b, C $c, D $d) {
        $this->_classB = $b;
        $this->_classC = $c;
        $this->_classD = $d;
    }
    
    public function doSomething() {
        // делаем что-либо.
    }

    public function __destruct() {
        $this->_classB->__destruct();
        $this->_classC->__destruct();
        $this->_classD->__destruct();
    }
};
$a = new A(new B(), new C(), new D());
$a->doSomething();
$a->__destruct();
Но при таком подходе необходимо наличие деструкторов в каждом из классов объектов используемых в объекте класса В. Также можно попросту забыть вызвать деструктор какого либо из объектов.
Обыкновенный контроль руками.
 

berkut

Новичок
Fally
ё, ну е позорься, а. реально это ты не въезжаешь в суть проблемы, а не топик стартер.
PHP:
class A { 
    private $_classB; 
    private $_classC; 

    public function __construct(B $b, C $c) { 
        $this->_classB = $b; 
        $this->_classC = $c; 
        $this->_classD = $d; 
    }

    public function __destruct() {
        echo "destruct\n"; // и ВСЁ!!!        
    } 
};
class B {};
class C {}; 
$a = new A(new B(), new C()); 
$a->__destruct();
var_dump($a);

echo "script end\n";
к тому-же я говорил о проблеме в первом примере
 

Fally

Новичок
berkut
Возможно ты прав, но тогда прошу разъяснить, почему в деструкторе А, я немогу ничего сделать с данными хранящимися в данном объекте? Если их в null отправить (не вызывать деструкторы) то всё вполне нормально отрабатывает..
 

whirlwind

TDD infected, paranoid
Fally
berkut

По моему вы оба не въехали.

Во-первых не класс создается, а объект (или инстанс).
Во-вторых $a->__destruct() это не деструктор. Это явный вызов метода. Деструктор вызывается автоматически при unset. НО! Деструктор вызывается только когда на объект не остается ссылок.

Смотрим еще раз пример топикстартера и считаем сцылки. Сцылка номер раз это $test_cl. Сцылка номер два это $this->parent_class.

После unset($test_cl) сколько ссылок останется? Правильно, останется сцылко в экземпляре A, так что в атрибуте parent_class. Так как ссылок больше чем 1, то B::__destruct() не вызывается. Для того, что бы он вызывался нужно так же сделать unset($this->parent_class) в классе A. Это можно было бы сделать в деструкторе класса A, но он тоже не вызовется, так как ссылка на A находится в объекте класса B, деструктор которого не может вызваться потому что... и т.д. и т.д. Образуется циклическая зависимость, которую GC в текущей реализации PHP обработать не может. И на самом деле это очень хреново для активно-юзающих ООПешников, потому что сводит на нет якобы "автоматическое" управление памятью.

И это мешает в реальных задачах. Возьмем например паттерн Observer. Так вот автоматическую отписку слушателя сделать нельзя, потому что до конструктора дело не доходит, так как в Observable объекте есть ссылка на этого самого слушателя. Приходится делать очистку вручную. Сами понимаете, что это не то же самое, что вышли из области действия переменной и память освободилась.
 

berkut

Новичок
whirlwind а ты читал мои посты на первой странице? я тоже самое говорил
 

mrsol

Новичок
2Gas
а зачем вообще этого хотеть в приложениях, которые должны отрабатывать за десятые/сотые доли секунды, где по окончании освобождаются все ресурсы и вызываются необходимые деструкторы.
Если вопрос в контексте консольных долгоживущих скриптов, то прежде чем делать - нужно хорошо подумать.
Вот именно я разрабатываю долгоживущие серверные приложения, а не домашние паги. Сразу скажу, почему именно на PHP это разговор другой и к данной теме не относится.

И не всегда даже проблема в цикличности. Может быть даже проблема в невидимости ссылки данного объекта из данного объекта. И тут уже ручной деструктор и суперобнул не помогут из данного объекта.
Вот пример.
PHP:
<?php
  class B{
    private $just_a_var;
    
    public function test($var){
        $var = strrev($var);
        $global_c->just_a_var = $var;
        //echo "Test var='".$var."' \n";
        return $var;
    }
    
    public function __construct($name){
        $this->name = $name;
        echo "Construct Class B name='".$this->name."'\n";
    }
    
    public function __destruct(){
        echo "Desctruct class B name='".$this->name."'\n";
    }
  }
  
  class D{
    public $s_class;
    
    public function add_class(&$class){
        
        $this->s_class = &$class;
    }
    
    public function test($var){
        
        $var = strtoupper($var);
        return $this->s_class->test($var);
    }
    
    public function __construct($name){
        $this->name = $name;
        echo "Construct Class D name='".$this->name."'\n";
    }
    
    public function __destruct(){
        echo "Desctruct class D name='".$this->name."'\n";
    }
  }
  
  
  echo "construc class B\n";
  $test_cl = new B('name_one');
  echo "construc class D\n";
  $test_D = new D('name_two');
  echo "add in class D class B\n";
  $test_D->add_class(&$test_cl);
  echo "run func from D\n";
  $ret = $test_D->test('abc');
  echo "return ret='".$ret."' \n";
  echo "Try unset class B\n";
  unset($test_cl);
  echo "Try unset class D\n";
  unset($test_D);
  echo "End Programm\n";
  
?>
Любая функция обнуления или ещё чего-нибудь в классе B никак не сможет добраться до $test_D, так-как находится не в пределах видимости данного объекта.
Ещё скажу что это пример НЕ хорошего программирование. Но чтобы показать данную проблему, пришлось написать так.

Кроме того, не только объект подвисает в памяти.
Допустим, объект на протяжении своего существования должен каждый тик вызывать какую-то функцию. После уничтожения, не должен её вызвать.
Вот пример
PHP:
<?php
  class B{
    private $child_class;
    public $name;
    private $memory;
    
    public function add_children(&$class, $counter){
        
        $t_counter = $counter;
        echo "Add child class counter='".$counter."' \n";
        if($counter<=0){
            echo "Exit by counter\n";
            return true;
        }
        $this->child_class = &$class;
        $counter--;
        $this->child_class->add_children($class, $counter);
        echo "End add child class couner='".$t_counter."' \n";
    }
    
    public function __construct($name, $count_child = 5){
        $this->name = $name;
        echo "Construct Class B name='".$this->name."'\n";
        $this->add_children(&$this, $count_child);
        register_tick_function('eche_ticks', $this->name);
        declare(ticks=1);
    }
    
    public function __destruct(){
        echo "Desctruct class B name='".$this->name."'\n";
        unregister_tick_function('eche_ticks');
    }
  }
  
  function eche_ticks($name){
    ///to do somthing at each ticks
    echo "---------------Ticks name='".$name."' \n";
  }
  
  declare(ticks=1);
  echo "Init \n";
  $test_cl = new B('name_one', 1);
  echo "Try unset\n";
  unset($test_cl);
  echo "Now variable is='".var_export($test_cl, true)."' \n";
  
  echo "Init \n";
  $test_cl = new B('name_two', 1);
  echo "Try NULL\n";
  $test_cl = NULL;
  echo "Now variable is='".var_export($test_cl, true)."' \n";
  
  for($x=1; $x<=3; $x++){
    echo "x='".$x."'\n";
  }
  echo "end script\n";
  
?>
Так как в объекте есть цикл, то он не будет уничтожен. и на момент выполнения for будет выполнятся тики от двух объектов. А должно быть не одного тика.

Далее
Типа сделать метод который бы обходил все переменные и смотрел объекты это или нет и уничтожал их, но при этом нужно ещё зайти в этот подобъект и в нём уничтожить всё по такому же принципу. Но это не получится.
Во первых, я уже описал выше, что ссылка может быть не в пределах видимости уничтожаемого объекта.
Во вторых представим ситуацию, когда есть объект которые в себе хранит ссылки на другие объекты и при этом общий для всех этих объектов. И при этом есть обратная ссылка на общий объект в данных объекта. И по этому правилу нудно уничтожить и его, во первых не получится, во вторых не нужно.
Вроде сам понимаю что написал :D

-~{}~ 18.02.08 20:10:

Ну чё пришлось свой каунтер ссылок написать. Правда сыровать, но вроде работает со всеми примерами. :)

PHP:
<?php
  
  
  class obj_links_works_cl{
    
    public $all_class = array();
    public $all_links = array();
    
    public function links(&$where, $parametr, &$who){
        
        $id_where = $this->search_id_obj_in_class(&$where);
        if($id_where===false){
            $this->add_obj_in_class(&$where);
            $id_where = $this->search_id_obj_in_class(&$where);
        }
        
        $id_who = $this->search_id_obj_in_class(&$who);
        if($id_who===false){
            $this->add_obj_in_class(&$who);
            $id_who = $this->search_id_obj_in_class(&$who);
        }
        
        if(empty($this->all_links[$id_who])){
            $this->all_links[$id_who] = array();
        }
        
        //echo "id_where='".$id_where."' id_who='".$id_who."' \n";
        if($id_where!==false and $id_who!==false){
            $info['id_where'] = $id_where;
            $info['parametr'] = $parametr;
            $this->all_links[$id_who][] = $info;
            /*echo "now all_links\n";
            var_export($this->all_links);
            echo "\n";*/
            //return $who;
        }else{
            //SOMTHING wrong with add object
        }
    }
    
    public function unset_all_links(&$object){
        
        $id_obj = $this->search_id_obj_in_class(&$object); 
        if($id_obj!==false){
            //echo "found id to unlinks='".$id_obj."' \n";
            if(!empty($this->all_links[$id_obj])){
                $all_links_obj = $this->all_links[$id_obj];
                if(is_array($all_links_obj) or is_object($all_links_obj)){
                    krsort($all_links_obj);
                    reset($all_links_obj);
                    while (list($key, $val) = each($all_links_obj)){
                        //echo "id_key='".$key."' param='".$val['parametr']."' \n";
                        //echo "try unset='".$val['id_where']."' \n";
                        $temp_obj = &$this->all_class[$val['id_where']];
                        unset($temp_obj->$val['parametr']);
                        unset($temp_obj);
                    }
                }
            }
            //echo "remove from global all_links\n";
            unset($this->all_links[$id_obj]);
            //echo "remove from global all_class\n";
            unset($this->all_class[$id_obj]);
        }else{
            //object not found in array
        }
        
    }
    
    public function search_id_obj_in_class(&$object){
        
        $ret = false;
        /*$all_key = array_keys($this->all_class, $object);
        for($x=0; $x<count($all_key); $x++){
            if($this->all_class[$all_key[$x]]===$object){
                $ret = $all_key[$x];
                break;
            }
        }*/
        if(is_array($this->all_class) or is_object($this->all_class)){
            reset($this->all_class);
            while (list($key, $val) = each($this->all_class)){
                if($val===$object){
                    $ret = $key;
                    break;
                }
            }
        }
        
        return $ret;
    }
    
    public function add_obj_in_class(&$object){
        
        //echo "add_one_object\n";
        $this->all_class[] = &$object;
    }
    
  }
  
  $obj_links_works = new obj_links_works_cl();
  
  function &add_links_obj(&$where, $paramter, &$who){
    global $obj_links_works;
    
    $obj_links_works->links(&$where, $paramter, &$who);
    return $who;
  }
  
  function unset_all_links_obj(&$who){
    global $obj_links_works;
    
    $obj_links_works->unset_all_links(&$who);
  }
  
?>

Как это работает.
Вместо
$this->child_class = &$class;
Указываем
$this->child_class = &add_links_obj(&$this, 'child_class', &$class);

Вместо
$global_c->temp_class = &$this;
Указваем
$global_c->temp_class = &add_links_obj(&$global_c, 'temp_class', &$this);

И обязательное условие, чтобы переменная в объекте которой присваевается ссылка (child_class, temp_class) была паблик.
И т.д.

А когда нужно уничтожить, то делаем так.
unset_all_links_obj(&$test_cl);
unset($test_cl);
И деструктор срабатывает нормально.


Кто, что может сказать по этому поводу? Может чё-то пропустил, какие-то варианты. А то понедельник день тяжёлый.
 

berkut

Новичок
$this->child_class = &add_links_obj(&$this
все эти амперсанды вообще не нужны. а add_links_obj(&$this
генерит E_STRICT

-~{}~ 18.02.08 21:43:

вообще, реальная проблема только с циклическими ссылками. а то, что нужно добираться до всех ссылок на объект - так это мне кажется не насущная проблема, а косяк в проектировании
 

mrsol

Новичок
Автор оригинала: berkut
$this->child_class = &add_links_obj(&$this
все эти амперсанды вообще не нужны. а add_links_obj(&$this
генерит E_STRICT

-~{}~ 18.02.08 21:43:

вообще, реальная проблема только с циклическими ссылками. а то, что нужно добираться до всех ссылок на объект - так это мне кажется не насущная проблема, а косяк в проектировании
Может апсерданты и не нужны, а вот E_STRICT у меня как не хотел, так и не получился. Можно пример.
 
Сверху