我发现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
跆拳道?!
答案 0 :(得分:3)
第一次测试中运行的早期析构函数是自动垃圾收集的结果。要理解这一点,让我们看看第二个(更简单的)测试:
1. $db = Mv_Core_Db_Manager::getInstance();
2. $sadb = $db->getStandaloneAdapter('bdm_bcb');
3. var_dump($sadb->getConnection());
步骤:
var_dump()
调试$ sadb独立适配器的getConnection()
方法的返回值,这是第二个输出中的resource(29, mysql link)
行,这里的垃圾收集最终会变得很糟糕。
如果你考虑你所描述的第一个测试,尽管看起来有类似的源代码,它有不同的步骤:
1. $db = Mv_Core_Db_Manager::getInstance();
2. $sadb = $db->getStandaloneAdapter('bdm_bcb')->getConnection();
3. var_dump($sadb);
步骤:
getConnection()
getter的返回值分配给$ sadb,var_dump()
调试MySQL独立适配器的getConnection()
getter返回的值,它基本上是垃圾收集器已经收集的资源的句柄。此处垃圾收集发生在var_dump()
之前。
总而言之,您提供的第一个测试强制垃圾收集器在代码的第2行和第3行之间跳转。另一方面,第二次测试在最后强制进行垃圾收集。
结果是您的资源句柄指向已经由GC清理过的内存。
答案 1 :(得分:1)
根据提供的代码...
__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
)^^