За последние 24 часа нас посетили 18600 программистов и 1735 роботов. Сейчас ищут 989 программистов ...

Деструкторы и exit

Тема в разделе "Прочие вопросы по PHP", создана пользователем botbot, 7 янв 2010.

  1. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    В общем долго искал баг в коде, а когда нашёл захотел узнать - это мой косяк или php.
    Итак, вот тестовый пример:
    PHP:
    1. <?php
    2. class a1 {
    3.     var $b;
    4.     function __construct($b) {
    5.         $this->b = $b;
    6.     }
    7.     function __destruct() {
    8.         echo ' a1 ';
    9.     }
    10. }
    11. class a2 {
    12.     var $b;
    13.     function __construct($b) {
    14.         $this->b = $b;
    15.     }
    16.     function __destruct() {
    17.         echo ' a2 ';
    18.     }
    19. }
    20. class b1 {
    21.     var $b;
    22.     function __construct($b) {
    23.         $this->b = $b;
    24.     }
    25.     function __destruct() {
    26.         echo " b1:{$this->b} ";
    27.         $this->b++;
    28.     }
    29. }
    30. $b1 = new b1(1);
    31. $a2 = new a2($b1);
    32. test($b1);
    33. function test($b) {
    34.     $a1 = new a1($b);
    35. }
    36. ?>
    Он работает как и положено, на выходе будет
    А теперь добавим в test() в конце слово exit:
    PHP:
    1. <?php
    2. class a1 {
    3.     var $b;
    4.     function __construct($b) {
    5.         $this->b = $b;
    6.     }
    7.     function __destruct() {
    8.         echo ' a1 ';
    9.     }
    10. }
    11. class a2 {
    12.     var $b;
    13.     function __construct($b) {
    14.         $this->b = $b;
    15.     }
    16.     function __destruct() {
    17.         echo ' a2 ';
    18.     }
    19. }
    20. class b1 {
    21.     var $b;
    22.     function __construct($b) {
    23.         $this->b = $b;
    24.     }
    25.     function __destruct() {
    26.         echo " b1:{$this->b} ";
    27.         $this->b++;
    28.     }
    29. }
    30. $b1 = new b1(1);
    31. $a2 = new a2($b1);
    32. test($b1);
    33. function test($b) {
    34.     $a1 = new a1($b);
    35.     exit();
    36. }
    37. ?>
    И тем самым, порядок вызова деструкторов сразу нарушится, вывод станет таким:
    Код (Text):
    1. a2 b1:1 a1
    И получается, что в a1 в деструкторе уже нельзя пользоваться объектом b1, т.к. он уже уничтожен.
    У меня вопрос - кто-нибудь может логично объяснить, зачем так сделано?
     
  2. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    botbot
    ты точно бот. Во-первых воспользуйся и посмотри будет ли он уничтожен, а не говори что мол уже нельзя.
     
  3. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    У меня в b1 находится обёртка mysqli, в деструкторе она вызывает mysqli::close(). А объект a1 пишет в базу свои поля при разрушении через обёртку b1. И т.к. b1 уже уничтожен, то a1 получает сообщение что коннекта к базе нету. Единственный выход - придётся в деструкторе a1 опять коннектится к базе, что влечёт за собой лишние тормоза, которые мне ни к чему. Но кроме моего конкретного случая может быть ещё масса других, когда неправильный вызов деструкторов может повлечь серъёзные проблемы. Поэтому я и хочу знать, зачем так сделали.
     
  4. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    в чем неправильность вызова?
     
  5. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Что бы деструкторы правильно вызывались, тебе нужно строить иерархию объектов и начинать уничтожение с праильного объекта. У меня система каждый созданный объект запихивает в массив в нужном порядке и при окончании работы я запускаю уничтожение в нужном мне порядке, таким образом гарантируя то, что всё уничтожится, сессии сохранятся и.т.д. Могу вечером вырезать ту часть системы, которая за это отвечает и показать. Скажу честно - я мучался 2 или 3 дня пока отладил это дело.
     
  6. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    не надо там особо мучаться.

    Достаточно унаследовать модели работающие с БД от DBAL.
    Тогда он будет убиваться самым последним.

    Upd: гоню немного. объект не уничтожается пока есть ссылки на него.
    Но вот деструктор таки вызывается при уничтожении ссылки!
     
  7. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Simpliest
    Вот именно по этому и важно правильная последовательность создания и убиения объектов :)
     
  8. dependency injection решает эти проблемы автоматически. Просто потому что при правильном использовании DI вы не сможете уничтожить обьекты в неправильной последовательности )))
     
  9. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
  10. необратил, мой косяк.

    С одной стороны как бы у него для коректного ооп некорретный пример - нельзя похерить скрипт в середке, и ждать что все будет хорошо.
    С другой стороны, его вопрос хорош и интересен. Пойду порою тоже маленько.
     
  11. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    флоппик
    а, что смотреть то, держать ссылки надо и всё. Это правило такое, если они могут потеряться произвольным образом, как бы.
     
  12. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    Где-то видел что объекты уничтожаются в порядке, обратном порядку их создания. Но сейчас в документации нашёл:
    Т.е. деструкторы могут быть вызваны в произвольном порядке и это очень плохо.
    Хороший вариант, но serialize+md5 меня смущает, думаю это тоже не быстро. Может просто
    PHP:
    1.  
    2.     public function registerCallee($obj) {
    3.         if(!in_array(&$obj, self::$_backReference) {
    4.             self::$_backReference[] = &$obj;
    5.         }
    6.     }
    7.  
    8.     public function  __destruct() {
    9.         foreach(self::$_backReference as &$obj) {
    10.             echo 'auto destruct ' . $obj->name . '<br>';
    11.             unset($obj);
    12.             echo '<hr>';
    13.         }
    14.         echo 'process was safely shutdown<br>';
    15.     }
    Или есть какие-то косяки?
    И собственно ещё вопрос: в чём различие между &$obj и $obj? Я как ни пробовал, в 5.2 и 5.3 версиях объект всегда по ссылке передаётся, если явно не клонировать.
     
  13. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Не думай насколько это быстро.
    Сколько у тебя таких объектов будет? 4? 40? 400?
    Пиши приложение и потом делай профайлинг.

    Ключ я сделал для того чтобы объект мог сам себя разрегистрировать при собственном уничтожении.
    А проблема в том, что я не сумел поймать, как убрать объект из глобальной области видимости
    Даже с бубном, - т.е. "&", я получаю 2 ссылки на объект, в глобальной области, и в стеке.

    Возьми и проверь. Я не помню почему от него отказался. Скорее всего он не работал как надо.

    в количестве ссылок.
    debug_zval_dump посмотри.

    Впрочем, можешь убрать из кода ссылки и посмотреть что он тебе выведет.

    Из того что я понял, сначала уничтожаются объекты глобальные. Т.е. те, которые имеют ссылку в основной области видимости скрипта.
    И только потом начинают по-очереди ломаться объекты которые имеют ссылку ТОЛЬКО внутри других объектов.

    Передавая ссылку я не даю запускаться деструктору объекта во время ломки глобальной области.
     
  14. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    Simpliest
    Кстати твой код в конкретно моём примере не работает тоже. Допиши вместо die('Start shutdown<br>'); вот это:
    PHP:
    1.  
    2. test($db);
    3. function test($db) {
    4.     $u31 = new user($db);
    5.     $u31->name = 'u31';
    6.     die('Start shutdown<br>');
    7. }
    8.  
    Вывод будет очень интересным:
    Код (Text):
    1. Start shutdown
    2. auto destruct u1
    3. c9b4be523e0fa6bc4d1d77eca248a43e unregistered
    4. destructed u1 = c9b4be523e0fa6bc4d1d77eca248a43e
    5. auto destruct u12
    6. 712831a9531ee9970b5df444addcaed4 unregistered
    7. destructed u12 = 712831a9531ee9970b5df444addcaed4
    8. auto destruct u21
    9. db5fd02c389300d5557ccf62d4ebfbcc unregistered
    10. destructed u21 = db5fd02c389300d5557ccf62d4ebfbcc
    11. auto destruct u22
    12. cca340d373debe55f1ee3efbbe53db58 unregistered
    13. destructed u22 = cca340d373debe55f1ee3efbbe53db58
    14. auto destruct u31
    15. a28ae9a5da567abd1156d7ee9b21306c unregistered
    16. process was safely shutdown
    17. process was safely shutdown
    18. destructed u31 = a28ae9a5da567abd1156d7ee9b21306c
    Я понял что надо делать. Надо свои собственные функции очистки писать (не деструкторы - это важно) и регистрировать их, а потом вызывать по цепочке перед exit(). Это единственный разумный способ. И весь огород приходится городить только потому, что php разрабы не могли чётко стандартизировать порядок вызова деструкторов.
    Щас пишу этот код, как будет готов покажу, хотя там ничего сложного.
    PS. До этого уже пробовал register_shutdown_function - но она вызывается после уничтожения части объектов. И ещё пробовал самому вызывать деструктор, но деструктор тогда при уничтожении объекта ещё повторно вызывается и это может плохо кончиться в ряде случаев.
     
  15. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Мда, я знаю почему это происходит, но не знаю пока как побороть для автоуничтожения.
     
  16. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    Simpliest
    Я тоже понимаю, почему не пашет. Думаю мой огород будет проще, чем твой огород
    PHP:
    1. <?php
    2. class destruct_stack {
    3.     protected static $_stack = array();
    4.     public function add_obj($obj) {
    5.         if(!in_array($obj, self::$_stack)) {
    6.             self::$_stack[] = &$obj;
    7.         }
    8.     }
    9.     public static function exiter() {
    10.         foreach(self::$_stack as $obj) {
    11.             $obj->destruct();
    12.         }
    13.         echo 'process was safely shutdown<br>';
    14.         exit;
    15.     }
    16. }
    17.  
    18. abstract class destruct_control {
    19.     function register() {
    20.         destruct_stack::add_obj($this);
    21.     }
    22.     abstract public function destruct();
    23. }
    24.  
    25. class a1 extends destruct_control {
    26.     var $b;
    27.     function __construct($b) {
    28.         $this->b = $b;
    29.         $this->register();
    30.     }
    31.     public function destruct() {
    32.         echo ' a1 ';
    33.     }
    34. }
    35.  
    36. class a2 extends destruct_control {
    37.     var $b;
    38.     function __construct($b) {
    39.         $this->b = $b;
    40.         $this->register();
    41.     }
    42.     public function destruct() {
    43.         echo ' a2 ';
    44.     }
    45. }
    46.  
    47. class b1 {
    48.     var $b;
    49.     var $n = 'b1';
    50.     function __construct($b) {
    51.         $this->b = $b;
    52.     }
    53.     function __destruct() {
    54.         echo " b1:{$this->b} ";
    55.         $this->b++;
    56.     }
    57. }
    58. $b1 = new b1(1);
    59. $a2 = new a2($b1);
    60. test($b1);
    61. function test($b) {
    62.     $a1 = new a1($b);
    63.     destruct_stack::exiter();
    64. }
    65. ?>
    Работает правильно, правда не деструктит, зато чистить может.
    Минусы, которые вижу:
    1) Менять везде exit() на destruct_stack::exiter();
    2) Не будет работать со сторонними классами, придётся делать для них обёртку или наследовать их и добавлять метод destruct().
     
  17. а если повесить destruct_stack::exiter(); как register_shutdown_function() ?
     
  18. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Еще кузябельнее вот это

    PHP:
    1. <?php
    2. echo '<h2>first instance</h2><br>';
    3. $u1 = new user($db);
    4. $u1->name = 'u1';
    5. echo '<h2>first pass</h2><br>';
    6. $u12 = new user($db);
    7. $u12->name = 'u12';
    8. echo '<h2>second pass</h2><br>';
    9. $db1 = DB::getInstance();
    10. echo '<h2>another instance</h2><br>';
    11. $u21 = new user2($db);
    12. $u21->name = 'u21';
    13. echo '<h2>third pass</h2><br>';
    14.  
    15. $u22 = new user2($db1);
    16. $u22->name = 'u22';
    17. echo '<h2>fourth pass</h2><br>';


     
  19. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Выводы. В топку подсчет ссылок. Я этим наелся еще в C.

    Именно это и надо сделать. Просто shutdown каждого класса делать не в автоматическом деструкторе как у меня, а в отдельном методе у этих классов, как у botbot
     
  20. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    А вот это очень хитрый баг будет. register_shutdown_function() вызывается после уничтожения части объектов. Что может получиться:
    мы делаем db - базу данных
    мы делаем o1 - наш объект, который что-то схораняет в db, и передаём в конструкторе для o1 наш db
    мы регим o1 для очистки.
    вызываем exit
    php убивает db
    потом он вызывает обработчик выхода, который мы зарегили в register_shutdown_function().
    o1 пытается сохраниться в уже несуществующий db.
    В общем налицо та же самая проблема непредсказуемости порядка вызовов, только в извращёной форме.
     
  21. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Не делай в нем close.
    пусть его закрое сам PHP.
     
  22. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    И кстати. У меня он всегда вызывается первым.

    Ты можешь дать кейс как его вызвать в середине процесса выхода?
     
  23. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    Хех, а где гарантии, что не вызовется деструктор mysqli и в нём не закроется соединение?)
     
  24. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    У тебя он относится к объекту порождаемому в основной области видимости скрипта?

    Пока все наши тесты только подтверждают это мое предположение.
     
  25. botbot

    botbot Активный пользователь

    С нами с:
    7 янв 2010
    Сообщения:
    12
    Симпатии:
    0
    Я боюсь надеяться на предположения, это ведёт к танцам с бубнами, извини если обидел :D.
    Насчёт register_shutdown_function(), щас попробую по памяти код заново написать. Пробовал уже, у меня 1 объект до этой ф-ции уничтожиться успел.