## 4.1 類型轉換
PHP是弱類型語言,不需要明確的定義變量的類型,變量的類型根據使用時的上下文所決定,也就是變量會根據不同表達式所需要的類型自動轉換,比如求和,PHP會將兩個相加的值轉為long、double再進行加和。每種類型轉為另外一種類型都有固定的規則,當某個操作發現類型不符時就會按照這個規則進行轉換,這個規則正是弱類型實現的基礎。
除了自動類型轉換,PHP還提供了一種強制的轉換方式:
* (int)/(integer):轉換為整形 integer
* (bool)/(boolean):轉換為布爾類型 boolean
* (float)/(double)/(real):轉換為浮點型 float
* (string):轉換為字符串 string
* (array):轉換為數組 array
* (object):轉換為對象 object
* (unset):轉換為 NULL
無論是自動類型轉換還是強制類型轉換,不是每種類型都可以轉為任意其他類型。
### 4.1.1 轉換為NULL
這種轉換比較簡單,任意類型都可以轉為NULL,轉換時直接將新的zval類型設置為`IS_NULL`即可。
### 4.1.2 轉換為布爾型
當轉換為 boolean 時,根據原值的TRUE、FALSE決定轉換后的結果,以下值被認為是 FALSE:
* 布爾值 FALSE 本身
* 整型值 0
* 浮點型值 0.0
* 空字符串,以及字符串 "0"
* 空數組
* NULL
所有其它值都被認為是 TRUE,比如資源、對象(這里指默認情況下,因為可以通過擴展改變這個規則)。
判斷一個值是否為true的操作:
```c
static zend_always_inline int i_zend_is_true(zval *op)
{
int result = 0;
again:
switch (Z_TYPE_P(op)) {
case IS_TRUE:
result = 1;
break;
case IS_LONG:
//非0即真
if (Z_LVAL_P(op)) {
result = 1;
}
break;
case IS_DOUBLE:
if (Z_DVAL_P(op)) {
result = 1;
}
break;
case IS_STRING:
//非空字符串及"0"外都為true
if (Z_STRLEN_P(op) > 1 || (Z_STRLEN_P(op) && Z_STRVAL_P(op)[0] != '0')) {
result = 1;
}
break;
case IS_ARRAY:
//非空數組為true
if (zend_hash_num_elements(Z_ARRVAL_P(op))) {
result = 1;
}
break;
case IS_OBJECT:
//默認情況下始終返回true
result = zend_object_is_true(op);
break;
case IS_RESOURCE:
//合法資源就是true
if (EXPECTED(Z_RES_HANDLE_P(op))) {
result = 1;
}
case IS_REFERENCE:
op = Z_REFVAL_P(op);
goto again;
break;
default:
break;
}
return result;
}
```
在擴展中可以通過`convert_to_boolean()`這個函數直接將原zval轉為bool型,轉換時的判斷邏輯與`i_zend_is_true()`一致。
### 4.1.3 轉換為整型
其它類型轉為整形的轉換規則:
* NULL:轉為0
* 布爾型:false轉為0,true轉為1
* 浮點型:向下取整,比如:`(int)2.8 => 2`
* 字符串:就是C語言strtoll()的規則,如果字符串以合法的數值開始,則使用該數值,否則其值為 0(零),合法數值由可選的正負號,后面跟著一個或多個數字(可能有小數點),再跟著可選的指數部分
* 數組:很多操作不支持將一個數組自動整形處理,比如:`array() + 2`,將報error錯誤,但可以強制把數組轉為整形,非空數組轉為1,空數組轉為0,沒有其他值
* 對象:與數組類似,很多操作也不支持將對象自動轉為整形,但有些操作只會拋一個warning警告,還是會把對象轉為1操作的,這個需要看不同操作的處理情況
* 資源:轉為分配給這個資源的唯一編號
具體處理:
```c
ZEND_API zend_long ZEND_FASTCALL _zval_get_long_func(zval *op)
{
try_again:
switch (Z_TYPE_P(op)) {
case IS_NULL:
case IS_FALSE:
return 0;
case IS_TRUE:
return 1;
case IS_RESOURCE:
//資源將轉為zend_resource->handler
return Z_RES_HANDLE_P(op);
case IS_LONG:
return Z_LVAL_P(op);
case IS_DOUBLE:
return zend_dval_to_lval(Z_DVAL_P(op));
case IS_STRING:
//字符串的轉換調用C語言的strtoll()處理
return ZEND_STRTOL(Z_STRVAL_P(op), NULL, 10);
case IS_ARRAY:
//根據數組是否為空轉為0,1
return zend_hash_num_elements(Z_ARRVAL_P(op)) ? 1 : 0;
case IS_OBJECT:
{
zval dst;
convert_object_to_type(op, &dst, IS_LONG, convert_to_long);
if (Z_TYPE(dst) == IS_LONG) {
return Z_LVAL(dst);
} else {
//默認情況就是1
return 1;
}
}
case IS_REFERENCE:
op = Z_REFVAL_P(op);
goto try_again;
EMPTY_SWITCH_DEFAULT_CASE()
}
return 0;
}
```
### 4.1.4 轉換為浮點型
除字符串類型外,其它類型轉換規則與整形基本一致,就是整形轉換結果加了一位小數,字符串轉為浮點數由`zend_strtod()`完成,這個函數非常長,定義在`zend_strtod.c`中,這里不作說明。
### 4.1.5 轉換為字符串
一個值可以通過在其前面加上 (string) 或用 strval() 函數來轉變成字符串。在一個需要字符串的表達式中,會自動轉換為 string,比如在使用函數 echo 或 print 時,或在一個變量和一個 string 進行比較時,就會發生這種轉換。
```c
ZEND_API zend_string* ZEND_FASTCALL _zval_get_string_func(zval *op)
{
try_again:
switch (Z_TYPE_P(op)) {
case IS_UNDEF:
case IS_NULL:
case IS_FALSE:
//轉為空字符串""
return ZSTR_EMPTY_ALLOC();
case IS_TRUE:
//轉為"1"
...
return zend_string_init("1", 1, 0);
case IS_RESOURCE: {
//轉為"Resource id #xxx"
...
len = snprintf(buf, sizeof(buf), "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
return zend_string_init(buf, len, 0);
}
case IS_LONG: {
return zend_long_to_str(Z_LVAL_P(op));
}
case IS_DOUBLE: {
return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
}
case IS_ARRAY:
//轉為"Array",但是報Notice
zend_error(E_NOTICE, "Array to string conversion");
return zend_string_init("Array", sizeof("Array")-1, 0);
case IS_OBJECT: {
//報Error錯誤
zval tmp;
...
zend_error(EG(exception) ? E_ERROR : E_RECOVERABLE_ERROR, "Object of class %s could not be converted to string", ZSTR_VAL(Z_OBJCE_P(op)->name));
return ZSTR_EMPTY_ALLOC();
}
case IS_REFERENCE:
op = Z_REFVAL_P(op);
goto try_again;
case IS_STRING:
return zend_string_copy(Z_STR_P(op));
EMPTY_SWITCH_DEFAULT_CASE()
}
return NULL;
}
```
### 4.1.6 轉換為數組
如果將一個null、integer、float、string、boolean 和 resource 類型的值轉換為數組,將得到一個僅有一個元素的數組,其下標為 0,該元素即為此標量的值。換句話說,(array)$scalarValue 與 array($scalarValue) 完全一樣。
如果一個 object 類型轉換為 array,則結果為一個數組,數組元素為該對象的全部屬性,包括public、private、protected,其中private的屬性轉換后的key加上了類名作為前綴,protected屬性的key加上了"*"作為前綴,但是這個前綴并不是轉為數組時單獨加上的,而是類編譯生成屬性zend_property_info時就已經加上了,也就是說這其實是成員屬性本身的一個特點,舉例來看:
```c
class test {
private $a = 123;
public $b = "bbb";
protected $c = "ccc";
}
$obj = new test;
print_r((array)$obj);
======================
Array
(
[testa] => 123
[b] => bbb
[*c] => ccc
)
```
轉換時的處理:
```c
ZEND_API void ZEND_FASTCALL convert_to_array(zval *op)
{
try_again:
switch (Z_TYPE_P(op)) {
case IS_ARRAY:
break;
case IS_OBJECT:
...
if (Z_OBJ_HT_P(op)->get_properties) {
//獲取所有屬性數組
HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op);
//將數組內容拷貝到新數組
...
}
case IS_NULL:
ZVAL_NEW_ARR(op);
//轉為空數組
zend_hash_init(Z_ARRVAL_P(op), 8, NULL, ZVAL_PTR_DTOR, 0);
break;
case IS_REFERENCE:
zend_unwrap_reference(op);
goto try_again;
default:
convert_scalar_to_array(op);
break;
}
}
//其他標量類型轉array
static void convert_scalar_to_array(zval *op)
{
zval entry;
ZVAL_COPY_VALUE(&entry, op);
//新分配一個數組,將原值插入數組
ZVAL_NEW_ARR(op);
zend_hash_init(Z_ARRVAL_P(op), 8, NULL, ZVAL_PTR_DTOR, 0);
zend_hash_index_add_new(Z_ARRVAL_P(op), 0, &entry);
}
```
### 4.1.7 轉換為對象
如果其它任何類型的值被轉換成對象,將會創建一個內置類 stdClass 的實例:如果該值為 NULL,則新的實例為空;array轉換成object將以鍵名成為屬性名并具有相對應的值,數值索引的元素也將轉為屬性,但是無法通過"->"訪問,只能遍歷獲取;對于其他值,會以scalar作為屬性名。
```c
ZEND_API void ZEND_FASTCALL convert_to_object(zval *op)
{
try_again:
switch (Z_TYPE_P(op)) {
case IS_ARRAY:
{
HashTable *ht = Z_ARR_P(op);
...
//以key為屬性名,將數組元素拷貝到對象屬性
object_and_properties_init(op, zend_standard_class_def, ht);
break;
}
case IS_OBJECT:
break;
case IS_NULL:
object_init(op);
break;
case IS_REFERENCE:
zend_unwrap_reference(op);
goto try_again;
default: {
zval tmp;
ZVAL_COPY_VALUE(&tmp, op);
object_init(op);
//以scalar作為屬性名
zend_hash_str_add_new(Z_OBJPROP_P(op), "scalar", sizeof("scalar")-1, &tmp);
break;
}
}
}
```
### 4.1.8 轉換為資源
無法將其他類型轉為資源。
- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考