咱們將介紹 include*
require*
的一些使用細節,
以及從 PHP
應用 和 zend
源碼角度,來分別分析 __autoload
spl_autoload_register
的實現和調用過程。
分析的目的更多的是讓本身對這些細節加深認識,並進一步深刻了解 Zend
源碼。php
PHP 版本:`php-5.6` 核心方法:` spl_autoload_register`
__autoload
spl_autoload_register
包含:include
include_once
requice
requice_one
git
如下文檔也適用於 require。
被包含文件先按參數給出的路徑尋找,若是沒有給出目錄(只有文件名)時則按照 include_path 指定的目錄尋找。若是在 include_path 下沒找到該文件則 include 最後纔在調用腳本文件所在的目錄和當前工做目錄下尋找。若是最後仍未找到文件則 include 結構會發出一條警告;這一點和 require 不一樣,後者會發出一個致命錯誤。vim若是定義了路徑——無論是絕對路徑(在 Windows 下以盤符或者 開頭,在 Unix/Linux 下以 / 開頭)仍是當前目錄的相對路徑(以 . 或者 .. 開頭)——include_path 都會被徹底忽略。例如一個文件以 ../ 開頭,則解析器會在當前目錄的父目錄下尋找該文件。函數
代碼示例:
FILE: run.php
fetch
<?php ini_set('display_errors', '1'); // 直接包含 $ret = include 'class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo(); // 包含不存在的文件 $ret1 = include './class1.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret1, gettype($ret1)); // 重複包含 $ret2 = include './class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo();
FILE: class.php
ui
<?php class Foo { static public function getFoo() { echo "I am foo!\n"; } }
結果:this
結論:spa
include
成功返回值:1
,失敗返回值:false
include
失敗會有 warning
,不會中斷進程
include_once
行爲和 include 語句相似,惟一區別是若是該文件中已經被包含過,則不會再次包含。.net
將 run.php
修改以下:rest
<?php ini_set('display_errors', '1'); // 直接包含 $ret = include 'class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo(); // 重複包含 $ret2 = include_once './class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo();
結果:
結論:
include_once
重複包含時,會直接返回 1
,並忽略這次包含操做,繼續執行
require
和include
幾乎徹底同樣,但require
在出錯時產生E_COMPILE_ERROR
級別的錯誤。(腳本將會停止運行)
將 run.php
修改以下:
<?php ini_set('display_errors', '1'); // 直接包含 $ret = require 'class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo(); // 包含不存在的文件 $ret1 = require './class1.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret1, gettype($ret1));
結果:
結論:
require
包含成功,同 include
同樣,返回值:1
require
包含失敗,直接拋出 Fatal error
,進程停止
require_once
語句和require
語句徹底相同,惟一區別是PHP
會檢查該文件是否已經被包含過,若是是則不會再次包含。
將 run.php
修改以下:
ini_set('display_errors', '1'); // 直接包含 $ret = require_once 'class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo(); // 重複包含 $ret2 = require_once './class.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret)); Foo::getFoo(); // 包含不存在的文件 $ret1 = require_once './class1.php'; echo sprintf("include ret-value:%d,ret-type:%s\n", $ret1, gettype($ret1));
結果:
結論:
1
1
,並忽略這次包含include
include_once
requice
requice_one
成功時,都會返回 1
,差異在於 包含失敗、重複包含 的處理
嘗試加載未定義的類。此函數將會在
PHP 7.2.0
中棄用。
使用示例:
FILE:foo.php
<?php class Foo { static public function getFoo() { echo "I am foo!\n"; } }
FILE:run.php
<?php ini_set('display_errors', '1'); function __autoload($classname) { $filename = "./". lcfirst($classname) .".php"; include_once($filename); } Foo::getFoo();
結果:
➜ load git:(master) ✗ php run.php I am foo!
結論:
遇到未包含的類,會觸發 __autoload
進行加載,若是全部加載規則中沒有此類,則 Fatal error
。
下面,咱們來看一下 Zend
引擎是如何觸發 __autoload
調用的。
利用 vld 來查看剛纔執行過程當中產生的 opcode
,結果以下:
咱們看到,PHP
運行到第 10 行時,所生成的 opcode
爲:INIT_STATIC_METHOD_CALL
,兩個操做數都爲常量(CONST
)。
根據 opcode 的處理函數對應規則,咱們利用 命名法
能夠肯定,
處理函數爲:ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER
源碼位置爲:vim Zend/zend_vm_execute.h +3819
源碼以下:
static int ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *function_name; zend_class_entry *ce; call_slot *call = EX(call_slots) + opline->result.num; SAVE_OPLINE(); if (IS_CONST == IS_CONST) { /* no function found. try a static method in class */ if (CACHED_PTR(opline->op1.literal->cache_slot)) { ce = CACHED_PTR(opline->op1.literal->cache_slot); } else { ce = zend_fetch_class_by_name(Z_STRVAL_P(opline->op1.zv), Z_STRLEN_P(opline->op1.zv), opline->op1.literal + 1, opline->extended_value TSRMLS_CC); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } if (UNEXPECTED(ce == NULL)) { zend_error_noreturn(E_ERROR, "Class '%s' not found", Z_STRVAL_P(opline->op1.zv)); } CACHE_PTR(opline->op1.literal->cache_slot, ce); } call->called_scope = ce; } else { ce = EX_T(opline->op1.var).class_entry; if (opline->extended_value == ZEND_FETCH_CLASS_PARENT || opline->extended_value == ZEND_FETCH_CLASS_SELF) { call->called_scope = EG(called_scope); } else { call->called_scope = ce; } } if (IS_CONST == IS_CONST && IS_CONST == IS_CONST && CACHED_PTR(opline->op2.literal->cache_slot)) { call->fbc = CACHED_PTR(opline->op2.literal->cache_slot); } else if (IS_CONST != IS_CONST && IS_CONST == IS_CONST && (call->fbc = CACHED_POLYMORPHIC_PTR(opline->op2.literal->cache_slot, ce))) { /* do nothing */ } else if (IS_CONST != IS_UNUSED) { char *function_name_strval = NULL; int function_name_strlen = 0; if (IS_CONST == IS_CONST) { function_name_strval = Z_STRVAL_P(opline->op2.zv); function_name_strlen = Z_STRLEN_P(opline->op2.zv); } else { function_name = opline->op2.zv; if (UNEXPECTED(Z_TYPE_P(function_name) != IS_STRING)) { if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } zend_error_noreturn(E_ERROR, "Function name must be a string"); } else { function_name_strval = Z_STRVAL_P(function_name); function_name_strlen = Z_STRLEN_P(function_name); } } if (function_name_strval) { if (ce->get_static_method) { call->fbc = ce->get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC); } else { call->fbc = zend_std_get_static_method(ce, function_name_strval, function_name_strlen, ((IS_CONST == IS_CONST) ? (opline->op2.literal + 1) : NULL) TSRMLS_CC); } if (UNEXPECTED(call->fbc == NULL)) { zend_error_noreturn(E_ERROR, "Call to undefined method %s::%s()", ce->name, function_name_strval); } if (IS_CONST == IS_CONST && EXPECTED(call->fbc->type <= ZEND_USER_FUNCTION) && EXPECTED((call->fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_HANDLER|ZEND_ACC_NEVER_CACHE)) == 0)) { if (IS_CONST == IS_CONST) { CACHE_PTR(opline->op2.literal->cache_slot, call->fbc); } else { CACHE_POLYMORPHIC_PTR(opline->op2.literal->cache_slot, ce, call->fbc); } } } if (IS_CONST != IS_CONST) { } } else { if (UNEXPECTED(ce->constructor == NULL)) { zend_error_noreturn(E_ERROR, "Cannot call constructor"); } if (EG(This) && Z_OBJCE_P(EG(This)) != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { zend_error_noreturn(E_ERROR, "Cannot call private %s::__construct()", ce->name); } call->fbc = ce->constructor; } if (call->fbc->common.fn_flags & ZEND_ACC_STATIC) { call->object = NULL; } else { if (EG(This) && Z_OBJ_HT_P(EG(This))->get_class_entry && !instanceof_function(Z_OBJCE_P(EG(This)), ce TSRMLS_CC)) { /* We are calling method of the other (incompatible) class, but passing $this. This is done for compatibility with php-4. */ if (call->fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) { zend_error(E_DEPRECATED, "Non-static method %s::%s() should not be called statically, assuming $this from incompatible context", call->fbc->common.scope->name, call->fbc->common.function_name); } else { /* An internal function assumes $this is present and won't check that. So PHP would crash by allowing the call. */ zend_error_noreturn(E_ERROR, "Non-static method %s::%s() cannot be called statically, assuming $this from incompatible context", call->fbc->common.scope->name, call->fbc->common.function_name); } } if ((call->object = EG(This))) { Z_ADDREF_P(call->object); call->called_scope = Z_OBJCE_P(call->object); } } call->num_additional_args = 0; call->is_ctor_call = 0; EX(call) = call; CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); }
經過以上源碼,咱們發現關鍵方法爲 zend_fetch_class_by_name
,跟進此方法:
zend_class_entry *zend_fetch_class_by_name(const char *class_name, uint class_name_len, const zend_literal *key, int fetch_type TSRMLS_DC) /* {{{ */ { zend_class_entry **pce; int use_autoload = (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) == 0; if (zend_lookup_class_ex(class_name, class_name_len, key, use_autoload, &pce TSRMLS_CC) == FAILURE) { if (use_autoload) { if ((fetch_type & ZEND_FETCH_CLASS_SILENT) == 0 && !EG(exception)) { if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_INTERFACE) { zend_error(E_ERROR, "Interface '%s' not found", class_name); } else if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_TRAIT) { zend_error(E_ERROR, "Trait '%s' not found", class_name); } else { zend_error(E_ERROR, "Class '%s' not found", class_name); } } } return NULL; } return *pce; }
咱們發現是經過 zend_lookup_class_ex
來獲取類,繼續跟進:
ZEND_API int zend_lookup_class_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_class_entry ***ce TSRMLS_DC) /* {{{ */ { ... /* 注意:在 類的符號表 中沒有找到示例中調用的類 foo */ if (zend_hash_quick_find(EG(class_table), lc_name, lc_length, hash, (void **) ce) == SUCCESS) { if (!key) { free_alloca(lc_free, use_heap); } return SUCCESS; } ... /* * ZVAL_STRINGL 爲 zval (即 PHP 類型的實現基礎 zvalue_value)賦值宏, * 此處實現了 把 ZEND_AUTOLOAD_FUNC_NAME 值 賦給 autoload_function * #define ZEND_AUTOLOAD_FUNC_NAME "__autoload" */ ZVAL_STRINGL(&autoload_function, ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1, 0); ... fcall_info.size = sizeof(fcall_info); fcall_info.function_table = EG(function_table); fcall_info.function_name = &autoload_function; fcall_info.symbol_table = NULL; fcall_info.retval_ptr_ptr = &retval_ptr; fcall_info.param_count = 1; fcall_info.params = args; fcall_info.object_ptr = NULL; fcall_info.no_separation = 1; fcall_cache.initialized = EG(autoload_func) ? 1 : 0; fcall_cache.function_handler = EG(autoload_func); /* 留意此處 */ fcall_cache.calling_scope = NULL; fcall_cache.called_scope = NULL; fcall_cache.object_ptr = NULL; ... retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC); /* 調用自動加載函數 */ ... EG(autoload_func) = fcall_cache.function_handler; zval_ptr_dtor(&class_name_ptr); zend_hash_quick_del(EG(in_autoload), lc_name, lc_length, hash); ... }
咱們發現是經過 zend_call_function
出發了自動加載函數,並且看到了加載方法的名字 __autoload
(宏:ZEND_AUTOLOAD_FUNC_NAME
)
zend_call_function
中會作一下檢測並調用等,並且咱們看到 zend_lookup_class_ex
的返回結果即爲 zend_call_function
的返回結果。
接下來咱們逐步退出函數調用棧:
假設 zend_call_function
調用失敗,返回 FALSE
,
則 zend_lookup_class_ex
返回 FALSE
;
則 zend_fetch_class_by_name
返回 NULL
;
則 ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER
拋出異常 Class ** not found
,以下圖所示:
至此,咱們經過 PHP 代碼
Zend 源碼
瞭解了 __autoload
的調用過程。
咱們知道 __autoload
如今已並不推薦使用,
它的缺點也很明顯,不支持多個自動加載函數。
將函數註冊到SPL __autoload函數隊列中。若是該隊列中的函數還沒有激活,則激活它們。若是在你的程序中已經實現了__autoload()函數,它必須顯式註冊到__autoload()隊列中。
使用示例:
FILE:foo.php
(同上 __autoload
)
FILE:foo2.class.php
<?php class Foo2 { static public function getFoo2() { echo "I am foo2!\n"; } }
FILE:run.php
<?php ini_set('display_errors', '1'); $my_autoload1 = function ($classname) { echo "entry my_autoload1 \n"; $filename = "./". lcfirst($classname) .".php"; include_once($filename); }; $my_autoload2 = function ($classname) { echo "entry my_autoload2 \n"; $filename = "./". lcfirst($classname) .".class.php"; include_once($filename); }; spl_autoload_register($my_autoload1); spl_autoload_register($my_autoload2); Foo::getFoo(); Foo2::getFoo2();
結果以下:
咱們看到,調用 getFoo2
時,會先調用第一個註冊的 autoload
方法,若是沒找到對應的類,會產生 warning
並繼續調用後邊註冊的 autoload
方法。
說明了 PHP
內核中爲經過 spl_autoload_register
註冊的 autoload
方法維護了一個隊列,當前文件爲包含調用類,便會觸發此隊列,並依次調用,直到隊列結束 或者 找到對應類。
首先,咱們看一下 PHP
文件生成的 opcode
咱們發現,其方法調用所生成的 opcode
跟 __autoload
同樣,
可是咱們以前調用了 `spl_autoload_register,
那麼,看一下 spl_autoload_register
的源碼:
FILE: ext/spl/php_spl.c
*
PHP_FUNCTION(spl_autoload_register) { char *func_name, *error = NULL; int func_name_len; char *lc_name = NULL; zval *zcallable = NULL; zend_bool do_throw = 1; zend_bool prepend = 0; zend_function *spl_func_ptr; autoload_func_info alfi; zval *obj_ptr; zend_fcall_info_cache fcc; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|zbb", &zcallable, &do_throw, &prepend) == FAILURE) { return; } if (ZEND_NUM_ARGS()) { if (Z_TYPE_P(zcallable) == IS_STRING) { if (Z_STRLEN_P(zcallable) == sizeof("spl_autoload_call") - 1) { if (!zend_binary_strcasecmp(Z_STRVAL_P(zcallable), sizeof("spl_autoload_call"), "spl_autoload_call", sizeof("spl_autoload_call"))) { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function spl_autoload_call() cannot be registered"); } RETURN_FALSE; } } } if (!zend_is_callable_ex(zcallable, NULL, IS_CALLABLE_STRICT, &func_name, &func_name_len, &fcc, &error TSRMLS_CC)) { alfi.ce = fcc.calling_scope; alfi.func_ptr = fcc.function_handler; obj_ptr = fcc.object_ptr; if (Z_TYPE_P(zcallable) == IS_ARRAY) { if (!obj_ptr && alfi.func_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array specifies a non static method but no object (%s)", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } else if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array does not specify %s %smethod (%s)", alfi.func_ptr ? "a callable" : "an existing", !obj_ptr ? "static " : "", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } else if (Z_TYPE_P(zcallable) == IS_STRING) { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function '%s' not %s (%s)", func_name, alfi.func_ptr ? "callable" : "found", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } else { if (do_throw) { zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Illegal value passed (%s)", error); } if (error) { efree(error); } efree(func_name); RETURN_FALSE; } } alfi.closure = NULL; alfi.ce = fcc.calling_scope; alfi.func_ptr = fcc.function_handler; obj_ptr = fcc.object_ptr; if (error) { efree(error); } lc_name = safe_emalloc(func_name_len, 1, sizeof(long) + 1); zend_str_tolower_copy(lc_name, func_name, func_name_len); efree(func_name); if (Z_TYPE_P(zcallable) == IS_OBJECT) { alfi.closure = zcallable; Z_ADDREF_P(zcallable); lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle)); memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(zcallable), sizeof(zend_object_handle)); func_name_len += sizeof(zend_object_handle); lc_name[func_name_len] = '\0'; } if (SPL_G(autoload_functions) && zend_hash_exists(SPL_G(autoload_functions), (char*)lc_name, func_name_len+1)) { if (alfi.closure) { Z_DELREF_P(zcallable); } goto skip; } if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) { /* add object id to the hash to ensure uniqueness, for more reference look at bug #40091 */ lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle)); memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(obj_ptr), sizeof(zend_object_handle)); func_name_len += sizeof(zend_object_handle); lc_name[func_name_len] = '\0'; alfi.obj = obj_ptr; Z_ADDREF_P(alfi.obj); } else { alfi.obj = NULL; } if (!SPL_G(autoload_functions)) { ALLOC_HASHTABLE(SPL_G(autoload_functions)); zend_hash_init(SPL_G(autoload_functions), 1, NULL, (dtor_func_t) autoload_func_info_dtor, 0); } zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &spl_func_ptr); if (EG(autoload_func) == spl_func_ptr) { /* registered already, so we insert that first */ autoload_func_info spl_alfi; spl_alfi.func_ptr = spl_func_ptr; spl_alfi.obj = NULL; spl_alfi.ce = NULL; spl_alfi.closure = NULL; zend_hash_add(SPL_G(autoload_functions), "spl_autoload", sizeof("spl_autoload"), &spl_alfi, sizeof(autoload_func_info), NULL); if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) { /* Move the newly created element to the head of the hashtable */ HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions)); } } if (zend_hash_add(SPL_G(autoload_functions), lc_name, func_name_len+1, &alfi.func_ptr, sizeof(autoload_func_info), NULL) == FAILURE) { if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) { Z_DELREF_P(alfi.obj); } if (alfi.closure) { Z_DELREF_P(alfi.closure); } } if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) { /* Move the newly created element to the head of the hashtable */ HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions)); } skip: efree(lc_name); } if (SPL_G(autoload_functions)) { zend_hash_find(EG(function_table), "spl_autoload_call", sizeof("spl_autoload_call"), (void **) &EG(autoload_func)); /* 注意此處 */ } else { zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &EG(autoload_func)); } RETURN_TRUE; } /* }}} */
經過分析源碼,咱們發現 spl_autoload_register
會把註冊的自動加載函數添加到 autoload_functions
中,最後將 autoload_functions
賦值給 EG(autoload_func)
(上方源碼倒數第一個 if
判斷邏輯中)。
而有印象的同窗會發現,EG(autoload_func)
在分析 __autoload
調用源碼時出現過(能夠劃到以前的分析查看),它是執行環境全局結構體中的成員,出現調用大概源碼以下:
ZEND_API int zend_lookup_class_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_class_entry ***ce TSRMLS_DC) { ... fcall_info.size = sizeof(fcall_info); fcall_info.function_table = EG(function_table); fcall_info.function_name = &autoload_function; fcall_info.symbol_table = NULL; fcall_info.retval_ptr_ptr = &retval_ptr; fcall_info.param_count = 1; fcall_info.params = args; fcall_info.object_ptr = NULL; fcall_info.no_separation = 1; fcall_cache.initialized = EG(autoload_func) ? 1 : 0; fcall_cache.function_handler = EG(autoload_func); /* 注意這裏 */ fcall_cache.calling_scope = NULL; fcall_cache.called_scope = NULL; fcall_cache.object_ptr = NULL; zend_exception_save(TSRMLS_C); retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC); zend_exception_restore(TSRMLS_C); ... return retval; }
分析到這裏,咱們已經知道了,spl_autoload_register
註冊的函數是如何在 PHP
代碼調用時被觸發的。
感興趣的同窗能夠繼續查看一下 zend_call_function
的源碼,瞭解具體的調用方式。
經過 spl_autoload_register
註冊自動加載函數,會在 Zend
引擎中維護一個 autoload
隊列,便可添加多個 autoload
函數,並在 PHP
調用當前文件未知的類時,觸發 autoload_func
的調用。
同時,細心的同窗也會從 spl_autoload_register
源碼中發現,當註冊時傳入的方法不可調用時, 若是有實現 spl_autoload
,也其會被註冊到 autoload
隊列中。
更多使用細節,請參考:
以上分析,若有不適的地方,請多多指教!