今日、PHP開発者はCレベルのAPIに取り組んでいますが、この記事では、PHPの内部開発について主に説明しますが、ストーリーの途中でユーザーレベルの観点から何か面白いことがあれば、脱線して説明します。
PHP 5と比較したオブジェクトの変更
PHPのオブジェクトのトピックを完全に理解するには、まずPHPのオブジェクトとクラスに関する詳細な投稿を読むことをお勧めします。
それで、5番目と比較して7番目のバージョンでは何が変わったのでしょうか?
- ユーザーレベルでは、ほとんど変更はありません。 言い換えると、PHP 7では、オブジェクトはPHP 5と同じままでした。深い変更は行われず、日常の作業で気付くことはありませんでした。 オブジェクトの動作はまったく同じです。 何も変更されていないのはなぜですか? オブジェクトモデルは成熟しており、非常に積極的に使用されていると考えており、新しいバージョンのPHPで混乱を招く必要はないと考えています。
- しかし、まだいくつかの低レベルの改善がありました。 変更はわずかですが、拡張パッチが必要です。 原則として、PHP 7では、内部オブジェクトはPHP 5よりもはるかに表現力があり、明確で、論理的です。最も重要な革新は、PHP 7の主な変更、zvalの更新とガベージコレクションの管理に関連しています。 しかし、この投稿ではトピックについてはオブジェクトであるため、後者については考慮しません。 ただし、新しいzvalとガベージコレクションメカニズムの性質がオブジェクトの内部管理に影響することを覚えておく必要があります。
オブジェクト構造とメモリ管理
まず、
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()
)を実行できるため、現在のオブジェクトは参照としてどこでも再利用できません。つまり、不安定な状態のままにする必要があります。 したがって、デストラクタを使用してメモリを解放する場合は非常に注意してください。 通常、デストラクタはリソースの使用を停止しますが、解放しません。 リリースハンドラーは既にこれを行っています。
したがって、デストラクタの作業を要約するには:
- まったく呼び出されないか 、1回(ほとんどの場合)呼び出されますが、 複数回呼び出されることはありません。 重大なエラーの場合、デストラクタは呼び出されません。
- デストラクタはリソースを解放しません。これは、まれにエンジンがオブジェクトを再利用できる場合があるためです。
- カスタムデストラクタから
zend_objects_destroy_object()
を呼び出さない場合、カスタム__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の多くの機能が改善され、場合によってはコードパッチが不要になりました。