PHP内核 - 数据类型 - 类型转换

PHP   PHP内核  

一、概述

  PHP 是弱语言类型,使用时不需要明确定义变量的类型,Zend 虚拟机在执行 PHP 代码时,会根据具体的应用场景进行转换,也就是变量会按照类型转换的规则将不合格的变量转为合格的变量,然后再进行操作。

  比如加法操作:$a = “150” + 300,执行时,Zend 发现相加的其中一项为字符串,这时就会试图将字符串 “150” 转换为数值类型(整型或浮点型),然后与 300 相加,转换的时候并不会改变原来的值,而是会生成一个新的变量进行处理。

  

二、强制类型转换

除了自动类型转换,PHP 还提供了一种强制的类型转换方式:

(1)  (int) / (integer) :转换为整型 int

(2)  (bool) /(boolean):转换为布尔类型 boolean

(3)  (float) / (double) / (real) :转换为浮点型 float

(4)  (string) :转换为字符串string

(5)  (array) :转换为数组array

(6)  (object) : 转换为对象object

(7) (unset):转换为NULL

 
  无论是自动类型转换,还是强制类型转换,这些都并非是万能的,有些类型之间是无法转换的,比如资源类型,无法将任何其他类型转换为资源类型。

  接下来看下不同类型之间的转换规则,这些转换方法定义在 zend_operators.c 中,其中一类是直接对原 value 进行转换,另外一类是不改变原来的值。

  

三、转换为NULL

这类转换比较简单,任意类型都可以转换为NULL,转换时直接将新的 zval 类型设置为 IS_NULL。

  1. ZEND_API void ZEND_FASTCALL convert_to_null(zval *op)
  2. {
  3. zval_ptr_dtor(op);
  4. ZVAL_NULL(op);
  5. }
  6. // 文件位于/Zend/zend_operators.c

  

四、转换为布尔型

  1. ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op)
  2. {
  3. int tmp;
  4. try_again:
  5. switch (Z_TYPE_P(op)) {
  6. case IS_FALSE:
  7. case IS_TRUE:
  8. break;
  9. case IS_NULL:
  10. ZVAL_FALSE(op);
  11. break;
  12. case IS_RESOURCE: {
  13. zend_long l = (Z_RES_HANDLE_P(op) ? 1 : 0);
  14. zval_ptr_dtor(op);
  15. ZVAL_BOOL(op, l);
  16. }
  17. break;
  18. case IS_LONG:
  19. ZVAL_BOOL(op, Z_LVAL_P(op) ? 1 : 0);
  20. break;
  21. case IS_DOUBLE:
  22. ZVAL_BOOL(op, Z_DVAL_P(op) ? 1 : 0);
  23. break;
  24. case IS_STRING:
  25. {
  26. zend_string *str = Z_STR_P(op);
  27. if (ZSTR_LEN(str) == 0 || (ZSTR_LEN(str) == 1
  28. && ZSTR_VAL(str)[0] == '0')) {
  29. ZVAL_FALSE(op);
  30. } else {
  31. ZVAL_TRUE(op);
  32. }
  33. zend_string_release_ex(str, 0);
  34. break;
  35. }
  36. case IS_ARRAY:
  37. tmp = (zend_hash_num_elements(Z_ARRVAL_P(op)) ? 1 : 0);
  38. zval_ptr_dtor(op);
  39. ZVAL_BOOL(op, tmp);
  40. break;
  41. case IS_OBJECT:
  42. {
  43. zval dst;
  44. convert_object_to_type(op, &dst, _IS_BOOL, convert_to_boolean);
  45. zval_ptr_dtor(op);
  46. if (Z_TYPE_INFO(dst) == IS_FALSE || Z_TYPE_INFO(dst) == IS_TRUE) {
  47. Z_TYPE_INFO_P(op) = Z_TYPE_INFO(dst);
  48. } else {
  49. ZVAL_TRUE(op);
  50. }
  51. break;
  52. }
  53. case IS_REFERENCE:
  54. zend_unwrap_reference(op);
  55. goto try_again;
  56. EMPTY_SWITCH_DEFAULT_CASE()
  57. }
  58. }

  

五、转换为整型

从其他类型转为整型的规则。

NULL:转为0。

布尔型:false 转为 0, true 转为 1

浮点型:向下取整,比如 (int)2.8 => 2

