В общем долго искал баг в коде, а когда нашёл захотел узнать - это мой косяк или php. Итак, вот тестовый пример: PHP: <?php class a1 { var $b; function __construct($b) { $this->b = $b; } function __destruct() { echo ' a1 '; } } class a2 { var $b; function __construct($b) { $this->b = $b; } function __destruct() { echo ' a2 '; } } class b1 { var $b; function __construct($b) { $this->b = $b; } function __destruct() { echo " b1:{$this->b} "; $this->b++; } } $b1 = new b1(1); $a2 = new a2($b1); test($b1); function test($b) { $a1 = new a1($b); } ?> Он работает как и положено, на выходе будет А теперь добавим в test() в конце слово exit: PHP: <?php class a1 { var $b; function __construct($b) { $this->b = $b; } function __destruct() { echo ' a1 '; } } class a2 { var $b; function __construct($b) { $this->b = $b; } function __destruct() { echo ' a2 '; } } class b1 { var $b; function __construct($b) { $this->b = $b; } function __destruct() { echo " b1:{$this->b} "; $this->b++; } } $b1 = new b1(1); $a2 = new a2($b1); test($b1); function test($b) { $a1 = new a1($b); exit(); } ?> И тем самым, порядок вызова деструкторов сразу нарушится, вывод станет таким: Код (Text): a2 b1:1 a1 И получается, что в a1 в деструкторе уже нельзя пользоваться объектом b1, т.к. он уже уничтожен. У меня вопрос - кто-нибудь может логично объяснить, зачем так сделано?
botbot ты точно бот. Во-первых воспользуйся и посмотри будет ли он уничтожен, а не говори что мол уже нельзя.
У меня в b1 находится обёртка mysqli, в деструкторе она вызывает mysqli::close(). А объект a1 пишет в базу свои поля при разрушении через обёртку b1. И т.к. b1 уже уничтожен, то a1 получает сообщение что коннекта к базе нету. Единственный выход - придётся в деструкторе a1 опять коннектится к базе, что влечёт за собой лишние тормоза, которые мне ни к чему. Но кроме моего конкретного случая может быть ещё масса других, когда неправильный вызов деструкторов может повлечь серъёзные проблемы. Поэтому я и хочу знать, зачем так сделали.
Что бы деструкторы правильно вызывались, тебе нужно строить иерархию объектов и начинать уничтожение с праильного объекта. У меня система каждый созданный объект запихивает в массив в нужном порядке и при окончании работы я запускаю уничтожение в нужном мне порядке, таким образом гарантируя то, что всё уничтожится, сессии сохранятся и.т.д. Могу вечером вырезать ту часть системы, которая за это отвечает и показать. Скажу честно - я мучался 2 или 3 дня пока отладил это дело.
не надо там особо мучаться. Достаточно унаследовать модели работающие с БД от DBAL. Тогда он будет убиваться самым последним. Upd: гоню немного. объект не уничтожается пока есть ссылки на него. Но вот деструктор таки вызывается при уничтожении ссылки!
dependency injection решает эти проблемы автоматически. Просто потому что при правильном использовании DI вы не сможете уничтожить обьекты в неправильной последовательности )))
Ну не все так кузяво с ссылками, как хотелось бы. Одну я так и не поймал Но это вполне безопасный вариант. http://simpliest.co.cc/sample/oop/safeshutdown.php http://simpliest.co.cc/sample/oop/safeshutdown.phps флоппик Если ты обратил внимание, то у ТС, таки DI
необратил, мой косяк. С одной стороны как бы у него для коректного ооп некорретный пример - нельзя похерить скрипт в середке, и ждать что все будет хорошо. С другой стороны, его вопрос хорош и интересен. Пойду порою тоже маленько.
флоппик а, что смотреть то, держать ссылки надо и всё. Это правило такое, если они могут потеряться произвольным образом, как бы.
Где-то видел что объекты уничтожаются в порядке, обратном порядку их создания. Но сейчас в документации нашёл: Т.е. деструкторы могут быть вызваны в произвольном порядке и это очень плохо. Хороший вариант, но serialize+md5 меня смущает, думаю это тоже не быстро. Может просто PHP: public function registerCallee($obj) { if(!in_array(&$obj, self::$_backReference) { self::$_backReference[] = &$obj; } } public function __destruct() { foreach(self::$_backReference as &$obj) { echo 'auto destruct ' . $obj->name . '<br>'; unset($obj); echo '<hr>'; } echo 'process was safely shutdown<br>'; } Или есть какие-то косяки? И собственно ещё вопрос: в чём различие между &$obj и $obj? Я как ни пробовал, в 5.2 и 5.3 версиях объект всегда по ссылке передаётся, если явно не клонировать.
Не думай насколько это быстро. Сколько у тебя таких объектов будет? 4? 40? 400? Пиши приложение и потом делай профайлинг. Ключ я сделал для того чтобы объект мог сам себя разрегистрировать при собственном уничтожении. А проблема в том, что я не сумел поймать, как убрать объект из глобальной области видимости Даже с бубном, - т.е. "&", я получаю 2 ссылки на объект, в глобальной области, и в стеке. Возьми и проверь. Я не помню почему от него отказался. Скорее всего он не работал как надо. в количестве ссылок. debug_zval_dump посмотри. Впрочем, можешь убрать из кода ссылки и посмотреть что он тебе выведет. Из того что я понял, сначала уничтожаются объекты глобальные. Т.е. те, которые имеют ссылку в основной области видимости скрипта. И только потом начинают по-очереди ломаться объекты которые имеют ссылку ТОЛЬКО внутри других объектов. Передавая ссылку я не даю запускаться деструктору объекта во время ломки глобальной области.
Simpliest Кстати твой код в конкретно моём примере не работает тоже. Допиши вместо die('Start shutdown<br>'); вот это: PHP: test($db); function test($db) { $u31 = new user($db); $u31->name = 'u31'; die('Start shutdown<br>'); } Вывод будет очень интересным: Код (Text): Start shutdown auto destruct u1 c9b4be523e0fa6bc4d1d77eca248a43e unregistered destructed u1 = c9b4be523e0fa6bc4d1d77eca248a43e auto destruct u12 712831a9531ee9970b5df444addcaed4 unregistered destructed u12 = 712831a9531ee9970b5df444addcaed4 auto destruct u21 db5fd02c389300d5557ccf62d4ebfbcc unregistered destructed u21 = db5fd02c389300d5557ccf62d4ebfbcc auto destruct u22 cca340d373debe55f1ee3efbbe53db58 unregistered destructed u22 = cca340d373debe55f1ee3efbbe53db58 auto destruct u31 a28ae9a5da567abd1156d7ee9b21306c unregistered process was safely shutdown process was safely shutdown destructed u31 = a28ae9a5da567abd1156d7ee9b21306c Я понял что надо делать. Надо свои собственные функции очистки писать (не деструкторы - это важно) и регистрировать их, а потом вызывать по цепочке перед exit(). Это единственный разумный способ. И весь огород приходится городить только потому, что php разрабы не могли чётко стандартизировать порядок вызова деструкторов. Щас пишу этот код, как будет готов покажу, хотя там ничего сложного. PS. До этого уже пробовал register_shutdown_function - но она вызывается после уничтожения части объектов. И ещё пробовал самому вызывать деструктор, но деструктор тогда при уничтожении объекта ещё повторно вызывается и это может плохо кончиться в ряде случаев.
Simpliest Я тоже понимаю, почему не пашет. Думаю мой огород будет проще, чем твой огород PHP: <?php class destruct_stack { protected static $_stack = array(); public function add_obj($obj) { if(!in_array($obj, self::$_stack)) { self::$_stack[] = &$obj; } } public static function exiter() { foreach(self::$_stack as $obj) { $obj->destruct(); } echo 'process was safely shutdown<br>'; exit; } } abstract class destruct_control { function register() { destruct_stack::add_obj($this); } abstract public function destruct(); } class a1 extends destruct_control { var $b; function __construct($b) { $this->b = $b; $this->register(); } public function destruct() { echo ' a1 '; } } class a2 extends destruct_control { var $b; function __construct($b) { $this->b = $b; $this->register(); } public function destruct() { echo ' a2 '; } } class b1 { var $b; var $n = 'b1'; function __construct($b) { $this->b = $b; } function __destruct() { echo " b1:{$this->b} "; $this->b++; } } $b1 = new b1(1); $a2 = new a2($b1); test($b1); function test($b) { $a1 = new a1($b); destruct_stack::exiter(); } ?> Работает правильно, правда не деструктит, зато чистить может. Минусы, которые вижу: 1) Менять везде exit() на destruct_stack::exiter(); 2) Не будет работать со сторонними классами, придётся делать для них обёртку или наследовать их и добавлять метод destruct().
Еще кузябельнее вот это PHP: <?php echo '<h2>first instance</h2><br>'; debug_zval_dump($db); $u1 = new user($db); $u1->name = 'u1'; echo '<h2>first pass</h2><br>'; debug_zval_dump($db); $u12 = new user($db); $u12->name = 'u12'; echo '<h2>second pass</h2><br>'; debug_zval_dump($db); $db1 = DB::getInstance(); echo '<h2>another instance</h2><br>'; debug_zval_dump($db1); debug_zval_dump($db); $u21 = new user2($db); $u21->name = 'u21'; echo '<h2>third pass</h2><br>'; debug_zval_dump($db1); debug_zval_dump($db); $u22 = new user2($db1); $u22->name = 'u22'; echo '<h2>fourth pass</h2><br>'; debug_zval_dump($db1); debug_zval_dump($db);
Выводы. В топку подсчет ссылок. Я этим наелся еще в C. Именно это и надо сделать. Просто shutdown каждого класса делать не в автоматическом деструкторе как у меня, а в отдельном методе у этих классов, как у botbot
А вот это очень хитрый баг будет. register_shutdown_function() вызывается после уничтожения части объектов. Что может получиться: мы делаем db - базу данных мы делаем o1 - наш объект, который что-то схораняет в db, и передаём в конструкторе для o1 наш db мы регим o1 для очистки. вызываем exit php убивает db потом он вызывает обработчик выхода, который мы зарегили в register_shutdown_function(). o1 пытается сохраниться в уже несуществующий db. В общем налицо та же самая проблема непредсказуемости порядка вызовов, только в извращёной форме.
И кстати. У меня он всегда вызывается первым. Ты можешь дать кейс как его вызвать в середине процесса выхода?
У тебя он относится к объекту порождаемому в основной области видимости скрипта? Пока все наши тесты только подтверждают это мое предположение.
Я боюсь надеяться на предположения, это ведёт к танцам с бубнами, извини если обидел . Насчёт register_shutdown_function(), щас попробую по памяти код заново написать. Пробовал уже, у меня 1 объект до этой ф-ции уничтожиться успел.