国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

PHP的生成器

LMou / 2260人閱讀

摘要:它最簡單的調用形式看起來像一個申明,不同之處在于普通會返回值并終止函數的執行,而會返回一個值給循環調用此生成器的代碼并且只是暫停執行生成器函數。

0x01 寫在前面

本文主要介紹:

Generator的簡單用法。

Generator的底層實現。

本文比較長,可能會耗費你比較多的時間。如果你比較了解Generator的用法,僅想了解底層實現,可以直接跳到底層實現部分。

本文分析的PHP源碼版本為:7.0.29。

目錄

0x01 寫在前面

0x02 Generator的用法

2.1 生成器總覽

2.2 生成器對象

2.3 生成器語法

2.3.1 yield關鍵字

2.3.2 yield from

2.4 Generator類

2.5 Generator方法

2.5.1 Generator::__wakeup()

2.5.2 Generator::send()

2.5.3 Generator::throw()

0x03 生成器的底層實現

3.1 Generator類的注冊及其存儲結構

3.2 zend_generator結構體

3.3 生成器對象的創建

3.3.1 編譯階段

3.3.2 執行階段

3.4 yield生成值

3.5 生成器對象的訪問

3.5.1 使用生成器對象接口訪問

3.5.2 使用foreach訪問

3.6 生成器的終止

3.7 小結

此文為個人的學習筆記,意在對自己的學習過程進行總結。由于個人能力有限,錯漏在所難免,歡迎批評指正。
0x02 Generator的用法

Generator,中文翻譯:生成器,是PHP 5.5開始支持的語法。

2.1 生成器總覽
生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 接口的方式,性能開銷和復雜性大大降低。

生成器允許你在 foreach 代碼塊中寫代碼來迭代一組數據而不需要在內存中創建一個數組, 那會使你的內存達到上限,或者會占據可觀的處理時間。相反,你可以寫一個生成器函數,就像一個普通的自定義函數一樣, 和普通函數只返回一次不同的是, 生成器可以根據需要 yield 多次,以便生成需要迭代的值。

2.2 生成器對象
當一個生成器的函數的被調用時,對返回內置類Generator的一個實例化對象。這個對象實現了Iterator接口,跟迭代器一樣可以向前迭代,并且提供了維護這個對象的狀態的接口,包括向它發送值和從它接收值。
2.3 生成器語法
一個生成器函數看起來像一個普通的函數,不同的是普通函數返回一個值,而一個生成器可以yield生成許多它所需要的值。

當一個生成器被調用的時候,它返回一個可以被遍歷的對象.當你遍歷這個對象的時候(例如通過一個foreach循環),PHP 將會在每次需要值的時候調用生成器函數,并在產生一個值之后保存生成器的狀態,這樣它就可以在需要產生下一個值的時候恢復調用狀態。

一旦不再需要產生更多的值,生成器函數可以簡單退出,而調用生成器的代碼還可以繼續執行,就像一個數組已經被遍歷完了

PHP 5是不可以有返回值的,如果這樣做會導致編譯錯誤。但是一個空的return語句是可以的,這會終止生成器的執行。PHP 7支持返回值,使用Generator::getReturn()獲取返回值。

2.3.1 yield關鍵字
生成器函數的核心是yield關鍵字。它最簡單的調用形式看起來像一個return申明,不同之處在于普通return會返回值并終止函數的執行,而yield會返回一個值給循環調用此生成器的代碼并且只是暫停執行生成器函數。

理論顯得空洞無力,show me your code,那就來看一段簡單的代碼,以便更容易理解生成器語法:

代碼片段2.1:


說明:

執行$generator = gen_one_to_three();,這時不會執行生成器函數gen_one_to_three()里面的代碼,而是返回一個生成器對象,也就是說$generator是一個生成器對象。

foreach ($generator as $value)遍歷生成器對象,因為Generator實現了Iterator接口,可以用foreach進行迭代。這時就會調用生成器函數gen_one_to_three(),于是執行gen_one_to_three()的代碼。

因為是首次調用,所以從開始執行,執行for循環,此時$i=1,執行到yield $i;相當于生成了一個值1,并且保存了當前的狀態(比如$i=1、執行yield $i;這里)并暫停執行。

foreach獲取到這個值1,并echo輸出。

繼續遍歷foreach,這是會調用生成器函數,并恢復從上次保存的狀態(包括變量值,和執行到的位置)繼續執行,$i++,這是$i=2

for循環繼續執行,再次執行yield $i;相當于生成一個值2,并且保存了當前的狀態并暫停執行。

foreach獲取到這個值2,并echo輸出。

foreach繼續執行,繼續調用生成器函數,這是$i++,$i=3,執行yield $i;生成一個值3給$value并輸出$value。

foreach繼續執行,但是生成器函數沒有生成值了(valid()返回false),所以結束foreach遍歷。

2.3.2 yield from
PHP 7允許您使用yield from關鍵字從另一個生成器、Traversable對象或數組中生成值(后面簡稱委托對象),這叫生成器委托。 生成器將從內嵌生成器、對象或數組中生成所有值,直到它不再有效,然后繼續生成器的執行。

代碼片段2.3.2

 
上例會輸出:

1 2 3 4 5 6 7 8 9 10 

以上的引用內容來自于PHP幫助手冊,例子也基本來自手冊,我只是加了一些說明,以便幫助更好的理解其語法。

2.4 Generator類

前面說Generator類實現了Iterator接口,那到底有哪些成員方法呢?

Generator implements Iterator {
    public mixed current ( void )
    public mixed key ( void )
    public void next ( void )
    public void rewind ( void )
    public mixed send ( mixed $value )
    public void throw ( Exception $exception )
    public bool valid ( void )
    public void __wakeup ( void )
}

Generator比起Iterator接口,增加了send()throw()以及__wakeup()方法。

