PHP 源碼 — is_array 函數源碼分析

is_array 函數源碼分析

本文首發github.com/suhanyujie/…
基於PHP 7.3.3php

php 中的 is_array

  • php 中的 is_array,它的簽名是 is_array ( mixed $var ) : bool

實現的源碼

  • \ext\standard\type.c 中能夠找到 PHP_FUNCTION(is_array) 所處的位置,大概位於 273 行。
  • 在 PHP 中,這個系列的函數,是由不少個,除了它自己以外,還有 is_bool 、 is_countable 、 is_callback 、 is_int 、 is_object 、 is_string 等等
  • 在它們之中,大部分的源代碼也都是和 is_array 的相似:
PHP_FUNCTION(is_array)
{
	php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_ARRAY);
}
複製代碼
  • 它的定義很簡潔,直接調用了 php_is_type ,宏 INTERNAL_FUNCTION_PARAM_PASSTHRU 的做用是,將調用 is_array 時的參數,原樣傳遞給 php_is_type 。它的定義以下:
#define INTERNAL_FUNCTION_PARAM_PASSTHRU execute_data, return_value
複製代碼
  • 函數 php_is_type 的定義以下:
static inline void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type) {
	zval *arg;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

	if (Z_TYPE_P(arg) == type) {
		if (type == IS_RESOURCE) {
			const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
			if (!type_name) {
				RETURN_FALSE;
			}
		}
		RETURN_TRUE;
	} else {
		RETURN_FALSE;
	}
}
複製代碼
  • 前面幾行是參數解析部分
ZEND_PARSE_PARAMETERS_START(1, 1)
    Z_PARAM_ZVAL(arg)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
複製代碼
  • 隨後經過 Z_TYPE_P(arg) 獲取變量的類型,再讓其結果和 IS_ARRAY 判等。若是爲真,則表示變量是數組,不然不是。
  • Z_TYPE_P 的做用很明顯,就是獲取變量的類型,這個宏展開後以下:
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
	return pz->u1.v.type;
}
複製代碼
  • 其中的 pz ,就是 zval 指針, zval 就是 常常提到的 _zval_struct:
struct _zval_struct {
	zend_value        value;			/* 值 */
	union {
		struct {
			ZEND_ENDIAN_LOHI_3(
				zend_uchar    type,			/* 類型 */
				zend_uchar    type_flags,
				union {
					uint16_t  call_info;    /* call info for EX(This) */
					uint16_t  extra;        /* not further specified */
				} u)
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash 碰撞時用到的鏈表 */
		uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
		uint32_t     opline_num;           /* opline number (for FAST_CALL) */
		uint32_t     lineno;               /* 行號 (ast 節點中) */
		uint32_t     num_args;             /* 參數數量 for EX(This) */
		uint32_t     fe_pos;               /* foreach 時的所在位置 */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* 類時的訪問權限標誌位 */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     constant_flags;       /* constant flags */
		uint32_t     extra;                /* 保留字段 */
	} u2;
};
複製代碼
  • 不作深刻介紹了。接續看 php_is_type
  • 在判斷類型時,有個地方比較蹊蹺: if (type == IS_RESOURCE) {
  • 爲什麼這裏要判斷是不是資源類型?

延伸資源類型

  • 這裏延伸一下,若是用 php_is_type 判斷的是資源類型
  • 這裏會調用 const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
  • 其中有 zend_rsrc_list_get_rsrc_type 的調用,其實現以下:
const char *zend_rsrc_list_get_rsrc_type(zend_resource *res) {
	zend_rsrc_list_dtors_entry *lde;

	lde = zend_hash_index_find_ptr(&list_destructors, res->type);
	if (lde) {
		return lde->type_name;
	} else {
		return NULL;
	}
}
複製代碼
  • 有一個叫作 list_destructors 的靜態變量,它的做用以下

list_destructors 是一個全局靜態 HashTable,資源類型註冊時,將一個 zval 結構體變量 zv 存放入 list_destructors 的 arData 中,而 zv 的 value.ptr 卻指向了 zend_rsrc_list_dtors_entry *lde ,lde中包含的該種資源釋放函數指針、持久資源的釋放函數指針,資源類型名稱,該資源在 hashtable 中的索引依據 (resource_id)等。 --來源於「PHP7 使用資源包裹第三方擴展原理分析git

  • 也就是說,建立了一個資源類型R1時,就會向 list_destructors 中存入一份 zend_rsrc_list_dtors_entry ,其中包含了該資源R1的一些信息
  • 這裏的 zend_hash_index_find_ptr 就是找到資源對應的 zend_rsrc_list_dtors_entry ,從而取其中的 lde->type_name
  • 若是 type 成員是存在的,則說明是資源類型。

總結

  • PHP 中使用 is_* 系列判斷類型的函數,大部分都是經過變量底層 zval 中的 u1.v.type 來判斷類型值
  • 若是是資源類型,須要經過 list_destructors 查詢對應的資源類型是否存在,若是存在,說明資源句柄是能夠正常使用的。

參考資料

相關文章
相關標籤/搜索