字符串:就是C语言 strtoll() 的规则,如果字符串以合法的数值开始,则使用该数值,否则其值为0(零)。
注:合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分组成

数组:很多操作不支持将一个数组自动转为整型处理,比如 array() + 2,将报eror误,但可以强制把数组转为整型,非空数组转为1,空数组转为0。

对象:与数组类似,很多操作也不支持将对象自动转为整型,但有些操作只会抛一个waming警告,还是会把对象转为1操作,这个需要看不同操作的处理情况。

资源:转为分配给这个资源的唯一编号。

zval_get_long_func() 根据以上规则返回不同类型 zval 转为整型后的值, convert to long() 直接将原 zal 转为整型,其判断逻辑是相同的。

自动转换:

  1. ZEND_API zend_long ZEND_FASTCALL zval_get_long_func(zval *op)
  2. {
  3. return _zval_get_long_func_ex(op, 1);
  4. }
  5. static zend_always_inline zend_long ZEND_FASTCALL _zval_get_long_func_ex(zval *op, zend_bool silent)
  6. {
  7. try_again:
  8. switch (Z_TYPE_P(op)) {
  9. case IS_UNDEF:
  10. case IS_NULL:
  11. case IS_FALSE:
  12. return 0;
  13. case IS_TRUE:
  14. return 1;
  15. case IS_RESOURCE:
  16. return Z_RES_HANDLE_P(op);
  17. case IS_LONG:
  18. return Z_LVAL_P(op);
  19. case IS_DOUBLE:
  20. return zend_dval_to_lval(Z_DVAL_P(op));
  21. case IS_STRING:
  22. {
  23. zend_uchar type;
  24. zend_long lval;
  25. double dval;
  26. if (0 == (type = is_numeric_string(Z_STRVAL_P(op), Z_STRLEN_P(op), &lval, &dval, silent ? 1 : -1))) {
  27. if (!silent) {
  28. zend_error(E_WARNING, "A non-numeric value encountered");
  29. }
  30. return 0;
  31. } else if (EXPECTED(type == IS_LONG)) {
  32. return lval;
  33. } else {
  34. /* Previously we used strtol here, not is_numeric_string,
  35. * and strtol gives you LONG_MAX/_MIN on overflow.
  36. * We use use saturating conversion to emulate strtol()'s
  37. * behaviour.
  38. */
  39. return zend_dval_to_lval_cap(dval);
  40. }
  41. }
  42. case IS_ARRAY:
  43. return zend_hash_num_elements(Z_ARRVAL_P(op)) ? 1 : 0;
  44. case IS_OBJECT:
  45. {
  46. zval dst;
  47. convert_object_to_type(op, &dst, IS_LONG, convert_to_long);
  48. if (Z_TYPE(dst) == IS_LONG) {
  49. return Z_LVAL(dst);
  50. } else {
  51. return 1;
  52. }
  53. }
  54. case IS_REFERENCE:
  55. op = Z_REFVAL_P(op);
  56. goto try_again;
  57. EMPTY_SWITCH_DEFAULT_CASE()
  58. }
  59. return 0;
  60. }

强制转换:

  1. ZEND_API void ZEND_FASTCALL convert_to_long(zval *op)
  2. {
  3. if (Z_TYPE_P(op) != IS_LONG) {
  4. convert_to_long_base(op, 10);
  5. }
  6. }
  7. ZEND_API void ZEND_FASTCALL convert_to_long_base(zval *op, int base)
  8. {
  9. zend_long tmp;
  10. try_again:
  11. switch (Z_TYPE_P(op)) {
  12. case IS_NULL:
  13. case IS_FALSE:
  14. ZVAL_LONG(op, 0);
  15. break;
  16. case IS_TRUE:
  17. ZVAL_LONG(op, 1);
  18. break;
  19. case IS_RESOURCE:
  20. tmp = Z_RES_HANDLE_P(op);
  21. zval_ptr_dtor(op);
  22. ZVAL_LONG(op, tmp);
  23. break;
  24. case IS_LONG:
  25. break;
  26. case IS_DOUBLE:
  27. ZVAL_LONG(op, zend_dval_to_lval(Z_DVAL_P(op)));
  28. break;
  29. case IS_STRING:
  30. {
  31. zend_string *str = Z_STR_P(op);
  32. if (base == 10) {
  33. ZVAL_LONG(op, zval_get_long(op));
  34. } else {
  35. ZVAL_LONG(op, ZEND_STRTOL(ZSTR_VAL(str), NULL, base));
  36. }
  37. zend_string_release_ex(str, 0);
  38. }
  39. break;
  40. case IS_ARRAY:
  41. tmp = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);
  42. zval_ptr_dtor(op);
  43. ZVAL_LONG(op, tmp);
  44. break;
  45. case IS_OBJECT:
  46. {
  47. zval dst;
  48. convert_object_to_type(op, &dst, IS_LONG, convert_to_long);
  49. zval_ptr_dtor(op);
  50. if (Z_TYPE(dst) == IS_LONG) {
  51. ZVAL_LONG(op, Z_LVAL(dst));
  52. } else {
  53. ZVAL_LONG(op, 1);
  54. }
  55. return;
  56. }
  57. case IS_REFERENCE:
  58. zend_unwrap_reference(op);
  59. goto try_again;
  60. EMPTY_SWITCH_DEFAULT_CASE()
  61. }
  62. }

  