既然實現了Iterator接口,那上面的代碼片段2.3.1也可以改成下面的,執行結果一樣的:

代碼片段2.4.1

valid()) {
    echo "{$generator->current()}
";
    $generator->next();
}

2.5 Generator方法 2.5.1 Generator::__wakeup()

這是一個魔術方法,當一個對象被反序列化時會調用,但生成器對象不能被序列化和反序列化,所以__wakeup()方法拋出一個異常以表示生成器不能被序列化。

2.5.2 Generator::send()

前面生成器對象部分提到:可以從生成器對象接收值和向它發送值。yield就是從它接收值,那發送值是什么呢?就是這個send()方法。

PHP幫助文檔的介紹:

public mixed Generator::send ( mixed $value )
向生成器中傳入一個值,并且當做 yield 表達式的結果,然后繼續執行生成器。 

如果當這個方法被調用時,生成器不在 yield表達式,那么在傳入值之前,它會先運行到第一個 yield 表達式.

先來理解第一段話:

向生成器中傳入一個值,并且當做 yield 表達式的結果,然后繼續執行生成器。

yield后生成了值,還可以用這個生成器對象的send()方法發送一個值,而這個值作為表達式的結果,然后在生成器函數里面可以獲取到這個值,接著繼續執行生成器。看下面的代碼:

代碼片段2.5.1

send("exit");
}

說明:

執行$generator = gen_one_to_three();,這時不會執行生成器函數gen_one_to_three()里面的代碼,而是返回一個生成器對象,也就是說$generator是一個生成器對象。

foreach ($generator as $value)遍歷生成器對象,因為Generator實現了Iterator接口,可以用foreach進行迭代。這時就會調用生成器函數gen_one_to_three(),于是執行gen_one_to_three()的代碼。

因為是首次調用,所以從開始執行,執行for循環,此時$i=1,執行到$cmd = (yield $i);相當于生成了一個值1,并且保存了當前的狀態(比如$i=1、執行yield $i;這里)并暫停執行。

foreach獲取到這個值1,賦給$value,并echo輸出。

執行$generator->send("exit");向生成器函數里面發送值"exit"

生成器函數拿到這個值"exit",作為yield $i;表達式的值,然后賦給$cmd,也就是$cmd = (yield $i);相當于$cmd = "exit";,繼續執行生成器函數

if ($cmd === "exit")條件成立,所以執行return,終止生成器函數的運行。

接下來,看看第二段話:

如果當這個方法被調用時,生成器不在 yield表達式,那么在傳入值之前,它會先運行到第一個 yield 表達式。

也就是說不一定用foreach來執行生成器函數,send()也可以,直到遇到第一個yield表達式,后面步驟就按照第一段話的步驟處理。

2.5.3 Generator::throw()

向生成器中拋入一個異常。

代碼片段2.4

throw(new Exception("test"));
}

說明:

執行$generator = gen_one_to_three();,這時不會執行生成器函數gen_one_to_three()里面的代碼,而是返回一個生成器對象,也就是說$generator是一個生成器對象。

foreach ($generator as $value)遍歷生成器對象,因為Generator實現了Iterator接口,可以用foreach進行迭代。這時就會調用生成器函數gen_one_to_three(),于是執行gen_one_to_three()的代碼。

因為是首次調用,所以從開始執行,執行for循環,此時$i=1,執行到yield $i;相當于生成了一個值1,并且保存了當前的狀態(比如$i=1、執行yield $i;這里)并暫停執行。

foreach獲取到這個值1,并echo輸出。

執行$generator->throw(new Exception("test"));,相當于在生成器函數yield $i;處拋出了一個異常new Exception("test")

這節只簡單介紹了生成器類Generator的用法,如果想要實現更復雜的功能,比較推薦鳥哥翻譯的《在PHP中使用協程實現多任務調度》。

0x03 生成器的底層實現

從前面幾節我們初步知道生成器函數跟別的函數不一樣,普通函數在返回返回時,除了靜態變量外其他的都會被銷毀,下次進來還是新的狀態,也就是不會保存狀態值,但生成器函數每次yield是會保存狀態,包括變量值和運行位置,下次調用時從上次運行的位置后面繼續運行。了解Generator的運行機制,需要對Zend VM有一定了解,可以先閱讀這篇文章《Zend引擎執行流程》。

從PHP語法層面分析,底層實現應該具有:

Generator實現了迭代器接口

生成器函數調用時返回生成器對象

yield后會保存函數的局部遍歷和運行位置(內存不會被銷毀)

下面,我們從源碼分析Generator的底層實現。

本節注意

代碼中// ...表示省略一部分代碼。

代碼中會加一些注釋說明,以便更好地了解代碼。

Zend/xxx.c:767-864表示Zend目錄下的xxx.c文件,行數為767864行。

3.1 Generator類的注冊及其存儲結構

先從數據結構入手,類和對象底層的結構分別為:zend_class_entryzend_object。類產生在是編譯時,而對象產生是在運行時。Generator是一個內置類,具有跟其他類共同的性質,但也有自己不同的特性。

本文不會介紹類和對象的內部實現,感興趣的可以閱讀《面向對象實現-類》和《面向對象實現-對象》。如果你對這些知識不太了解,請先閱讀上面兩篇文章,以便更好地理解后面的內容。

內置類在PHP模塊初始化(MINIT)的時候就注冊了。調用路徑為:ZEND_MINIT_FUNCTION(core) -> zend_register_default_classes() -> zend_register_generator_ce():

代碼片段3.1.1

void zend_register_generator_ce(void) /* {{{ */
{
    zend_class_entry ce;

    INIT_CLASS_ENTRY(ce, "Generator", generator_functions); // 初始化Generator類,主要其方法
    zend_ce_generator = zend_register_internal_class(&ce);  // 注冊為內部類
    zend_ce_generator->ce_flags |= ZEND_ACC_FINAL; // 設置為final類,表示不能被繼承。
    /* 下面3個函數時鉤子函數,內部類用到,用戶自定義的會使用默認函數 */
    zend_ce_generator->create_object = zend_generator_create; // 創建對象
    zend_ce_generator->serialize = zend_class_serialize_deny; // 序列化,zend_class_serialize_deny表示不能序列化
    zend_ce_generator->unserialize = zend_class_unserialize_deny; // 反序列化,zend_class_unserialize_deny表示不能反序列化

    /* get_iterator has to be assigned *after* implementing the inferface */
    zend_class_implements(zend_ce_generator, 1, zend_ce_iterator); // 實現zend_ce_iterator類,也就是Iterator
    zend_ce_generator->get_iterator = zend_generator_get_iterator;  // 遍歷方法,這也是個鉤子方法,用戶自定義的使用默認的
    zend_ce_generator->iterator_funcs.funcs = &zend_generator_iterator_functions; // 遍歷相關的方法(valid/next/current等)使用自己的

    /* 下面幾個是對象(Generator類的實例)相關的 */
    memcpy(&zend_generator_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); // 先使用默認的,后面的相應覆蓋
    zend_generator_handlers.free_obj = zend_generator_free_storage; // 釋放
    zend_generator_handlers.dtor_obj = zend_generator_dtor_storage; // 銷毀
    zend_generator_handlers.get_gc = zend_generator_get_gc; // 垃圾回收相關
    zend_generator_handlers.clone_obj = NULL; // 克隆。禁止克隆
    zend_generator_handlers.get_constructor = zend_generator_get_constructor; // 構造

    INIT_CLASS_ENTRY(ce, "ClosedGeneratorException", NULL);
    zend_ce_ClosedGeneratorException = zend_register_internal_class_ex(&ce, zend_ce_exception);
}

代碼片段3.1.1可以看出:

Generator類實現了Iterator接口,但有些方法和Iterator默認的方法不太一樣。比如不能序列化/反序列化、遍歷方法(getIterator)不一樣等。

Generator類不能被繼承。

Generator類的實例不能被克隆等。

3.2 zend_generator結構體

在介紹后面的內容之前,我覺得有必要先了解zend_generator這個結構體,因為底層代碼基本都是圍繞著這個結構體來開展的。

代碼片段3.2.1

typedef struct _zend_generator zend_generator;
struct _zend_generator {
    zend_object std;
    zend_object_iterator *iterator;
    /* 生成器函數的execute_data */
    zend_execute_data *execute_data;
    /* VM stack */
    zend_vm_stack stack;
    /* 當前元素的值 */
    zval value;
    /* 當前元素的鍵 */
    zval key;
    /* 返回值 */
    zval retval;
    /* 用來保存send()的值 */
    zval *send_target;
    /* 當前使用的最大自增key */
    zend_long largest_used_integer_key;
    /* yield from才用到,數組和非生成器的Traversables類用到,后面會介紹 */
    zval values;
    /* Node of waiting generators when multiple "yield *" expressions are nested. */
    zend_generator_node node;
    /* Fake execute_data for stacktraces */
    zend_execute_data execute_fake;
    /* 標識 */
    zend_uchar flags;
};

重點介紹幾個重要的:

execute_data:生成器函數的上下文execute_data,包括當前運行到的位置、變量等狀態信息,底層EX宏就是訪問這個結構的成員。如果這個為NULL,則表明該生成器已經結束,也就是沒有更多的值生成了。當生成器函數return時(沒有顯式return底層默認return NULL),execute_data變為NULL,后面會介紹。

vm_stack:VM棧,這個會在《3.3 生成器對象的創建》中詳細介紹。

key:當前元素的key,每次yield都會更新此值,如果yield沒有指定key(也就是yield $key => $value形式),則使用largest_used_integer_key值。

value:當前元素的value,也就是生成的值,每次yield都會更新此值。

retval:生成器的返回值,也就是return返回的值,可以通過Generator::getReturn()獲取。

largest_used_integer_key:存儲當前已使用的自增key,yield沒有指定key時使用下一個自增值。

send_target:send()的值就存放在這里。

values:yield from委托對象時用到;yield from生成器不會存儲在這里,使用后面的node存儲關系。

node:存儲生成器與其委托對象的關系,這個數據結構有點復雜,暫時不做介紹。

3.3 生成器對象的創建

從生成器語法可以看出,生成器函數(方法)具有:

必須是個函數

函數有yield關鍵字

調用生成器函數返回生成器對象

3.3.1 編譯階段

先從編譯PHP代碼開始分析,PHP7會先把PHP代碼編譯成AST(Abstract Syntax Tree,抽象語法生成樹),然后再生成opcode數組,每條opcode就是一條指令,每條指令都有相應的處理函數(handler)。這里面細講起來篇幅很長,建議閱讀《PHP代碼的編譯》、《詞法解析、語法解析》和《抽象語法樹編譯流程》這幾篇文章。

先來看第一個特征:必須是個函數。函數的編譯,比較復雜,不是本文的重點,需要了解可以閱讀《函數實現》。函數的開始先標識CG(active_op_array),展開是compiler_globals.active_op_array,這是一個zend_op_array結構,在PHP中,每一個也就是獨立的代碼段(函數/方法/全局代碼段)都會編譯成一個zend_op_array,生成的opcode數組就存在zend_op_array.opcodes

再來看第二個特征:函數有yield關鍵字。在詞法語法分析階段,如果遇到函數里面的表達式有yield,則會標識為生成器函數。看詞法語法過程,在Zend/zend_language_parser.y:855

代碼片段3.3.1

expr_without_variable:
        T_LIST "(" assignment_list ")" "=" expr
            { $$ = zend_ast_create(ZEND_AST_ASSIGN, $3, $6); }
    |    variable "=" expr
            { $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); }        
// ...

    |    T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); } // 958行 
    |    T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); }
    |    T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); }
    |    T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); }

