PHP 7のオブジェクト

画像



今日、PHP開発者はCレベルのAPIに取り組んでいますが、この記事では、PHPの内部開発について主に説明しますが、ストーリーの途中でユーザーレベルの観点から何か面白いことがあれば、脱線して説明します。



PHP 5と比較したオブジェクトの変更



PHPのオブジェクトのトピックを完全に理解するには、まずPHPのオブジェクトとクラスに関する詳細な投稿を読むことをお勧めします。



それで、5番目と比較して7番目のバージョンでは何が変わったのでしょうか?





オブジェクト構造とメモリ管理



まず、 zend_object_value



に別れを告げることができます。この構造はPHP 7で廃止されました。



zend_object定義の例を見てみましょう:



 /* in PHP 5 */ typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object; /* in PHP 7 */ struct _zend_object { zend_refcounted_h gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; /* C struct hack */ };
      
      





ご覧のとおり、PHP 5とは若干の違いがあります。



最初に、新しいzvalおよびガベージコレクションメカニズムの一部であるzend_refcounted_h



ヘッダーが含まれています。



次に、オブジェクトにそのhandle



が含まれるようになりましたhandle



、PHP 5ではこのタスクはzend_object_store



によって実行されていzend_object_store



。 また、PHP 7では、オブジェクトストアの責任がはるかに少なくなります。



第三に、zvalベクトルのproperties_table



を置き換えるには、Cの構造的なハックが使用されます。これは、カスタムオブジェクトを作成するときに役立ちます。



カスタムオブジェクト管理



重要な変更は、ニーズに合わせて作成するカスタムオブジェクトの管理に影響を与えました。 現在、それらにはzend_object



が含まれていzend_object



。 これはZend Engineオブジェクトモデルの非常に重要な機能です。拡張機能は独自のオブジェクトを宣言および管理し、エンジンのソースコードを変更せずにZendのオブジェクトの標準実装の機能を開発できます。



PHP 5では、単純にzend_object



基本定義を含むC構造の形式で継承を作成します。



 /* PHP 5 */ typedef struct _my_own_object { zend_object zobj; my_custom_type *my_buffer; } my_own_object;
      
      





C構造の継承のおかげで、単純な構造を作成するだけで十分です。



 /* PHP 5 */ my_own_object *my_obj; zend_object *zobj; my_obj = (my_own_object *)zend_objects_store_get_object(this_zval); zobj = (zend_object *)my_obj;
      
      





PHP 5でzvalを取得するとき、たとえばOOメソッドで$ thisを取得すると、このzval内から直接指すオブジェクトにアクセスできないことに気づいたかもしれません。 これを行うには、オブジェクトストアに移動する必要があります。 (PHP 5の)zvalからハンドラーを抽出し、それを使用してストアに、見つかったオブジェクトを返すように依頼します。 このオブジェクト(カスタムでも可)はvoid*



として返されvoid*



。 何もカスタマイズしていない場合は、 zend_object*



の形式でzend_object*



する必要があります。そうでない場合は、 zend_object*



の形式でzend_object*



する必要があります。



要するに、メソッドからオブジェクトを取得するには、PHP 5で検索手順を実装する必要があります。 そして、これはパフォーマンスにあまり影響しません。



PHP 7では、状況は異なります。 オブジェクト(カスタムまたはクラシックzend_object



いずれか)は、zvalに直接保存されます。 この場合、 オブジェクトストレージは抽出操作をサポートしなくなりました 。 つまり、オブジェクトストアの内容を読み取ることはできず、書き込むか消去するだけです。



ホストされたオブジェクトは完全にzvalに埋め込まれているため、パラメーターとしてzvalを呼び出し、それが指すオブジェクトのメモリ領域を取得する場合、追加で検索する必要はありません。 PHP 7でオブジェクトを取得する方法は次のとおりです。



 /* PHP 7 */ zend_object *zobj; zobj = Z_OBJ_P(this_zval);
      
      





PHP 5よりもはるかに簡単ですか?



それ以外の場合、オブジェクトのカスタム配置での作業が調整されます。 上記のコードからわかるように、カスタムオブジェクトを取得する唯一の方法はメモリを操作することです。必要なメモリサイズ内で任意の方向にポインタを移動します。 純粋なCプログラミングと高いパフォーマンス:おそらく同じ物理メモリページに留まるため、カーネルは新しいページをロードしません。



PHP 7でカスタムオブジェクトを宣言する:



 /* PHP 7 */ typedef struct _my_own_object { my_custom_type *my_buffer; zend_object zobj; } my_own_object;
      
      





