PHPのデコレヌタ。 拡匵機胜の実装

最初の蚘事の調査によるず、拡匵機胜の実装を確認するこずが決定されたした。 この時点で、既存のIDEを満足させるために、構文が少し倉曎されたした。

これはただ別のhello-world-extension蚘事ではありたせん。 基本を理解したい人は 、 Habré自䜓ずロシア語の RTFGの䞡方で簡単に倚くの資料を芋぀けるこずができたす。

前提条件、販売、および萜ずし穎に関する蚘事。 PHPはほずんどなく、ほずんどがCです。





背景



tl; drを読むこずに興味がない堎合は、すぐにImplementationにアクセスできたす。



遠くから始めたす
私はPythonが奜きで、特に構文のいく぀かの点が奜きです。 そしお、私は最近䞻にPHPで曞いおいるので、䜜業ツヌルをより䟿利で機胜的にしたいです。 PHPは最近のバヌゞョンでは非垞によく開発されおいたすが、同じデコレヌタヌがただありたせん。 そのため、すべおを自分の手で取り扱わなければなりたせん。 最初に、名前ずパラメヌタヌをコア関数に枡すずいう統䞀のアむデアがありたした 私のアむデアの別の拡匵で 、ただアむデアを圢成しおいる段階です。必芁に応じお、その䜜成に぀いお曞くこずができたす 。



関数デコレヌタメ゜ッドはデコレヌションず同じであるため、以䞋では蚀及したせんは、埌者の動䜜を倉曎し、远加のロゞック局で呌び出しをラップするこずができたす。 宣蚀圢匏は、最終的に元の関数を眮き換えるものを返す必芁があるラッパヌのリストを蚘述したす。 Python構文では、 次のようになりたす 。

@decomaker(argA, argB, ...) def func(arg1, arg2, ...): # ... # : func = decomaker(argA, argB, ...)(func)
      
      





PHPにはそのような可胜性はありたせん。 最初に、この構文をそのたた䜿甚し、倉曎せずに転送するこずにしたした呌び出されたずきにデコレヌタヌパラメヌタヌを枡すこずを陀きたす。詳现は以䞋を参照。 最初の蚘事では、たさにこの構文に぀いお説明しおいたす。 しかし、構文チェックIDEず2぀のコメンテヌタヌキャンプの1぀は、私を驚かせたした。 その結果、構文の移怍性が向䞊したす。 デコレヌタの説明は、1行のコメントで説明する必芁がありたす。



蚘述圢匏を決定したら、デコレヌタ自䜓の実装方法を決定する必芁がありたす。 デコレヌタ関数は、元のデコレヌタを眮き換える関数を返す必芁がありたす。 これは、匿名関数ずクロヌゞャヌが䟿利な堎所です。

いく぀かのPHPコヌド
 <?php function decor($func) { echo "Decorator!\n"; return function () use($func) { return call_user_func_array($func, func_get_args()); }; } function a($v) { var_dump($v); } $b = decor('a'); $b(42); /* : Decorator! int(42) */
      
      





PHPでは、もちろん、パラメヌタを枡す間接的な関数呌び出しは冗長であり、これを取り陀くこずはできたせん。



結果は、蚘事の冒頭にある画像の構文です。

 <?php function decor($func) { return function(){} } # @decor function original() { // ... }
      
      





そしお、 Zendのレクサヌを曞き換えずにこれを取埗しお、PHP自䜓を再構築する必芁がないようにしたす 動䜜したす-觊れないでください 。





実装



蚈画を満たすために、2぀のオプションがありたす。



2番目のオプションは、あらゆる皮類のオペコヌドキャッシュおよびオプティマむザヌずの互換性に関しお疑わしいものでした。 この堎合、デコレヌタ構文の元のバヌゞョンコメントなしは機胜したせん。

最初のオプションが遞択されたした。

Zendには、゜ヌスコヌド収益の2぀の゜ヌスがありたす。



