【问题标题】:php destructor behaviourphp 析构函数行为
【发布时间】:2026-02-18 22:50:02
【问题描述】:

我试图了解 php 构造函数和析构函数的行为。构造函数的一切都按预期进行,但我无法让析构函数隐式触发。我已经在 php.net 和相关网站上完成了所有阅读,但我找不到这个问题的答案。

如果我有一个简单的类,比如:

class test{

     public function __construct(){
          print "contructing<br>";
     }

     public function __destruct(){
          print "destroying<br>";
     }
}

我用类似这样的方式来称呼它:

$t = new test;

它打印构造函数消息。但是,我希望当脚本结束并呈现页面时,析构函数应该触发。当然不会。

如果我在脚本结束时调用unset($t);,当然会触发析构函数,但是有没有办法让它隐式触发?

【问题讨论】:

    标签: php oop destructor


    【解决方案1】:

    这很容易测试。

    <?php
    
    class DestructTestDummy {
        protected $name;
    
        function __construct($name) {
            echo "Constructing $name\n";
            $this->name = $name;
        }
    
        function __destruct() {
            echo "Destructing $this->name\n";
            //exit;
        }
    }
    
    echo "Start script\n";
    
    register_shutdown_function(function() {
        echo "Shutdown function\n";
        //exit
    });
    
    $a = new DestructTestDummy("Mr. Unset");
    $b = new DestructTestDummy("Terminator 1");
    $c = new DestructTestDummy("Terminator 2");
    
    echo "Before unset\n";
    unset($a);
    echo "After unset\n";
    
    
    echo "Before func\n";
    call_user_func(function() {
        $c = new DestructTestDummy("Mrs. Scopee");
    });
    echo "After func\n";
    
    $b->__destruct();
    
    exit("Exiting\n");
    

    在 PHP 5.5.12 中打印:

    Start script
    Constructing Mr. Unset
    Constructing Terminator 1
    Constructing Terminator 2
    Before unset
    Destructing Mr. Unset
    After unset
    Before func
    Constructing Mrs. Scopee
    Destructing Mrs. Scopee
    After func
    Destructing Terminator 1
    Exiting
    Shutdown function
    Destructing Terminator 2
    Destructing Terminator 1
    

    所以我们可以看到,当我们显式取消设置对象时,当它超出范围时,在脚本结束时调用了析构函数。

    【讨论】:

    • 所以,它保证像C++ 一样被调用,而不是像Java 那样被垃圾收集,对吗?
    • @Top-Master 是的,除非可能存在严重错误。
    【解决方案2】:

    __destruct() 魔法函数在对象被删除/销毁时执行(使用unset)。在脚本关闭期间不会调用它。当 PHP 脚本完成执行时,它会清理内存,但不会像这样“删除”对象,因此不会调用 __destruct() 方法。

    您可能会想到register_shutdown_function(),它会在您的 PHP 脚本执行完毕时触发。

    function shutdown()
    {
        // code here
        echo 'this will be called last';
    }
    
    register_shutdown_function('shutdown');
    

    【讨论】:

    • 实际上,根据手册,这不是真的。见php.net/manual/en/language.oop5.decon.php。 “只要删除了对特定对象的所有引用,或者当对象被显式销毁或以关闭顺序中的任何顺序,就会调用析构函数方法。”
    • 你需要非常小心地尝试在关闭函数中写入浏览器 - “关闭所有打开的输出缓冲区后调用关闭函数” - althuogh stdout 仍然连接,这可能不是您之前使用的输出流
    • 或者得到这个错误信息:LoggerAppenderFile 类的对象被扫描,因此它的析构函数没有被调用。
    • 同样,当抛出异常但未处理时,析构函数将不会被调用
    【解决方案3】:

    我的理解是,当脚本结束时,会自动为任何剩余的对象调用析构函数。

    查看manual page on constructors and destructors,似乎完全绕过析构函数的唯一方法是,如果您从在相关对象之前被销毁的对象的析构函数中调用exit()

    您是否在任何析构函数中使用exit()?您的脚本中是否有多个对象?

    如果不是太麻烦,也许您可​​以发布有问题的实际代码,而不是您现在问题中的示例代码。除了示例构造函数中的拼写错误之外,该代码应该调用test 对象的构造函数和析构函数。

    【讨论】:

    • 我认为它比这更复杂。如果您将exit 放入析构函数中,那么对象第一次被析构时,它将调用使用register_shutdown_function 注册的任何函数,然后调用任何其他析构函数。如果这些析构函数还包含exit,则脚本将停在那里。即,第一次调用 exit 将脚本置于关闭模式,第二次强制它立即停止。除非第二个出口位于关闭函数内部——否则它的行为或多或少类似于“返回”。
    【解决方案4】:

    一旦取消设置对对象的所有引用,就会调用类的 __destruct 方法。

    例如

    $dummy = (object) new Class();
    

    如果将虚拟对象设置为 null 或退出脚本,则会自动调用析构函数。

    unset($dummy); // or $dummy = null;
    //exit(); //also possible
    

    然而,调用析构函数有三个重要的记忆注意事项:

    首先,析构函数应该是一个公共方法,而不是受保护的或私有的。

    其次,避免使用内部引用和循环引用。例如:

    class NewDemo
    {
         function __construct()
         {
              $this->foo = $this;
         } 
         function __destruct()
         {
              // this method will never be called 
              // and cause memory leaks
              // unset will not clear the internal reference
         }
    }
    

    以下内容也不起作用:

    $a = new Class();
    $b = new Class();
    $a->pointer = $b;
    $b->pointer = $a;
    
    unset($a); // will not call destructor
    unset($b); // will not call destructor
    

    第三,决定在输出发送后是否调用析构函数。使用

    gc_collect_cycles() 
    

    可以在向用户发送数据之前确定是否调用了所有析构函数。

    请参阅http://php.net/manual/en/language.oop5.decon.php 以获取有关魔法破坏方法的来源和详细说明以及示例。

    【讨论】: