最初の近似では、弱いリンクとは確かにオブジェクトが必要であることを意味しますが、必要がある場合は、それを削除して、何らかの形でうまくいくことができます。
当初、PHPでは、弱いリンクの概念はありませんが、修正は非常に簡単です。 次に、PHP 7について説明します。
以下のソリューションはオブジェクトに対してのみ機能することを別に注意する価値があります。これは、PHPのガベージコレクターの特性によるものです。
推奨読書
オブジェクトハンドラー
PHPでは、オブジェクトのさまざまなイベント(操作)を処理するための関数は、構造体zend_object_handlersを持つハンドラーのテーブルで表されます。
内部クラスは、オブジェクトハンドラーを定義および再定義できるため、クローン作成、比較、オブジェクトプロパティへのアクセス、デバッグ情報の取得など、さまざまな一般的な操作の動作を変更できます。 ハンドラーを設定できる可能な操作の完全なリストは、上記のzend_object_handlers構造体へのリンクから入手できます。
内部クラスを継承しなかったユーザークラスは、 std_object_handlers変数に格納されているデフォルトハンドラーを受け取ります。
ごみ収集
PHPのガベージコレクションのトピックについては、 公式ドキュメントを含めて詳細に説明されており、この記事の主題ではありません。
要するに、値は、その参照カウンターが0に等しくならないときにすぐにアセンブルされます。通常モードでは、オブジェクトはアセンブル中に呼び出され、オブジェクトによって占有されていたメモリがクリアされます。 内部的には、 dtor_objハンドラーとfree_objハンドラーがそれぞれこれを担当します。
内部的には、標準デストラクタの実装はzend_objects_destroy_object関数であり、その本質は__destruct()メソッドがあればそれを呼び出すことです。
例外がスローされるか、言語コンストラクトexit (またはその同義語die )が呼び出された場合、オブジェクトのデストラクタは呼び出されません。次に例を示します。
<?php class Test { public function __destruct() { echo 'Goodbye Cruel World!', PHP_EOL; } } $test = new Test(); throw new Exception('Test exception');
何がもたらす
$ php test.php Fatal error: Uncaught Exception: Test exception in /home/vagrant/Development/php-weak/test.php on line 13 Exception: Test exception in /home/vagrant/Development/php-weak/test.php on line 13 Call Stack: 0.0011 365968 1. {main}() /home/vagrant/Development/php-weak/test.php:0
オブジェクトストレージ
弱いリンクの実装で起こりうる落とし穴をより完全に理解するために、すべてのオブジェクトがPHPに格納される場所と方法を検討してください。幸いなことに、これは非常に簡単です-EG(objects_store).object_bucketsはキーがZ_OBJ_HANDLE(zval)である配列ですオブジェクトの整数インデックスです。以降、オブジェクト記述子と呼びます。 各瞬間に、それはオブジェクトごとに一意です; EG(objects_store).object_bucketsからオブジェクトを削除するとき、オブジェクト記述子の値は別のオブジェクトに割り当てることができます、例えば:
<?php $obj1 = new stdClass(); $obj2 = new stdClass(); debug_zval_dump($obj1); debug_zval_dump($obj2); $obj2 = null; // EG(objects_store).object_buckets $obj2 = new SplObjectStorage(); debug_zval_dump($obj2);
パフォーマンスで得られるもの
object(stdClass)#1 (0) refcount(2){ } object(stdClass)#2 (0) refcount(2){ } object(SplObjectStorage)#2 (1) refcount(2){ ["storage":"SplObjectStorage":private]=> array(0) refcount(1){ } }
ハッシュ記号(#)の後の値は、オブジェクト記述子の値です。 ご覧のとおり、記述子2の値が再利用されました。
弱いリンクを実装する
上記に基づいて、弱参照の実装に対する非常に明らかな解決策は、参照するオブジェクトデストラクタをラップし、初期デストラクタの実行後に独自のロジックを追加することです。 スキルのピークは、確かに通訳者自身のレベルで弱い参照を作成することですが、これはまったく別の話です。
オブジェクトハンドラのテーブルへのポインタにはconst指定子があり、標準C90およびC99では 、 非定数型にキャストすることでこの値を変更すると、未定義の動作が発生することが示されています。
const値を変更するための関連リンク
これには理由があります。 1つのオブジェクトのハンドラーの値を変更すると、このクラスのすべてのオブジェクトのハンドラーが変更されます。さらに、サブクラスが完全に再定義されていない場合、カスタムクラスのオブジェクト(内部クラスの子孫ではない)の場合、作成されたすべてのオブジェクトのハンドラーカスタムクラスから。
最善の解決策は、元のテーブルをコピーし、その中に必要なハンドラdtor_objを置き換えることにより、単一オブジェクトのハンドラテーブルへのポインタを置き換えることです 。 省略されたエントリは次のとおりです。
php_weak_referent_t *referent = (php_weak_referent_t *) ecalloc(1, sizeof(php_weak_referent_t)); memcpy(&referent->custom_handlers, referent->original_handlers, sizeof(zend_object_handlers)); referent->custom_handlers.dtor_obj = php_weak_referent_object_dtor_obj; Z_OBJ_P(referent_zv)->handlers = &referent->custom_handlers;
障害物
テスト中に、かなり不快な事実が明らかになりました-ハンドラーテーブルへのポインターの値の変更は、PHPテーブルspl_object_hash()の結果に影響しました。この関数は、ハンドラーテーブルへのポインターの値を使用して、最後の16文字のID(最初の16はオブジェクト記述子のハッシュ)を生成し、その結果、オブジェクトの戻りID値は、そのための弱いリンクを作成する前後で異なります。
<?php $obj = new stdClass(); var_dump(spl_object_hash($obj)); // 0000000046d2e51 a000000003e3e4a43 $ref = new Weak\Reference($obj); var_dump(spl_object_hash($obj)); // 0000000046d2e51 a00007fc62b682d1b
肯定的な事実は、変更された後、ハッシュは変更されないことです(少なくともこの拡張機能では)。
しかし、さらに先に進みます。 PHP開発者との議論を通して、ハッシュを生成するときにハンドラーテーブルへのポインターの値を使用しないというアイデアが浮上しました。最初の部分でさえ、オブジェクトのハッシュの一意性条件をその寿命を通して満たしているからです。 2番目の部分は 、マスクとして使用されていた任意の値のみを使用するようになりました。
<?php class Test {} class X {} $t = new Test(); $x = new X(); $s = new SplObjectStorage(); var_dump(spl_object_hash($t)); // 00000000054acbeb 0000000050eaeb6f var_dump(spl_object_hash($x)); // 00000000054acbe8 0000000050eaeb6f var_dump(spl_object_hash($s)); // 00000000054acbe9 0000000050eaeb6f
はるかに良い! この改善は、PHPバージョン> 7.0.2で利用できる可能性が最も高いでしょう。
ただし、これはPHP 7.0.2以降でのみ発生するため、PHP 7の以前のバージョンではグローバルかつ確実に動作することを確認する必要があります。
あまり美しくありませんが、非常に機能する方法は... spl_object_hash()関数を独自の実装に置き換えることです :
<?php $spl_hash_function = $EG->function_table['spl_object_hash']; $custom_hash_function = function (object $obj) { $hash = null; if (weak\refcounted($obj)) { $referent = execute_referent_object_metadata($obj); $obj->handlers = $referent->original_handlers; $hash = $spl_hash_function($obj); $obj->handlers = $referent->custom_handlers; } if (null == $hash) { $hash = $spl_hash_function($obj); } return $hash; }; $EG->function_table['spl_object_hash'] = $custom_hash_function;
この置換メソッドの作成者は、拡張機能php-weakrefの形式でのPHP 5での弱いリンクの初期実装の作成者であるEtienne Kneuss です 。 残念ながら、PHP 7のサポートも実装するまで数時間待たなかったのですが、2つの拡張機能はどれよりも優れており、現時点では機能と概念がまったく異なります。
破壊通知メカニズム
弱いリンクを作成するとき、参照しているオブジェクトが破棄されたことを通知するメカニズムは非常に重要です。 概念的に、この問題は、JavaでのWeakReferenceの実装で行われているように、弱いリンクを配列に格納することにより、最も弱いリンクを調べることで解決できます。 そして実際には、Pythonのweakref.refの実装で行われているように、ユーザー定義の関数を呼び出す、美食の興味の対象です。
これを行うには、オブジェクトのソースデストラクターを呼び出した後、ウィークリンクの作成時に指定された場合は各ウィークリンクのユーザー関数を呼び出し、指定された場合は各ウィークリンクオブジェクトを配列に保存します。 デストラクタまたはユーザー通知関数のいずれかで例外がスローされた場合、それ以降の通知関数は呼び出されないことに注意してください。 ユーザー関数の呼び出しによるオブジェクトの破壊に関する単純化された通知メカニズムは、デストラクタの再定義を伴うオブジェクトの基本クラスの拡張として表すことができます。親デストラクタを呼び出した後、最後から最初の順にユーザ関数が次々に呼び出されます。
その結果、デストラクタラッパーはメタコードとして表すことができます。
run_original_dtor_obj($object); foreach($weak_references as $weak_ref_object_handle => $weak_reference) { if (is_array($weak_reference->notifier)) { $weak_reference->notifier[] = $weak_reference; } elseif (is_callable($weak_reference->notifier) && $no_exception_thrown) { $weak_reference->notifier($weak_reference); } unset($weak_references[$weak_ref_object_handle]); }
弱リンククラス自体の形式は次のとおりです。
namespace Weak; class Reference { /** * @param object $referent * @param callable | array | null $notify * * , . * . * * - . * * , , , * , * - . */ public function __construct(object $referent, $notify = null) { } /** * . null. * * @return object | null */ public function get() { } /** * : . * * @return bool */ public function valid() : bool { } /** * * * @param callable | array | null $notify . , . * * @return callable | array | null , */ public function notifier($notify = null) { } }
値とオブジェクトの詳細については、この拡張機能は、弱いリンククラスの機能を補完する多くの関数を提供し、ユーザーレベルでさまざまなソリューションの作成を
拡張機能のリスト
これらの機能は非常に具体的であり、ユーザーが何をして何をしているのかをユーザーが理解している場合にのみ使用しないでください。
namespace Weak; /** * , . * * @param mixed $value * * @return bool */ function refcounted(mixed $value) : bool {} /** * . 0. * * , * weak\refcount(new stdClass()) 0, * . * * @param mixed $value * * @return int */ function refcount(mixed $value) : int {} /** * , . * * @param object $value * * @return bool */ function weakrefcounted(object $value) : bool {} /** * . 0. * * @param object $value * * @return int */ function weakrefcount(object $value) : int {} /** * . . * * @param object $value * * @return mixed */ function weakrefs(object $value) : array {} /** * . * * @param object $value * * @return int */ function object_handle(object $value) : int {}
これらの機能は非常に具体的であり、ユーザーが何をして何をしているのかをユーザーが理解している場合にのみ使用しないでください。
実用化
上記の機能を提供するこの拡張機能は、別の拡張機能-PHPでのv8 JavaScriptエンジンの統合の結果です(v8jsではなく、ソースコードが公開されていない現時点でよりシンプルで強力です)。
phpオブジェクトをjsオブジェクトに変換するための抽象化レイヤーを作成するとき、phpオブジェクトとjs表現の対応表を明確に比較し、最も重要なことには、クリーンアップする必要がありました。 典型的な問題は、同じphpオブジェクトが既にjs表現が存在するjsで返されるときに状況が異なる場所で発生することであり、js内でビューが同一になるためには、対応表を保存する必要があります。
最初は、オブジェクトのid phpとjs表現自体の対応表が作成されましたが、作業が進むにつれて、過剰な孤立したjs表現が形成される状況が発生しました:誰も参照していないため、対応するphpオブジェクトがガベージコレクターによって削除されました。 弱いリンクと共に実装される通知メカニズムは、この問題を完全に解決します。
上記の症状に悩まされておらず、健康的なナイトライフを送っている人にとって、この拡張機能は次の場合に役立ちます。
- 実行時にキャッシュオブジェクトを実装します。
- データをオブジェクトに関連付ける。たとえば、継承や構成があまり適切でない状況でプロパティやメソッドを追加する(hello、Java)。
- オブジェクトが削除された後、対応するイベントリストを削除します。
コメントで、PHPで弱いリンクを使用する方法を共有することをお勧めします。
私たちのニーズに合わせて、 SplObjectStorage - Weak \ SplObjectStorageに基づいたWeakMapデータ構造の実装が作成されました
拡張機能はgithub.com/pinepain/php-weakで入手でき、 MITでライセンスされています。 動作するにはPHP 7が必要です。