從定義可以看出yield允許以下三種語法:

yield

yield value

yield key => value

第一種沒有寫返回值,則默認返回值為NULL;第二種僅僅返回value,key則為自增的key;第三種返回自定義的key和value。

詞法語法分析器掃描到yield會調用zend_ast_create()函數(Zend/zend_ast.c:135-144),得到類型(zend_ast->kind)為ZEND_AST_YIELD或者ZEND_AST_YIELD_FROM的zend_ast結構體。從代碼片段3.3.1可以看出:T_YIELD/T_YIELD_FROM會被當成expr_without_variable,也就是表達式。接著,我們看看表達式的編譯,在Zend/zend_compile.c:1794zend_compile_expr()函數:

代碼片段3.3.2

void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
{
    /* CG(zend_lineno) = ast->lineno; */
    CG(zend_lineno) = zend_ast_get_lineno(ast);

    switch (ast->kind) {
        case ZEND_AST_ZVAL:
            ZVAL_COPY(&result->u.constant, zend_ast_get_zval(ast));
            result->op_type = IS_CONST;
    // ...
        case ZEND_AST_YIELD: // 7272行
            zend_compile_yield(result, ast);
            return;
        case ZEND_AST_YIELD_FROM:
            zend_compile_yield_from(result, ast);
            return;
    // ...
}
/* }}} */

yield調用的zend_compile_yield(result, ast)函數,yield from調用的zend_compile_yield_from(result, ast)函數,這兩個函數都會調用zend_mark_function_as_generator(),在Zend/zend_compile.c:1145

代碼片段3.3.3

static void zend_mark_function_as_generator() /* {{{ */
{
    /* 判斷是不是函數/方法,不是就報錯,也就是yield必須在函數/方法內 */
    if (!CG(active_op_array)->function_name) {
        zend_error_noreturn(E_COMPILE_ERROR,
            "The "yield" expression can only be used inside a function");
    }
    
    /* 如果有標識返回類型,則判斷返回類型是否正確,只能是Generator及其父類(Traversable/Iterator) */
    if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
        const char *msg = "Generators may only declare a return type of Generator, Iterator or Traversable, %s is not permitted";
        if (!CG(active_op_array)->arg_info[-1].class_name) {
            zend_error_noreturn(E_COMPILE_ERROR, msg,
                zend_get_type_by_const(CG(active_op_array)->arg_info[-1].type_hint));
        }
        if (!(ZSTR_LEN(CG(active_op_array)->arg_info[-1].class_name) == sizeof("Traversable")-1
                && zend_binary_strcasecmp(ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name), sizeof("Traversable")-1, "Traversable", sizeof("Traversable")-1) == 0) &&
            !(ZSTR_LEN(CG(active_op_array)->arg_info[-1].class_name) == sizeof("Iterator")-1
                && zend_binary_strcasecmp(ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name), sizeof("Iterator")-1, "Iterator", sizeof("Iterator")-1) == 0) &&
            !(ZSTR_LEN(CG(active_op_array)->arg_info[-1].class_name) == sizeof("Generator")-1
                && zend_binary_strcasecmp(ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name), sizeof("Generator")-1, "Generator", sizeof("Generator")-1) == 0)) {
            zend_error_noreturn(E_COMPILE_ERROR, msg, ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name));
        }
    }

    CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR; // 標識函數是生成器類型!!!
}
/* }}} */
3.3.2 執行階段

前兩個特征都是在編譯階段,生成器函數編譯完,得到的opcode為DO_FCALL/DO_FCALL_BY_NAME,解析opcode,得到對應的處理函數(handler)為ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER/ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER,這兩個函數對于生成器處理基本是相同的,最終會調用zend_generator_create_zval()函數:

代碼片段3.3.4

ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array *op_array, zval *return_value) /* {{{ */
{
    zend_generator *generator;
    zend_execute_data *current_execute_data;
    zend_execute_data *execute_data;
    zend_vm_stack current_stack = EG(vm_stack); // 保存當前的vm_stack,以便后面恢復

    current_stack->top = EG(vm_stack_top);

    /* 先保存當前執行的execute_data,后面恢復 */
    current_execute_data = EG(current_execute_data);
    execute_data = zend_create_generator_execute_data(call, op_array, return_value); // 創建新的execute_data
    EG(current_execute_data) = current_execute_data; // 恢復之前的execute_data

    object_init_ex(return_value, zend_ce_generator); // 實例化生成器對象,賦給return_value,所以生成器函數返回的是生成器對象。 

    /* 如果當前執行的是對象方法,則增加對象的引用計數 */
    if (Z_OBJ(call->This)) {
        Z_ADDREF(call->This);
    }

    /* 把上面創建新的execute_data,保存到zend_generator */
    generator = (zend_generator *) Z_OBJ_P(return_value);
    generator->execute_data = execute_data;
    generator->stack = EG(vm_stack);
    generator->stack->top = EG(vm_stack_top);
    EG(vm_stack_top) = current_stack->top;
    EG(vm_stack_end) = current_stack->end;
    EG(vm_stack) = current_stack;

    /* 賦值給生成器函數返回值,真正是zend_generator,為了存儲,轉為zval類型,后面訪問Generator類的時候會介紹 */
    execute_data->return_value = (zval*)generator;

    memset(&generator->execute_fake, 0, sizeof(zend_execute_data));
    Z_OBJ(generator->execute_fake.This) = (zend_object *) generator;
}

通過上面的代碼片段可以知道:生成器調用時,函數的返回值返回了一個生成器對象,這就是上面提到的第三個特征。另外會申請自己的VM棧(vm_stack)跟原來的VM棧分離開來,互不干擾,每次執行生成器函數代碼時只要修改executor_globals(EG)相應指針就可以切換到生成器函數自己的VM棧,這樣就恢復到了生成器函數之前的狀態。通常,execute_data在VM棧上分配(因為它實際上不進行任何內存分配,所以很快)。對于生成器,這不是最理想的,因為每次執行被暫停或恢復時都必須來回復制(相當大)的結構。 這就是為什么對于生成器,使用多帶帶的VM棧分配執行上下文,從而允許僅通過替換指針來保存和恢復它。