六、转换为浮点型

  1. ZEND_API void ZEND_FASTCALL convert_to_double(zval *op)
  2. {
  3. double tmp;
  4. try_again:
  5. switch (Z_TYPE_P(op)) {
  6. case IS_NULL:
  7. case IS_FALSE:
  8. ZVAL_DOUBLE(op, 0.0);
  9. break;
  10. case IS_TRUE:
  11. ZVAL_DOUBLE(op, 1.0);
  12. break;
  13. case IS_RESOURCE: {
  14. double d = (double) Z_RES_HANDLE_P(op);
  15. zval_ptr_dtor(op);
  16. ZVAL_DOUBLE(op, d);
  17. }
  18. break;
  19. case IS_LONG:
  20. ZVAL_DOUBLE(op, (double) Z_LVAL_P(op));
  21. break;
  22. case IS_DOUBLE:
  23. break;
  24. case IS_STRING:
  25. {
  26. zend_string *str = Z_STR_P(op);
  27. ZVAL_DOUBLE(op, zend_strtod(ZSTR_VAL(str), NULL));
  28. zend_string_release_ex(str, 0);
  29. }
  30. break;
  31. case IS_ARRAY:
  32. tmp = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);
  33. zval_ptr_dtor(op);
  34. ZVAL_DOUBLE(op, tmp);
  35. break;
  36. case IS_OBJECT:
  37. {
  38. zval dst;
  39. convert_object_to_type(op, &dst, IS_DOUBLE, convert_to_double);
  40. zval_ptr_dtor(op);
  41. if (Z_TYPE(dst) == IS_DOUBLE) {
  42. ZVAL_DOUBLE(op, Z_DVAL(dst));
  43. } else {
  44. ZVAL_DOUBLE(op, 1.0);
  45. }
  46. break;
  47. }
  48. case IS_REFERENCE:
  49. zend_unwrap_reference(op);
  50. goto try_again;
  51. EMPTY_SWITCH_DEFAULT_CASE()
  52. }
  53. }

  

七、转换为字符串

  一个值可以通过在其前面加上 (string) 或用 strval() 函数来转变成字符串。在一个需要字符串的表达式中,会自动转换为 string,比如在使用函数echo或 print时,或在一个非 string类型变量和一个 string 类型变量进行比较时,就会发生这种转换。从其他类型转为字符串的规则。

NULL/FALSE:转为空字符串

TRUE:转为“1”

整型:原样转为字符串,转换时将各位依次除10取余。

浮点型:原样转为字符串。

资源:转为“Resource id林xx”

数组:转为“Aray”,但是报 Notice

