2

私は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

えっ?!

4

2 に答える 2

3

最初のテストで初期のデストラクタが実行されるのは、自動ガベージ コレクションの結果です。これを理解するために、2 番目の (より簡単な) テストを見てみましょう。

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

手順:

  1. db マネージャーは変数 $db に割り当てられています。
  2. スタンドアローン アダプタ (この場合、デバッグ用に導入したファクトリのハックによる MySQL アダプタ) が $sadb に割り当てられています。
  3. var_dump()getConnection()$sadb スタンドアロン アダプターのメソッドの戻り値をデバッグしresource(29, mysql link)ます。これは、2 番目の出力の行です。
  4. クリーンアップ時間; PHP ガベージ コレクターは、$sadb (debug のおかげで出力に表示されます) のデストラクタを実行し、その後に $db (出力には表示されません) を実行します。

ここで最後にガベージ コレクションが行われます。

あなたが説明した最初のテストを考えると、ソースコードは見た目が似ているにもかかわらず、手順が異なります。

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

手順:

  1. 上記のテストケースでも同じです。
  2. MySQL スタンドアロン アダプタ オブジェクトの getter の戻り値はgetConnection()$sadb に割り当てられます。
  3. MySQL スタンドアロン アダプター自体はどの変数にも割り当てられていないため、PHP ガベージ コレクターはそれがもう使用されていないと判断し、オブジェクトをクリーンアップしてそのデストラクタを実行します (デストラクタのデバッグは最初に出力に表示されます)。
  4. var_dump()MySQL スタンドアロン アダプタの getter によって返された値をデバッグしますgetConnection()。これは基本的に、ガベージ コレクタによってすでに収集されているリソースへのハンドルです。

ここでは、 の前にガベージ コレクションが行われvar_dump()ます。

要約すると、提供した最初のテストでは、ガベージ コレクターがコードの 2 行目と 3 行目の間にジャンプするように強制されます。一方、2 番目のテストでは、最後にガベージ コレクションが強制されます。

その結果、リソース ハンドルは、GC によって既にクリーンアップされたメモリを指しています。

于 2013-04-23T08:40:45.463 に答える
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) ^^

于 2013-04-23T08:45:58.363 に答える