3.4 yield生成值

3.3生成器對象的創建》中提到yield是一個表達式,
編譯的時候最終會調用zend_compile_yield()函數,在Zend/compile.c:6337-6368

代碼片段 3.4.1

void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */
{
    // ...
    /* 編譯key部分 */
    if (key_ast) {
        zend_compile_expr(&key_node, key_ast);
        key_node_ptr = &key_node;
    }
    /* 編譯value部分 */
    if (value_ast) {
        if (returns_by_ref && zend_is_variable(value_ast) && !zend_is_call(value_ast)) {
            zend_compile_var(&value_node, value_ast, BP_VAR_REF);
        } else {
            zend_compile_expr(&value_node, value_ast);
        }
        value_node_ptr = &value_node;
    }
    /* 生成opcode為ZEND_YIELD的zend_op結構體,操作數1(OP1)為value ,操作數2(OP2)為key*/
    opline = zend_emit_op(result, ZEND_YIELD, value_node_ptr, key_node_ptr);

    // ...
}

從上面代碼片段可以看出,yield對應的opcode是ZEND_YIELD,所以對應的處理函數為ZEND_YIELD_SPEC_{OP1}_{OP2}_HANDLER,生成的處理函數很多,但是代碼基本都是一樣的,都是由Zend/zend_vm_def.h中的ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSED)生成的:

第一個參數160:ZEND_YIELD宏的值。

第二個參數ZEND_YIELD:opcode類型

第三個參數CONST|TMP|VAR|CV|UNUSED:表示操作數1(OP1,也就是值value)可以為這些類型的值。

第四個參數CONST|TMP|VAR|CV|UNUSED:表示操作數2(OP2,也就是鍵key)可以為這些類型的值。

Zend/zend_vm_execute.h(所有處理函數的存放文件)都是通過執行zend_vm_gen.php根據Zend/zend_vm_def.h的定義生成的。下面我們看一下這個定義函數:

代碼片段 3.4.2

ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSED)
{
    // ...
    /* 先銷毀原來元素的key和value */
    zval_ptr_dtor(&generator->value);
    zval_ptr_dtor(&generator->key);

    /* 這部分是對value部分的處理 */
    if (OP1_TYPE != IS_UNUSED) { // 如果操作數1類型不是IS_UNUSED,也就是有返回值(yield value這類型)
        if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
            // 前面一些判斷,基本意思就是把值賦給generator->value,也就是生成值,這里就不貼代碼了
        } else { // 如果不是引用類型
            // 根據不同的類型,把值賦給generator->value,也就是生成值,這里也不貼代碼了
        }
    } else { // 如果操作數1類型是IS_UNUSED,也就是沒有返回值(yield這類型),則生成值為NULL
        ZVAL_NULL(&generator->value);
    }

    /* 這部分是對key部分的處理  */
    if (OP2_TYPE != IS_UNUSED) { // 如果操作數2類型不是IS_UNUSED,也就是有返回自定義的key(yield key => value這類型)
        // 根據不同的類型,把值賦給generator->key,也就是生成自定義的鍵,這里也不貼代碼了

        /* 如果鍵的值類型為整型(IS_LONG)且大于當前自增key(largest_used_integer_key),則修改自增key為鍵的值*/
        if (Z_TYPE(generator->key) == IS_LONG
            && Z_LVAL(generator->key) > generator->largest_used_integer_key
        ) {
            generator->largest_used_integer_key = Z_LVAL(generator->key);
        }
    } else {
        /* 如果沒有自定義key,則把下一個自增的值賦給key */
        generator->largest_used_integer_key++;
        ZVAL_LONG(&generator->key, generator->largest_used_integer_key);
    }

    if (RETURN_VALUE_USED(opline)) {
        /* If the return value of yield is used set the send
         * target and initialize it to NULL */
        generator->send_target = EX_VAR(opline->result.var);
        ZVAL_NULL(generator->send_target);
    } else {
        generator->send_target = NULL;
    }

    /* 遞增到下個op,這樣下次繼續執行就可以從下個op開始執行了 */
    ZEND_VM_INC_OPCODE();

    /* The GOTO VM uses a local opline variable. We need to set the opline
     * variable in execute_data so we don"t resume at an old position. */
    SAVE_OPLINE();

    ZEND_VM_RETURN(); // 中斷執行
}

從上面代碼片段可以看出:yield首先生成鍵和值(本質就是修改zend_generator的key和value),生成完鍵值后保存狀態,然后中斷生成器函數的執行。

3.5 生成器對象的訪問

前面兩節介紹了Generator類和生成器對象的結構及創建,我們知道生成器對象可以通過foreach訪問,也可以多帶帶調用生成器對象接口訪問。本節介紹這兩種方式訪問生成器對象的底層實現,兩種訪問方式都是圍繞zend_generator這個結構開展。

3.5.1 使用生成器對象接口訪問

前面《2.4 Generator類》已經提到過Generator類實現了Iterator類,主要有以下方法:

Generator implements Iterator {
    public mixed current ( void )
    public mixed key ( void )
    public void next ( void )
    public void rewind ( void )
    public mixed send ( mixed $value )
    public void throw ( Exception $exception )
    public bool valid ( void )
}

對應C代碼的函數如下:

rewind  -> ZEND_METHOD(Generator, rewind)
key     -> ZEND_METHOD(Generator, key)
next    -> ZEND_METHOD(Generator, next)
current -> ZEND_METHOD(Generator, current)
valid   -> ZEND_METHOD(Generator, valid)
send    -> ZEND_METHOD(Generator, send)
throw   -> ZEND_METHOD(Generator, throw)