PHP 5と比較した構造コンポーネントの順列に注意してください。これは何のためですか? zend_object



からzend_objectを読み取るzend_object



、my_own_objectを取得するには、構造内のzend_object



のオフセットを減算して、反対方向にメモリを取得するmy_own_object



があります。 これは、 OffsetOf()



を使用して行われます(必要であれば、簡単にエミュレートできます)。 これは高度なC構造の使用を検討していますが、使用している言語がよくわかっている場合(そうでない場合)は、おそらく既にこれをしている必要があります。



PHP 7でカスタムオブジェクトを取得するには、これを行う必要があります。



 /* PHP 7 */ zend_object *zobj; my_own_object *my_obj; zobj = Z_OBJ_P(this_zval); my_obj = (my_own_object *)((char *)zobj - XoffsetOf(struct my_own_object, zobj));
      
      





ここでoffsetof()



使用すると、混乱が生じoffsetof()



your_custom_struct



最後のコンポーネントはyour_custom_struct



なければなりzend_object



。 もちろん、この後に型を宣言すると、PHP 7でzend_object



の配置を整理するzend_object



これらの型にアクセスするのが難しくなります。



PHP 7では、 zend_object



が構造的ハックを使用するようになりました。 これは、割り当てられたメモリがsizeof(zend_object)



と異なることを意味します。 zend_object



配置:



 /* PHP 5 */ zend_object *zobj; zobj = ecalloc(1, sizeof(zend_object)); /* PHP 7 */ zend_object *zobj; zobj = ecalloc(1, sizeof(zend_object) + zend_object_properties_size(ce));
      
      





クラスは宣言された属性に関するすべてを知っているため、コンポーネントに割り当てる必要があるメモリのサイズを決定します。



オブジェクト作成



実際の例を考えてみましょう。 カスタムオブジェクトがあるとします。



 /* PHP 7 */ typedef struct _my_own_object { void *my_custom_buffer; zend_object zobj; /* MUST be the last element */ } my_own_object;
      
      





これはcreate_object()



ハンドラーのように見えるかもしれません:



 /* PHP 7 */ static zend_object *my_create_object(zend_class_entry *ce) { my_own_object *my_obj; my_obj = ecalloc(1, sizeof(my_obj) + zend_object_properties_size(ce)); my_obj->my_custom_buffer = emalloc(512); /* ,      512  */ zend_object_std_init(&my_obj->zobj, ce); /*      zend_object! */ object_properties_init(&my_obj->zobj, ce); my_obj->zobj.handlers = &my_class_handlers; /*     ,      */ return &my_obj->zobj; }
      
      





PHP 5とは異なり、割り当てられたメモリの量を忘れてはなりませんzend_object



プロパティを置き換える構造的なハックを思い出してzend_object



。 さらに、オブジェクトストレージはここでは使用されなくなりました。 PHP 5では、オブジェクト作成ハンドラーはそれをリポジトリーに登録し、オブジェクトの将来の破壊とリリースのためにいくつかの関数ポインターを渡す必要がありました。 PHP 7では、これを行う必要がなくなり、 create_object()



関数がより明確に機能します。



このカスタムハンドラを使用するには、拡張機能でcreate_object()



を宣言する必要があります。 この方法で、各ハンドラーを宣言します。



 /* PHP 7 */ zend_class_entry *my_ce; zend_object_handlers my_ce_handlers; PHP_MINIT_FUNCTION(my_extension) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "MyCustomClass", NULL); my_ce = zend_register_internal_class(&ce); my_ce->create_object = my_create_object; /*    */ memcpy(&my_ce_handlers, zend_get_std_object_handlers(), sizeof(my_ce_handlers)); my_ce_handlers.free_obj = my_free_object; /*  free */ my_ce_handlers.dtor_obj = my_destroy_object; /*  dtor */ /*   my_ce_handlers.clone_obj,        */ my_ce_handlers.offset = XtOffsetOf(my_own_object, zobj); /*       */ return SUCCESS; }
      
      





ご覧のとおり、MINITではfree_obj()



およびdtor_obj()



を宣言しています。 PHP 5では、オブジェクトをストレージに登録するときに、両方をzend_objects_store_put()



で宣言する必要がありますが、PHP 7ではこれは不要になりました 。 これでzend_object_std_init()



はオブジェクトをリポジトリ自体に書き込みます。これを手動で行う必要はありませんので、この呼び出しを忘れないでください。



そのため、ハンドラーfree_obj()



およびdtor_obj()