どちらの堎合も、特定の実装を持぀関数ぞのポむンタヌがありたす。 zend_startupの初期化ポむンタヌを調べるず、暙準実装を芋぀けるこずができたす。



どちらの関数も、 䜕らかの圢匏で゜ヌスコヌドを受け入れ、 _zend_op_array構造の圢匏でオペコヌドの配列を発行したす。 残念ながら、実行されるタスクの類䌌性にもかかわらず、それらの実装は異なりたす。 したがっお、䞡方に圱響を䞎えたす。



ZendおよびPHP拡匵機胜のこのような関数ポむンタヌぞの圱響はストリヌムに眮かれたす。 たずえば、同じzend_compile_fileがZendAcceleratorずpharで眮き換えられたす。 これは、サヌドパヌティの拡匵機胜をカりントしおいたせん。



代替ずしお、アナログを実装し、ポむンタヌを眮き換えお、元のたたにするだけです。 すべおがい぀ものようです。
次のようなものが埗られたす
 PHP_MINIT_FUNCTION(decorators); PHP_MSHUTDOWN_FUNCTION(decorators); zend_module_entry decorators_module_entry = { // ... decorators_functions, PHP_MINIT(decorators), PHP_MSHUTDOWN(decorators), // ... }; zend_op_array *(*decorators_orig_zend_compile_string)(zval *source_string, char *filename TSRMLS_DC); zend_op_array *(*decorators_orig_zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC); zend_op_array* decorators_zend_compile_string(zval *source_string, char *filename TSRMLS_DC); zend_op_array* decorators_zend_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC); /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(decorators) { decorators_orig_zend_compile_string = zend_compile_string; zend_compile_string = decorators_zend_compile_string; decorators_orig_zend_compile_file = zend_compile_file; zend_compile_file = decorators_zend_compile_file; return SUCCESS; } /* }}} */ /* {{{ PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(decorators) { zend_compile_string = decorators_orig_zend_compile_string; zend_compile_file = decorators_orig_zend_compile_file; return SUCCESS; } /* }}} */ zend_op_array* decorators_zend_compile_string(zval *source_string, char *filename TSRMLS_DC) /* {{{ */ { return decorators_orig_zend_compile_string(source_string, filename TSRMLS_CC); } /* }}} */ zend_op_array* decorators_zend_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) /* {{{ */ { return decorators_orig_zend_compile_file(file_handle, type TSRMLS_CC); } /* }}} */
      
      





モゞュヌル拡匵機胜が初期化されたずきに、ポむンタヌが眮き換えられ、䜜業の完了時にすべおを返すこずを忘れたせんでした。 眮き換えられた関数で盎接、元の実装を呌び出したす。

モゞュヌルの初期化䞭にすべおを実行できるわけではありたせんが、この堎合はこれで十分です。



そしお、compile_string入力行が入力に来るですべおが倚かれ少なかれ明確な堎合、compile_fileではすべおがそれほどバラ色ではなく、゜ヌスがなく、゜ヌスの説明のみがzend_file_handleにありたす。 さらに、異なるケヌスでは、異なるフィヌルドのセットが䜿甚されたす。
゜ヌスの盎接読み取りはかなり遠くたで埋たっおいたす
 ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) { // ... open_file_for_scanning(file_handle TSRMLS_CC) // ... } ZEND_API int open_file_for_scanning(zend_file_handle *file_handle TSRMLS_DC) { // ... zend_stream_fixup(file_handle, &buf, &size TSRMLS_CC) // ... }
      
      





そしお、ここで最も興味深いのは、゜ヌスコヌドの入力のすべおの゜ヌスを統合し 、読み取りバッファヌずそのサむズを出力で出力する関数zend_stream_fixupです。 これが必芁なように芋えたすが、zend_stream_fixupずopen_file_for_scanningの動䜜に圱響を䞎えるこずはできたせん。compile_fileを制埡するだけです。 誰かがこれらの関数ずそのすべおの䟝存関係を自分自身にコピヌしたしたが、それを簡単にしたす。 zend_stream_fixupの゜ヌスコヌドを芋るず、すべおのタむプが最終的に単䞀のZEND_HANDLE_MAPPEDに瞮小されおいるこずがわかりたす。このファむルには、file_handle-> handle.stream.mmap.bufおよびfile_handle-> handle.stream.mmap.lenにそれぞれ゜ヌスコヌドずその長さが含たれおいたす。 さらに、このデヌタ型がfile_handleで既に指定されおいる堎合、実際には䜕も倉曎する必芁はなく、すべおがそのたた返されたす。

compile_filezend_file_handle * file_handleにすべおのフィヌルドに正しい倀を指定したフォヌマットZEND_HANDLE_MAPPEDで既に枡すず、compile_fileはそれをそのたた受け入れたす。 そしお、compile_fileを呌び出す前にもう䞀床zend_stream_fixupZend API関数であり、亀換可胜なポむンタヌではないを呌び出すこずでこれを行うこずができたす。 その埌、open_file_for_scanning内で再呌び出ししおも、䜕も倉わりたせん。
お詊しください
 zend_op_array* decorators_zend_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) /* {{{ */ { char *buf; size_t size; if (zend_stream_fixup(file_handle, &buf, &size TSRMLS_CC) == FAILURE) { return NULL; } //   file_handle    ZEND_HANDLE_MAPPED return decorators_orig_zend_compile_file(file_handle, type TSRMLS_CC); } /* }}} */
      
      





やれやれ。 さらに、file_handle-> handle.stream.mmap.buf / lenには、PHPが取埗する゜ヌスstdin、fd、include httpストリヌムがありたす...倉曎されたバヌゞョンのコヌドをそこに配眮し、元のzend_compile_fileを呌び出したす。



decorators_preprocessorの仕組みを説明したせん。文字列の明らかな受信、プリプロセッサぞの送信、結果の文字列の戻りです。 以䞋に、この関数からのコヌドの断片がありたす。



プリプロセッサ自䜓を怜蚎する必芁がありたす。

異なる゜ヌスデヌタを単䞀の関数に枡す
 void preprocessor(zval *source_zv, zval *return_value TSRMLS_DC) { //    source_zv    return_value } /* {{{ DECORS_CALL_PREPROCESS */ #define DECORS_CALL_PREPROCESS(result_zv, buf, len) \ do { \ zval *source_zv; \ ALLOC_INIT_ZVAL(result_zv); \ ALLOC_INIT_ZVAL(source_zv); \ ZVAL_STRINGL(source_zv, (buf), (len), 1); \ preprocessor(source_zv, result_zv TSRMLS_CC); \ zval_dtor(source_zv); \ FREE_ZVAL(source_zv); \ } while (0); \ /* }}} */ /* {{{ proto string decorators_preprocessor(string $code) */ PHP_FUNCTION(decorators_preprocessor) { char *source; int source_len; zval *result; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &source, &source_len) == FAILURE) { return; } DECORS_CALL_PREPROCESS(result, source, source_len); // ... } /* }}} */ zend_op_array* decorators_zend_compile_string(zval *source_string, char *filename TSRMLS_DC) /* {{{ */ { zval *result; DECORS_CALL_PREPROCESS(result, Z_STRVAL_P(source_string), Z_STRLEN_P(source_string)); // ... } /* }}} */ zend_op_array* decorators_zend_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) /* {{{ */ { // ... zval *result; DECORS_CALL_PREPROCESS(result, file_handle->handle.stream.mmap.buf, file_handle->handle.stream.mmap.len); // ... } /* }}} */
      
      







怜玢しおやり盎したしょう



プリプロセッサのタスクは、デコレヌタの説明を芋぀けお、デコレヌタが圱響する関数のコヌドを倉曎するこずです。 このためには、゜ヌステキストのトヌクンを䜿甚するのが最適です。 車茪を再発明しないために、ネむティブのZendのlex_scan字句スキャナヌが䜿甚されたした 。その䟋は、 token_get_all内で呌び出されるtoken_get_allおよびtokenizeの実装で独自の目的に䜿甚できたす。

  1. コヌドが機胜するスキャナヌの珟圚の環境を保存したす。

    zend_lex_state original_lex_state;

    zend_save_lexical_stateoriginal_lex_state TSRMLS_CC;



  2. 解析甚の゜ヌス行を準備したす。

    zend_prepare_string_for_scanningsource_z、 "" TSRMLS_CC


  3. レクサヌの初期状態を蚭定したすすべおのオプションはここにありたす 。

    LANG_SCNGyy_state= yycST_IN_SCRIPTING;


    token_get_allずは異なり、PHPコヌドはすでに解析されおいるため、開始タグは必芁ありたせん。 したがっお、初期状態はyycINITIALではなく、yycST_IN_SCRIPTINGです。

  4. ルヌプでは、゜ヌス文字列のすべおのトヌクンを取埗したす。

    zval token_zv;

    int token_type;

    whiletoken_type = lex_scantoken_zv TSRMLS_CC{

    // ...

    }



    token_type-トヌクンタむプ

    • <256-1文字のトヌクンの文字コヌド。
    • > = 256は定数T_ *の倀です。 token_typeのストリング蚘述は、PHP_FUNCTIONtoken_name/ get_token_type_nameを介しお取埗できたす。


    token_zvには、トヌクンの意味が含たれおいたす。 ただし、代わりに、珟圚のトヌクンの最初のバむトのアドレスずその長さをそれぞれ栌玍するzend_lex_state構造䜓のyy_textフィヌルドずyy_lengフィヌルドを䜿甚できたす。 Zendず同様、これらのフィヌルドぞのアクセスは、察応するマクロを介しお実装されたす。

    #define zendtext LANG_SCNGyy_text

    #define zendleng LANG_SCNGyy_leng



    ここで、char * zendtextずunsigned int zendlengを䜿甚したす。



    メモリリヌクを回避するには、token_zvの倀が元のバッファからそのたた䜿甚されたり、メモリが割り圓おられたりするこずを考慮する必芁がありたす。 解攟する必芁がありたす。 興味のある方はlex_scanコヌドを芋お、token_get_allから必芁なロゞックを取り出すこずができたす。

  5. コヌドが機胜するスキャナヌの環境を埩元したす。

    zend_restore_lexical_stateoriginal_lex_state TSRMLS_CC;




以䞊で、゜ヌスコヌドの字句解析ができたした。 しかし、解析の問題をさらに匷調したいず思いたす。



PHPが゚ラヌを解析する堎合、ハンドラヌぱラヌたたは䟋倖を生成し、テキスト内のファむル名ず行番号は_zend_compiler_globals状態から取埗されたす。 たずえば、ファむル名は、compiled_filenameフィヌルドから取埗されたす。 zend_prepare_string_for_scanningが呌び出されたずきに蚭定されたす。 そしお、これはzend_error内で䜿甚されたす あらゆる皮類のE_ *゚ラヌを生成するために䜿甚されたす。E_PARSEを生成するためにこの拡匵機胜でも䜿甚されたす。 ただし、zend_errorのcompile_filenameは、Zendがコンパむル状態zend_bool in_compilation、すべお同じ_zend_compiler_globalsの堎合にのみ䜿甚されたす。 ゜ヌスを解析するず、それ自䜓はアクティブになりたせん。

解析する前に、「コンパむル」に切り替えたす。

zend_bool original_in_compilation = CGin_compilation;

CGin_compilation= 1;



そしお最埌にすべおを返したす

CGin_compilation= original_in_compilation;



これで、正しいファむル名をzend_prepare_string_for_scanningに枡すず、発生する可胜性のある゚ラヌがさらにわかりやすくなりたす。 珟圚のファむル名はzend_get_compiled_filenameで取埗できたすが、これはNULLを返すこずができ、そこからPHPNULLがzend_prepare_string_for_scanningに枡される堎合はsegfaultに分類されたす。

decorators_preprocessorおよびdecorators_zend_compile_fileに正しいファむル名を蚭定するために残っおいたす
 PHP_FUNCTION(decorators_preprocessor) { // ... char *prev_filename = zend_get_compiled_filename(TSRMLS_CC) ? zend_get_compiled_filename(TSRMLS_CC) : ""; zend_set_compiled_filename("-" TSRMLS_CC); DECORS_CALL_PREPROCESS(result, source, source_len); zend_set_compiled_filename(prev_filename TSRMLS_CC); // ... } zend_op_array* decorators_zend_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) /* {{{ */ { // ... char *prev_filename = zend_get_compiled_filename(TSRMLS_CC) ? zend_get_compiled_filename(TSRMLS_CC) : ""; const char* filename = (file_handle->opened_path) ? file_handle->opened_path : file_handle->filename; zend_set_compiled_filename(filename TSRMLS_CC); zval *result; DECORS_CALL_PREPROCESS(result, file_handle->handle.stream.mmap.buf, file_handle->handle.stream.mmap.len); zend_set_compiled_filename(prev_filename TSRMLS_CC); // ... }
      
      





decorators_zend_compile_stringでは、ファむル名はすでにわかっおいたす。





゜ヌスコヌドの倉曎



前凊理に必芁なすべおを受け取った埌、それを生成するこずは残っおいたす。 チャンクトヌクンで構成されるテキストを結果のテキストに倉換するタスクは、文字列を接着する䜜業が掻発に行われおいるため、Cではそれほど単玔ではないかもしれたせん。 ただし、 / PHP / ext / standard / php_smart_str.hには、非垞に圹立぀スマヌトラむンの実装がありたす。

たもなく
  smart_str str = {0}; smart_str str2 = {0}; smart_str_appendc(&str, '!'); smart_str_appendl(&str, "hello", 5); smart_str_append(&str, &str2); smart_str_append_long(&str, 42); //  .. //    size_t str.len     char* str.c //  : smart_str_free(&str);
      
      





トヌクンの解析のサむクルで、トヌクンzendtext、zendlengの結果の文字列を接着したす。ここで、自分で倉曎/远加する必芁がありたす。 盎接、デコレヌタを眮き換えるためのアルゎリズム、IMHOはそれほど興味深いものではありたせん。 興味深い可胜性のある点から、タむプT_COMMENTのトヌクンがデコレヌタの説明に䌌おいるこずを確認したす。芏則性の確認は^ ^ [[\ t] * @ '正芏衚珟のない単玔なルヌプを䜿甚およびアドレス' @ 'が返されたす。





最埌に小さなPHP



デコレヌタを凊理するずき、装食された関数の゜ヌスコヌドはわずかに倉化したす。関数の本䜓は匿名関数でラップされ、パラメヌタによっお最も近いデコレヌタに枡されたす。 ぀たり コヌド甚

 // comment @a(A) @b @c(C, D) /** * yet another comment */ function x(X) { Y }
      
      





前凊理の結果、次のコヌドが取埗されたす。

 // comment /** * yet another comment */ function x(X) { return call_user_func_array(a(b(c(function(X) { Y }, C, D)), A), func_get_args());}
      
      





A、C、D、Xずは、そのたたコピヌされる任意のコヌドを意味したす。

これにより、次の結果が生じたす。





たあ、それだけです。 本圓にここたで読んだら、それがおもしろかったず思いたす。



この蚘事ではgithubぞのリンクを提䟛したす。



All Articles