ZEND_METHOD是內核定義的一個宏,方便閱讀和開發,這里不做介紹,底層代碼都在Zend/zend_generators.c:767-864

3.5.1.1 ZEND_METHOD(Generator, rewind)

ZEND_METHOD(Generator, rewind)
代碼片段3.5.1

ZEND_METHOD(Generator, rewind)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());
    zend_generator_rewind(generator);
}

Z_OBJ_P(getThis()),展開來是(*(&execute_data.This)).value.obj, 獲取的是當前execute_data.This這個zval(類型為object)的object值(zval.value)的地址。但是這里強行轉換是不是覺得很奇怪?

還記得代碼片段3.3.6中提到:

object_init_ex(return_value, zend_ce_generator); // 實例化生成器對象,賦給return_value,所以生成器函數返回的是生成器對象。

初始化函數object_init_ex()最終會調用_object_and_properties_init()函數,在Zend/zend_API.c:1275-1310

代碼片段3.5.2

ZEND_API int _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties ZEND_FILE_LINE_DC) /* {{{ */
{
    // ...
    if (class_type->create_object == NULL) {
        ZVAL_OBJ(arg, zend_objects_new(class_type));
        if (properties) {
            object_properties_init_ex(Z_OBJ_P(arg), properties);
        } else {
            object_properties_init(Z_OBJ_P(arg), class_type);
        }
    } else {
        ZVAL_OBJ(arg, class_type->create_object(class_type));
    }
    return SUCCESS;
}
/* }}} */

代碼片段3.4.2可以看出,如果zend_class_entry定義有create_object()函數,那么會調用create_object()函數。而zend_ce_generator是有定義有create_object()函數,該函數為zend_generator_create(),參見《3.1 Generator類的注冊及其存儲結構》:

代碼片段3.5.3

static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ */
{
    // ... 
    generator = emalloc(sizeof(zend_generator));
    memset(generator, 0, sizeof(zend_generator));
    // ...
    return (zend_object*)generator;
}
/* }}} */

內存里存儲的是zend_generator,后面強制轉換為zend_object,因為返回值要是zval類型,所以這里做了強制轉換。這就能解釋為什么可以generator = (zend_generator *) Z_OBJ_P(getThis())

回到正題,ZEND_METHOD(Generator, rewind)得到zend_generator后,調用zend_generator_rewind()

代碼片段3.5.4

static void inline zend_generator_rewind(zend_generator *generator)
{
    zend_generator_ensure_initialized(generator); // 保證generator已經初始化過了
    /* 如果已經yield過了,就不能再rewind */
    if (!(generator->flags & ZEND_GENERATOR_AT_FIRST_YIELD)) {
        zend_throw_exception(NULL, "Cannot rewind a generator that was already run", 0);
    }
}

如果yield過了,則不能再rewind,也就是不能再用foreach遍歷,因為foreach也會調用rewind,這個后面再介紹。

3.5.1.2 ZEND_METHOD(Generator, valid)

ZEND_METHOD(Generator, valid),檢查當前位置是否有效,如果無效,foreach會停止遍歷。

代碼片段3.5.5

ZEND_METHOD(Generator, valid)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    zend_generator_get_current(generator);

    RETURN_BOOL(EXPECTED(generator->execute_data != NULL));
}

valid也是獲取到zend_generator后,調用zend_generator_get_current()函數,獲取當前需要運行的zend_generator,然后判斷為NULL,以此已經更多的值生成了,這在《3.2 zend_generator結構體》中詳細說明過。

3.5.1.3 ZEND_METHOD(Generator, current)

ZEND_METHOD(Generator, current)獲取當前元素的值。

代碼片段3.5.6

ZEND_METHOD(Generator, current)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    root = zend_generator_get_current(generator);
    if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->value) != IS_UNDEF)) {
        zval *value = &root->value;

        ZVAL_DEREF(value);
        ZVAL_COPY(return_value, value);
    }
}

和valid方法一樣,也是先獲取到zend_generator,然后判斷生成器函數是否結束(generator->execute_data != NULL)并且有值(Z_TYPE(root->value) != IS_UNDEF),然后把值返回。

3.5.1.4 ZEND_METHOD(Generator, key)

ZEND_METHOD(Generator, key)獲取當前元素的鍵,也就是yield生成值時的key,沒有指定會使用自增的key,即zend_generator.largest_used_integer_key

代碼片段3.5.7

ZEND_METHOD(Generator, key)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    root = zend_generator_get_current(generator);
    if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->key) != IS_UNDEF)) {
        zval *key = &root->key;

        ZVAL_DEREF(key);
        ZVAL_COPY(return_value, key);
    }
}

ZEND_METHOD(Generator, value)差不多,zend_generator.key存儲的就是當前元素的鍵,這在《3.2 zend_generator結構體》中詳細說明過。

3.5.1.5 ZEND_METHOD(Generator, next)

ZEND_METHOD(Generator, next)向前移動到下一個元素,也就是執行到下一個yield *。

代碼片段3.5.8

ZEND_METHOD(Generator, next)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    zend_generator_resume(generator);
}

主要分析zend_generator_resume()函數,這個函數比較重要:

代碼片段3.5.9