を登録し、メモリ内のカスタムオブジェクトの位置を計算するために使用されるオフセットコンポーネントも登録しました。 エンジンがこの情報を必要とするのは、今ではオブジェクトのリリースに従事しているのは彼であり、あなたではないからです 。 PHP 5では、通常free()



を使用して手動でこれを行う必要がありました。 エンジンが今それを行う場合、ポインタ全体を解放するには、 zend_object



タイプだけでなく、カスタム構造のoffset



値も取得する必要があります。 ここに例を見ることができます



オブジェクトの破壊



__destruct()



呼び出されるのと同じように、PHPのユーザーレベルでオブジェクトが破棄されるときにデストラクタが呼び出されることを思い出してください。 そのため、重大なエラーの場合、デストラクタはまったく呼び出されない可能性があり、PHP 7ではこの状況は変わりません。 冒頭で述べた投稿、またはこのプレゼンテーションを注意深く調べた場合、破壊されたオブジェクトは特定の状況で利用可能にならなければならないため、デストラクタはオブジェクトを不安定な状態のままにしないでください。 したがって、PHPでは、オブジェクトを破棄および解放するためのハンドラーは互いに分離されています。 リリースハンドラは、オブジェクトが他のどこでも使用されていないことをエンジンが完全に確認したときに呼び出されます。 オブジェクトのrefcountが0に達するとデストラクタが呼び出されますが、一部のユーザーコード( __destruct()



)を実行できるため、現在のオブジェクトは参照としてどこでも再利用できません。つまり、不安定な状態のままにする必要があります。 したがって、デストラクタを使用してメモリを解放する場合は非常に注意してください。 通常、デストラクタはリソースの使用を停止しますが、解放しません。 リリースハンドラーは既にこれを行っています。



したがって、デストラクタの作業を要約するには:



 /* PHP 7 */ static void my_destroy_object(zend_object *object) { my_own_object *my_obj; my_obj = (my_own_object *)((char *)object - XoffsetOf(my_own_object, zobj)); /*    -   my_obj->my_custom_buffer,     ,    ,   -.     . */ zend_objects_destroy_object(object); /*  __destruct()    */ }
      
      





オブジェクトストレージの解放



ストレージを解放する機能は、オブジェクトが他の場所で使用されていないことが確実な場合にエンジンによって開始されます。 オブジェクトが破壊される直前に、エンジンはfree_obj()



ハンドラーを呼び出します。 カスタムcreate_object()



ハンドラーにリソースを配置しましたか? それらを解放する時が来ました:



 /* PHP 7 */ static void my_free_object(zend_object *object) { my_own_object *my_obj; my_obj = (my_own_object *)((char *)object - XoffsetOf(my_own_object, zobj)); efree(my_obj->my_custom_buffer); /*    */ zend_object_std_dtor(object); /*   ,     */ }
      
      





それだけです。 PHP 5のように、自分でリリースする必要はありません。以前は、ハンドラーはfree(object)



ようなもので終了していました。 create_object()



ハンドラーはカスタムオブジェクト構造にスペースを割り当てますが、MINITでエンジンに値offset



渡すと、それを個別に解放する機会が得られます。 たとえば、次のようになります



もちろん、多くの場合、 free_obj()



ハンドラーはfree_obj()



ハンドラーの直後に呼び出されます。 例外は、ユーザー定義のデストラクタが$ thisを誰かに渡す場合、または設計が不十分なカスタム拡張オブジェクトの場合です。 エンジンでオブジェクトをリリースするときに完全なコードシーケンスに関心がある場合は、 zend_object_store_del()



について読んでください。



おわりに



PHP 7でオブジェクトの処理がどのように変化したかを見ました。 ユーザーレベルでは、すべてが実質的に変更されずに維持され、オブジェクトモデルのみが最適化されました。動作が速くなり、機能が少し増えました。 しかし、重要な革新はありません。



しかし、「ボンネットの下」にはさらに多くの変化があります。 それらは大きすぎず、何時間も勉強する必要はありませんが、それでもある程度の努力が必要です。 両方のオブジェクトモデルは低レベルで互換性がなくなったため、オブジェクトに関連する拡張機能のソースコードの一部を書き換える必要があります。 この投稿では、違いを説明しようとしました。 開発中にPHP 7に切り替えると、PHP 5に比べてより明確で構造化されていることに気付くでしょう。新しいバージョンは、10年にわたる重い遺産から解放されました。 PHP 7の多くの機能が改善され、場合によってはコードパッチが不要になりました。



All Articles