对象:不能转换,将报错

  
自动转换:

  1. ZEND_API zend_string* ZEND_FASTCALL zval_get_string_func(zval *op)
  2. {
  3. return __zval_get_string_func(op, 0);
  4. }
  5. static zend_always_inline zend_string* __zval_get_string_func(zval *op, zend_bool try)
  6. {
  7. try_again:
  8. switch (Z_TYPE_P(op)) {
  9. case IS_UNDEF:
  10. case IS_NULL:
  11. case IS_FALSE:
  12. // 转为空字符串 ''
  13. return ZSTR_EMPTY_ALLOC();
  14. case IS_TRUE:
  15. // 转为 '1'
  16. return ZSTR_CHAR('1');
  17. case IS_RESOURCE: {
  18. // 转为"Resource id #"
  19. return zend_strpprintf(0, "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
  20. }
  21. case IS_LONG: {
  22. return zend_long_to_str(Z_LVAL_P(op));
  23. }
  24. case IS_DOUBLE: {
  25. return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
  26. }
  27. case IS_ARRAY:
  28. // 转为 'ARRAY',并报 NOTICE
  29. zend_error(E_NOTICE, "Array to string conversion");
  30. return (try && UNEXPECTED(EG(exception))) ?
  31. NULL : ZSTR_KNOWN(ZEND_STR_ARRAY_CAPITALIZED);
  32. case IS_OBJECT: {
  33. zval tmp;
  34. if (Z_OBJ_HT_P(op)->cast_object) {
  35. if (Z_OBJ_HT_P(op)->cast_object(Z_OBJ_P(op), &tmp, IS_STRING) == SUCCESS) {
  36. return Z_STR(tmp);
  37. }
  38. }
  39. if (!EG(exception)) {
  40. zend_throw_error(NULL, "Object of class %s could not be converted to string", ZSTR_VAL(Z_OBJCE_P(op)->name));
  41. }
  42. return try ? NULL : ZSTR_EMPTY_ALLOC();
  43. }
  44. case IS_REFERENCE:
  45. op = Z_REFVAL_P(op);
  46. goto try_again;
  47. case IS_STRING:
  48. return zend_string_copy(Z_STR_P(op));
  49. EMPTY_SWITCH_DEFAULT_CASE()
  50. }
  51. return NULL;
  52. }

  
强制转换:

  1. ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op)
  2. {
  3. try_again:
  4. switch (Z_TYPE_P(op)) {
  5. case IS_UNDEF:
  6. case IS_NULL:
  7. case IS_FALSE: {
  8. ZVAL_EMPTY_STRING(op);
  9. break;
  10. }
  11. case IS_TRUE:
  12. ZVAL_INTERNED_STR(op, ZSTR_CHAR('1'));
  13. break;
  14. case IS_STRING:
  15. break;
  16. case IS_RESOURCE: {
  17. zend_string *str = zend_strpprintf(0, "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
  18. zval_ptr_dtor(op);
  19. ZVAL_NEW_STR(op, str);
  20. break;
  21. }
  22. case IS_LONG: {
  23. ZVAL_STR(op, zend_long_to_str(Z_LVAL_P(op)));
  24. break;
  25. }
  26. case IS_DOUBLE: {
  27. zend_string *str;
  28. double dval = Z_DVAL_P(op);
  29. str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
  30. /* %G already handles removing trailing zeros from the fractional part, yay */
  31. ZVAL_NEW_STR(op, str);
  32. break;
  33. }
  34. case IS_ARRAY:
  35. zend_error(E_NOTICE, "Array to string conversion");
  36. zval_ptr_dtor(op);
  37. ZVAL_INTERNED_STR(op, ZSTR_KNOWN(ZEND_STR_ARRAY_CAPITALIZED));
  38. break;
  39. case IS_OBJECT: {
  40. zval tmp;
  41. if (Z_OBJ_HT_P(op)->cast_object) {
  42. if (Z_OBJ_HT_P(op)->cast_object(Z_OBJ_P(op), &tmp, IS_STRING) == SUCCESS) {
  43. zval_ptr_dtor(op);
  44. ZVAL_COPY_VALUE(op, &tmp);
  45. return;
  46. }
  47. }
  48. if (!EG(exception)) {
  49. zend_throw_error(NULL, "Object of class %s could not be converted to string", ZSTR_VAL(Z_OBJCE_P(op)->name));
  50. }
  51. zval_ptr_dtor(op);
  52. ZVAL_EMPTY_STRING(op);
  53. break;
  54. }
  55. case IS_REFERENCE:
  56. zend_unwrap_reference(op);
  57. goto try_again;
  58. EMPTY_SWITCH_DEFAULT_CASE()
  59. }
  60. }

  

八、转换为数组

  如果将一个 null、integer、float、string、 boolean和 resource类型的值转换为数组,则将得到一个仅有一个元素的数组,其下标为 0,该元素为此标量的值。换句话说,(array) $scalarValue 与 array($scalarvalue) 完全一样。

  如果一个 object类型转换为array,则结果为一个数组,数组元素为该对象的全部属性,包括 public、private、 protected。

  其中 private的属性转换后的key加上了类名作为前缀, protected属性的key加上了“*”作为前缀,但是这个前缀并不是转为数组时单独加上的,而是类编译生成属性 zend_property_info时就已经加上了。也就是说,这其实是成员属性本身的一个特点。

  
举例:

  1. <?php
  2. declare(strict_types = 1);
  3. class test
  4. {
  5. private $a = '123b';
  6. protected $b = 456;
  7. public $c = 'ccc';
  8. }
  9. $testObject = new test();
  10. print_r((array) $testObject);

结果:

  1. Array (
  2. [testa] => 123b
  3. [*b] => 456
  4. [c] => ccc
  5. )

  
内部实现:

  1. ZEND_API void ZEND_FASTCALL convert_to_array(zval *op)
  2. {
  3. try_again:
  4. switch (Z_TYPE_P(op)) {
  5. case IS_ARRAY:
  6. break;
  7. /* OBJECTS_OPTIMIZE */
  8. case IS_OBJECT:
  9. if (Z_OBJCE_P(op) == zend_ce_closure) {
  10. convert_scalar_to_array(op);
  11. } else {
  12. HashTable *obj_ht = zend_get_properties_for(op, ZEND_PROP_PURPOSE_ARRAY_CAST);
  13. if (obj_ht) {
  14. HashTable *new_obj_ht = zend_proptable_to_symtable(obj_ht,
  15. (Z_OBJCE_P(op)->default_properties_count ||
  16. Z_OBJ_P(op)->handlers != &std_object_handlers ||
  17. GC_IS_RECURSIVE(obj_ht)));
  18. zval_ptr_dtor(op);
  19. ZVAL_ARR(op, new_obj_ht);
  20. zend_release_properties(obj_ht);
  21. } else {
  22. zval_ptr_dtor(op);
  23. /*ZVAL_EMPTY_ARRAY(op);*/
  24. array_init(op);
  25. }
  26. }
  27. break;
  28. case IS_NULL:
  29. /*ZVAL_EMPTY_ARRAY(op);*/
  30. array_init(op);
  31. break;
  32. case IS_REFERENCE:
  33. zend_unwrap_reference(op);
  34. goto try_again;
  35. default:
  36. convert_scalar_to_array(op);
  37. break;
  38. }
  39. }
  40. // 执行default 分支时:
  41. static void convert_scalar_to_array(zval *op)
  42. {
  43. HashTable *ht = zend_new_array(1);
  44. zend_hash_index_add_new(ht, 0, op);
  45. ZVAL_ARR(op, ht);
  46. }
  47. // 文件位于 /Zend/zend_operators.c

  

九、转换为对象

(1)如果其他任何类型的值被转换成对象,将会创建一个内置类 stdClass 的实例;

(2)如果该值为 NULL,则新的实例为空;

(3)array 转换成 object 将以键名成为属性名,并具有相对应的值;

(4)对于其他值,会以 ‘scalar’作为属性名;

  1. ZEND_API void ZEND_FASTCALL convert_to_object(zval *op)
  2. {
  3. try_again:
  4. switch (Z_TYPE_P(op)) {
  5. case IS_ARRAY:
  6. {
  7. HashTable *ht = zend_symtable_to_proptable(Z_ARR_P(op));
  8. zend_object *obj;
  9. if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) {
  10. /* TODO: try not to duplicate immutable arrays as well ??? */
  11. ht = zend_array_dup(ht);
  12. } else if (ht != Z_ARR_P(op)) {
  13. zval_ptr_dtor(op);
  14. } else {
  15. GC_DELREF(ht);
  16. }
  17. obj = zend_objects_new(zend_standard_class_def);
  18. obj->properties = ht;
  19. ZVAL_OBJ(op, obj);
  20. break;
  21. }
  22. case IS_OBJECT:
  23. break;
  24. case IS_NULL:
  25. object_init(op);
  26. break;
  27. case IS_REFERENCE:
  28. zend_unwrap_reference(op);
  29. goto try_again;
  30. default: {
  31. zval tmp;
  32. ZVAL_COPY_VALUE(&tmp, op);
  33. object_init(op);
  34. zend_hash_add_new(Z_OBJPROP_P(op), ZSTR_KNOWN(ZEND_STR_SCALAR), &tmp);
  35. break;
  36. }
  37. }
  38. }

 

 



Top