ZEND_API void zend_generator_resume(zend_generator *orig_generator) 
{
    zend_generator *generator = zend_generator_get_current(orig_generator); // 獲取要執行生成器

    /* 如果生成器函數已經結束,則直接返回,不能繼續執行 */
    if (UNEXPECTED(!generator->execute_data)) {
        return;
    }

try_again: // 這個標簽是個yield from用的,解析完yield from表達式,需要生成(yield)一個值。
    /* 如果有ZEND_GENERATOR_CURRENTLY_RUNNING標識,則表示已經運行,已經運行的不能再調用這方法繼續運行 */
    if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
        zend_throw_error(NULL, "Cannot resume an already running generator");
        return;
    }

    if (UNEXPECTED((orig_generator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) {
        /* We must not advance Generator if we yield from a Generator being currently run */
        return;
    }
    /* 如果values有值,說明是非生成器類的委托對象產生(yield from)的 */
    if (UNEXPECTED(!Z_ISUNDEF(generator->values))) {
        if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { // 委托對象有值則直接返回
            return;
        }
        /* yield from沒有更多值生成,則繼續運行生成器函數后面的代碼 */
    }

    /* Drop the AT_FIRST_YIELD flag */
    orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;

    {
        /* 保存當前執行的execute_data上下文和VM棧,以便后面恢復,這在前面已經介紹過了 */
        zend_execute_data *original_execute_data = EG(current_execute_data);
        zend_class_entry *original_scope = EG(scope);
        zend_vm_stack original_stack = EG(vm_stack);
        original_stack->top = EG(vm_stack_top);

        /* 修改執行器的指針,指向要運行的生成器函數和其相應的VM棧 */
        EG(current_execute_data) = generator->execute_data;
        EG(scope) = generator->execute_data->func->common.scope;
        EG(vm_stack_top) = generator->stack->top;
        EG(vm_stack_end) = generator->stack->end;
        EG(vm_stack) = generator->stack;

        // ...

        /* 執行生成器函數的代碼 */
        generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
        zend_execute_ex(generator->execute_data); // 執行,遇到yield停止繼續執行
        generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;

        /* 修改VM棧相關的指針,因為上面運行過程中,VM棧不夠,會重新申請新的MV棧,所以需要修改相關指針 */
        if (EXPECTED(generator->execute_data)) {
            generator->stack = EG(vm_stack);
            generator->stack->top = EG(vm_stack_top);
        }

        /* 恢復原來保存的execute_data上下文和VM棧 */
        EG(current_execute_data) = original_execute_data;
        EG(scope) = original_scope;
        EG(vm_stack_top) = original_stack->top;
        EG(vm_stack_end) = original_stack->end;
        EG(vm_stack) = original_stack;

        /* 處理異常,后面介紹throw()方法時再講 */
        if (UNEXPECTED(EG(exception) != NULL)) {
            if (generator == orig_generator) {
                zend_generator_close(generator, 0);
                zend_throw_exception_internal(NULL);
            } else {
                generator = zend_generator_get_current(orig_generator);
                zend_generator_throw_exception(generator, NULL);
                goto try_again;
            }
        }

        /* yiled from沒有生成值時,要重新進入(try_again)生成值 */
        if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) {
            generator = zend_generator_get_current(orig_generator);
            goto try_again;
        }
    }
}

zend_generator_resume()函數,表面意思就是繼續運行生成器函數。前面是一些判斷,然后保存當前上下文,執行生成器代碼,遇到yield返回,然后恢復上下文。

3.5.1.6 ZEND_METHOD(Generator, send)

(未完成)

3.5.1.7 ZEND_METHOD(Generator, throw)

(未完成)

3.5.2 使用foreach訪問

foreach訪問生成器對象,其實就是調用zend_ce_generator->get_iterator,這在《3.1Generator類的注冊及其存儲結構》中介紹過,這是一個鉤子,生成器用的是zend_generator_get_iterator,在Zend/zend_generators.c:1069-1093

代碼片段3.5.10

zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
{
    zend_object_iterator *iterator;
    zend_generator *generator = (zend_generator*)Z_OBJ_P(object);
    // ...
    zend_iterator_init(iterator); // 初始化

    iterator->funcs = &zend_generator_iterator_functions; //設置迭代器對象的相關處理函數
    ZVAL_COPY(&iterator->data, object); // 把zend_generator賦給iterator的data,后面會用到

    return iterator;
}
/* }}} */

zend_generator_get_iterator()把迭代器對象的相關處理函數設置為zend_generator_iterator_functions,使得迭代生成器對象是使用相應的自定義函數,主要函數有:

代碼片段3.5.11

zend_generator_iterator_valid()         // 判斷當前位置是否有效
zend_generator_iterator_get_data()      // 獲取當前元素的值
zend_generator_iterator_get_key()       // 獲取當前元素的鍵
zend_generator_iterator_move_forward()  // 向前移動到下一個元素
zend_generator_iterator_rewind()        // 指向第一個元素

函數細節就不一一介紹了,跟《3.5.1 使用生成器對象接口訪問》的相應函數差不多的。這里我們僅僅分析zend_generator_iterator_rewind()函數,其他的都類似:

代碼片段3.5.12

static void zend_generator_iterator_rewind(zend_object_iterator *iterator) /* {{{ */
{
    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);

    zend_generator_rewind(generator);
}

因為在初始化的時候已經把zend_generator賦給iterator->data,詳見代碼片段3.5.10,所以這里可以從iterator拿到zend_generator對象,其他幾個函數亦是如此。zend_generator_rewind()函數在ZEND_METHOD(Generator, rewind)已經介紹過了,這里就不多說了。

3.6 生成器的終止

從生成器語法我們知道:return語句會終止生成器的執行,如果沒有顯式return,則默認會在結束return null。生成器里面的return語句的opcode是ZEND_GENERATOR_RETURN,而return語句的opcode應該是ZEND_RETURN,這個處理是pass_two()函數里:

代碼片段3.6.1

ZEND_API int pass_two(zend_op_array *op_array)
{
    // ...
    opline = op_array->opcodes;
    end = opline + op_array->last;
    while (opline < end) {
        switch (opline->opcode) {
            case ZEND_RETURN:
            case ZEND_RETURN_BY_REF:
                if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
                    opline->opcode = ZEND_GENERATOR_RETURN;
                }
                break;
        }
        // ...
    }
    // ...
}

