MySQLi在析构函数中关闭,立即触发

I've recently been redesigning an archaic PHP application, which had most of it's code in 1000+ line long PHP files for each page. I first started by refactoring much of the code into classes. I recently have been working on database connections, and began writing a class for it. I decided to throw $mysqli->close() into the destructor (I'm using the OO approach).

Unfortunately I've almost immediately ran in to problems with it. Instead of the MySQLi connection closing when the page finishes rendering (or when there are no more references to the DB object), it just immediately closes. I've tested this through writing a simple test:

$db = new DBConnect(); //My abstraction class
$db->getSQL()->query("SELECT 1"); //Query fails. Error message states connection closed.

My destructor looks like this:

public function __destruct() {
    $this->mysqli->close();
}

My constructor looks like this:

public function __construct() {
     $this->mysqli=new mysqli(\MainConfig::$database['host'], \MainConfig::$database['user'], \MainConfig::$database['pass'], \MainConfig::$database['db']);
     if($this->mysqli->connect_error) {
          die('Connect error (' . $this->mysqli->connect_errno . ') ' . $this->mysqli->connect_error); //This is not ever fired.
     }
}

I know it's not some other bit of code closing the connection, since if I comment out the $this->mysqli->close() line, then the code works as expected. It seems to me that the destructor fires immediately which is not the desired behavior. Am I misunderstanding how they are intended to work?

I was able to reproduce the behavior you described with the code:

<?php

class DestructorTest {
    public function getter() {
        print "DestructorTest::getter() called
";
        return new CallableTest();
    }

    public function __destruct() {
        print "DestructorTest destructor called
";
    }
}

class CallableTest {
    public function method() {
        print "CallableTest::method() called
";
    }
}

(new DestructorTest())->getter()->method();

which prints:

DestructorTest::getter() called
DestructorTest destructor called
CallableTest::method() called

The long and the short of it is: A destructor may be called as soon as there are no references to an object. This can even happen in the middle of a line -- in this case, for instance, after DestructorTest:getter() is called, the DestructorTest object is no longer reachable, so it is destroyed.

My advice:

  • You don't need to close MySQLi handles. They already have an internal destructor which will close them when they are garbage-collected.

  • In fact: avoid writing destructor methods in general. (This applies to many programming languages, not just PHP.) They are frequently not called when you expect them to be, and have a tendency to cause strange, hard-to-debug behavior.

  • If you're going to ignore this and write a destructor anyways, then you need to make sure that you don't inadvertently destroy something which you've "leaked" a reference to outside your class. In the case of your code, you've allowed code outside the class to get a reference to your MySQLi handle, but you're destroying that handle as soon as your object goes away -- even if the handle is still being used outside.