php析构函数调用过早,界面流畅 [英] php destructor called too soon with fluent interface

查看:71
本文介绍了php析构函数调用过早,界面流畅的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现有关php析构函数的事情很奇怪:

I found a really weird thing about php destructor:

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

basically I have a database management class which loads an adapter using a factory to define which adapter should be loaded (mysql, mysqli, etc.)

由于类本身更长,但是代码不涉及当前的麻烦

I'll write down only the part of the code insteresting, as the class itself is way longer but code isn't involved in the current trouble

该问题仅在mysql上发生(mysqli& pdo正常工作),但是出于兼容性目的,摆脱mysql毫无疑问.

The problem occurs ONLY with mysql (mysqli & pdo works just fine) but for compatibility purposes, getting rid of mysql it's out of question.

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;
    }
}

这是我正在运行的测试:

so here's the test i'm running:

$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)

如果我将测试更改为此:

if i change my test to this:

$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

wtf?!

推荐答案

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

The early destructor run in the first test is an outcome of automatic garbage collection. To understand this, let's look at the second (easier) test:

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

步骤:

  1. 数据库管理器已分配给变量$ db,
  2. 将独立适配器(在本例中为MySQL适配器,是由于您引入调试的工厂被黑客入侵)已分配给$ sadb,
  3. var_dump()调试$ sadb独立适配器的getConnection()方法的返回值,该方法是第二个输出中的resource(29, mysql link)
  4. 清理时间; PHP垃圾收集器为$ sadb(在调试中在输出中可见)运行析构函数,在$ db之后(在输出中不可见)运行析构函数.
  1. the db manager is being assigned to the variable $db,
  2. the standalone adapter (MySQL adapter in this case due to hack in the factory you have introduced for debugging) is being assigned to $sadb,
  3. var_dump() debugs the return value of the getConnection() method of the $sadb standalone adapter, which is the resource(29, mysql link) line in your second output,
  4. clean up time; PHP garbage collector runs the destructor for $sadb (visible in your output thanks to debug) and after that $db (not visible in your output).

在这里,垃圾收集工作正在逐渐结束.

Here garbage collection is hapenning at the end.

如果您看过所描述的第一个测试,尽管看起来源代码看起来很相似,但是它具有不同的步骤:

If you consider the first test you have described, despite deceivingly similar looking source code, it has different steps:

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返回的值,该值基本上是垃圾收集器已经收集的资源的句柄.
  1. the same in the test case above,
  2. the return value of the getConnection() getter of the MySQL standalone adapter object is assigned to $sadb,
  3. because the MySQL standalone adapter itself has not been assigned to any variable, PHP garbage collector decides that it's not used anymore so it cleans up the object and runs its destructor (the destructor debug is visible in your output in first),
  4. var_dump() debugs the value returned by the of the getConnection() getter of the MySQL standalone adapter, which is basically a handle to a resource which has already been collected by the garbage collector.

这里垃圾收集发生在var_dump()之前.

Here garbage collection happens before var_dump().

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

To summarize, the first test you have provided forces the garbage collector to jump in between lines 2 and 3 of your code. On the other hand, the second test forces the garbage collection at the very end.

结果是您的资源句柄指向GC已清除的内存.

The outcome is that your resource handle is pointing to memory that has been already cleaned up by the GC.

这篇关于php析构函数调用过早,界面流畅的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