從上面代碼可以看出,如果是生成器函數里面的return則把opcode由ZEND_RETURN修改為ZEND_GENERATOR_RETURN,對應的處理函數定義為ZEND_VM_HANDLER(161, ZEND_GENERATOR_RETURN, CONST|TMP|VAR|CV, ANY)

ZEND_VM_HANDLER(161, ZEND_GENERATOR_RETURN, CONST|TMP|VAR|CV, ANY)
{
    // ...
    zend_generator *generator = zend_get_running_generator(execute_data); // 獲取當前運行的生成器函數
    // ...
    retval = GET_OP1_ZVAL_PTR(BP_VAR_R);
    /* 不同操作值類型不同處理,但都是賦給給retval,后面可以使用getReturn()方法獲取返回值 */
    if (OP1_TYPE == IS_CONST || OP1_TYPE == IS_TMP_VAR) {
        ZVAL_COPY_VALUE(&generator->retval, retval);
        // ... 
    } else if (OP1_TYPE == IS_CV) {
        ZVAL_DEREF(retval);
        ZVAL_COPY(&generator->retval, retval);
    } else /* if (OP1_TYPE == IS_VAR) */ {
        if (UNEXPECTED(Z_ISREF_P(retval))) {
            // ...
            ZVAL_COPY_VALUE(&generator->retval, retval);
            // ...
        } else {
            ZVAL_COPY_VALUE(&generator->retval, retval); // 
        }
    }

    /* 關閉生成器,釋放資源(包括申請的VM棧) */
    zend_generator_close(generator, 1);

    /* 執行器返回 */
    ZEND_VM_RETURN();
}

前面是根據不同類型,把值賦給retval,后面調用zend_generator_close()關閉生成器,釋放資源,我們來看看這個函數:

ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished_execution) /* {{{ */
{
    if (EXPECTED(generator->execute_data)) {
        zend_execute_data *execute_data = generator->execute_data;
        // ...
        /* 生成器函數執行過程中出現了致命錯誤,也會執行zend_generator_close(). 但是為啥后面的語句不執行暫時還不清楚 */
        if (UNEXPECTED(CG(unclean_shutdown))) {
            generator->execute_data = NULL;
            return;
        }

        zend_vm_stack_free_extra_args(generator->execute_data); // 釋放額外的參數,也就是參數列表之外的
        /* return語句的清理工作 */
        if (UNEXPECTED(!finished_execution)) {
            zend_generator_cleanup_unfinished_execution(generator, 0);
        }

        // ...
        efree(generator->stack); // 釋放申請的VM棧
        generator->execute_data = NULL; // 把execute_data賦值為NULL,這樣isValid()就返回FALSE.
    }
}
3.7 小結

生成器底層實現僅介紹了yield部分實現,包括yield生成值、生成器的訪問以及生成器的終止。底層實現還是很好理解的,基本圍繞著zend_generator結構體進行。yield from部分較復雜,目前尚未分析清楚,有興趣的同學可以分析一下。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29751.html

相關文章

  • PHP回顧之協程

    摘要:本文先回顧生成器,然后過渡到協程編程。其作用主要體現在三個方面數據生成生產者,通過返回數據數據消費消費者,消費傳來的數據實現協程。解決回調地獄的方式主要有兩種和協程。重點應當關注控制權轉讓的時機,以及協程的運作方式。 轉載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎 web請求 cookie web響應 sess...

    Java3y 評論0 收藏0
  • PHP 成器入門

    摘要:執行語句的唯一目的是結束生成器執行。這就是需要生成器需要有返回值的意義,這也是為何我們將這個特性加入到中的原因,我們會將最后執行的值作為返回值,但這不是一個好的解決方案。 本文首發于 入門 PHP 生成器,轉載請注明出處。 PHP 在 5.5 版本中引入了「生成器(Generator)」特性,不過這個特性并沒有引起人們的注意。在官方的 從 PHP 5.4.x 遷移到 PHP 5.5.x...

    IamDLY 評論0 收藏0
  • PHP異步嘗試一:初識成器

    摘要:下的異步嘗試系列下的異步嘗試一初識生成器下的異步嘗試二初識協程下的異步嘗試三協程的版自動執行器下的異步嘗試四版的下的異步嘗試五版的的繼續完善生成器類獲取迭代器當前值獲取迭代器當前值返回當前產生的鍵生成器從上一次處繼續執行重置迭代器向生成器中 PHP下的異步嘗試系列 PHP下的異步嘗試一:初識生成器 PHP下的異步嘗試二:初識協程 PHP下的異步嘗試三:協程的PHP版thunkify自...

    tomorrowwu 評論0 收藏0
  • PHP 7 值得期待新特性(下)

    摘要:在本系列的第一篇我們介紹了中最重要的一些不兼容性修復以及兩大新特性。例如這個綠色的心形,,可以表示為字符串。雖然現在它只具備內部測試品質目前已可以下載,但的確讓人期待。向項目報告錯誤,并定期重試。 這是我們期待已久的 PHP 7 系列文章的第二篇。點此閱讀 第一篇本文系 OneAPM 工程師編譯整理。 也許你已經知道,重頭戲 PHP 7 的發布將在今年到來!現在,讓我們來了解一下,新版...

    BetaRabbit 評論0 收藏0
  • Swagger 生成 PHP restful API 接口文檔

    摘要:需求和背景需求為客戶端同事寫接口文檔的各位后端同學已經在各種場合回憶了使用自動化文檔工具前手寫文檔的血淚史我的故事卻又不同因為首先來說我在公司是組負責人屬于上述血淚史中催死人不償命的客戶端陣營但血淚史卻是相通的沒有自動化文檔的日子對接口就是 需求和背景 需求: 為客戶端同事寫接口文檔的各位后端同學,已經在各種場合回憶了使用自動化文檔工具前手寫文檔的血淚史.我的故事卻又不同,因為首先來說...

    xiaotianyi 評論0 收藏0

發表評論

0條評論

LMou

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<