使用流畅的界面调用php析构函数太快了

时间:2013-04-23 07:49:22

标签: php mysql destructor fluent-interface

我发现php析构函数非常奇怪:

基本上我有一个数据库管理类,它使用工厂加载适配器来定义应该加载哪个适配器(mysql,mysqli等)

我只会记下有趣的代码部分,因为类本身更长但代码不涉及当前的麻烦

问题只发生在mysql上(mysqli& pdo工作正常)但出于兼容性目的,摆脱mysql是不可能的。

class manager
{
    private static $_instance;

    public static function getInstance()
    {
        return isset(self::$_instance) ? self::$_instance : self::$_instance = new self;
    }

    public function getStandaloneAdapter()
    {
        return new mysql_adapter(array('host'=>'127.0.0.1', 'username' => 'root', 'password' => '', 'dbname' => 'etab_21'));
    }
}

abstract class abstract_adapter
{
    protected $_params;
    protected $_connection;

    public function __construct($params)
    {
        $this->_params = (object)$params;
    }

    public function __destruct()
    {
        echo 'destructor<br/>';
        var_dump(debug_backtrace(false));
        $this->closeConnection();
    }

    abstract public function closeConnection();
}

class mysql_adapter extends abstract_adapter
{
    public function getConnection()
    {
        $this->_connect();

        if ($this->_connection) {
            // switch database
            $this->_useDB($this->_params->dbname);
        }

        return $this->_connection;
    }

    protected function _connect()
    {
        if ($this->_connection) {
            return;
        }

        // connect
        $this->_connection = mysql_connect(
            $this->_params->host,
            $this->_params->username,
            $this->_params->password,
            true
        );

        if (false === $this->_connection || mysql_errno($this->_connection)) {
            $this->closeConnection();
            throw new Mv_Core_Db_Exception(null, Mv_Core_Db_Exception::CONNECT, mysql_error());
        }

        if ($this->_params->dbname) {
            $this->_useDB($this->_params->dbname);
        }
    }

    private function _useDB($dbname)
    {
        return mysql_select_db($dbname, $this->_connection);
    }

    public function isConnected()
    {
        $isConnected = false;
        if (is_resource($this->_connection)) {
            $isConnected = mysql_ping($this->_connection);
        }
        return $isConnected;
    }

    public function closeConnection()
    {
        if ($this->isConnected()) {
            mysql_close($this->_connection);
        }
        $this->_connection = null;
    }
}

所以这是我正在运行的测试:

$sadb1 = manager::getInstance()->getStandaloneAdapter()->getConnection();
var_dump($sadb1);

和我得到的输出:

destructor
array
  0 => 
    array
      'file' => string '**\index.php' (length=48)
      'line' => int 119
      'function' => string '__destruct' (length=10)
      'class' => string 'abstract_adapter' (length=16)
      'type' => string '->' (length=2)
      'args' => 
        array
          empty
  1 => 
    array
      'file' => string '**\index.php' (length=48)
      'line' => int 119
      'function' => string 'unknown' (length=7)
resource(26, Unknown)

如果我将测试更改为:

$sadb1 = manager::getInstance()->getStandaloneAdapter();
var_dump($sadb1->getConnection());

输出很好:

resource(26, mysql link)
destructor
array
  0 => 
    array
      'function' => string '__destruct' (length=10)
      'class' => string 'abstract_adapter' (length=16)
      'type' => string '->' (length=2)
      'args' => 
        array
          empty

跆拳道?!

2 个答案:

答案 0 :(得分:3)

第一次测试中运行的早期析构函数是自动垃圾收集的结果。要理解这一点,让我们看看第二个(更简单的)测试:

1. $db = Mv_Core_Db_Manager::getInstance();
2. $sadb = $db->getStandaloneAdapter('bdm_bcb');
3. var_dump($sadb->getConnection());

步骤:

  1. 将数据库管理器分配给变量$ db,
  2. 独立适配器(在这种情况下由于你在调试时引入的工厂中的hack引入的MySQL适配器)被分配给$ sadb,
  3. var_dump()调试$ sadb独立适配器的getConnection()方法的返回值,这是第二个输出中的resource(29, mysql link)行,
  4. 清理时间; PHP垃圾收集器为$ sadb运行析构函数(由于调试在输出中可见),然后运行$ db(在输出中不可见)。
  5. 这里的垃圾收集最终会变得很糟糕。

    如果你考虑你所描述的第一个测试,尽管看起来有类似的源代码,它有不同的步骤:

    1. $db = Mv_Core_Db_Manager::getInstance();
    2. $sadb = $db->getStandaloneAdapter('bdm_bcb')->getConnection();
    3. var_dump($sadb);
    

    步骤:

    1. 与上述测试案例相同,
    2. 将MySQL独立适配器对象的getConnection() getter的返回值分配给$ sadb,
    3. 因为MySQL独立适配器本身已将分配给任何变量,PHP垃圾收集器决定它不再使用,因此它清理对象并运行其析构函数(析构函数调试在你的输出在第一个),
    4. var_dump()调试MySQL独立适配器的getConnection() getter返回的值,它基本上是垃圾收集器已经收集的资源的句柄。
    5. 此处垃圾收集发生在var_dump()之前。

      总而言之,您提供的第一个测试强制垃圾收集器在代码的第2行和第3行之间跳转。另一方面,第二次测试在最后强制进行垃圾收集。

      结果是您的资源句柄指向已经由GC清理过的内存。

答案 1 :(得分:1)

根据提供的代码...

PHP __destruct()

__destruct()方法,根据文档:

  

只要没有对特定对象的其他引用,或者在关闭序列期间以任何顺序,就会调用析构函数方法。

您将遇到不同结果的原因:

$sadb1 = manager::getInstance()->getStandaloneAdapter()->getConnection();
var_dump($sadb1);

给你不正确的结果(从你期望的结果)是你的代码中没有更多对mysql_adapter实例的引用,因此调用了__destruct()方法,这样做会关闭对持有resource链接的mysql - 因为PHP5很聪明并且通过引用自动神奇地传递大多数东西(好东西 - 大部分时间都是^^),所以当你var_dump($sadb);时,在前一行调用了__destruct方法,因此var_dump为您提供了引用,但现在却没有。

此代码为您提供所期望的原因:

$sadb1 = manager::getInstance()->getStandaloneAdapter();
var_dump($sadb1->getConnection());

是您转储资源,然后调用__destruct方法,在转储后为您提供debug_trace

我希望这有助于为析构函数解决问题。

出于好奇,为什么b'伤心'? ($sadb)^^