php define常量詳解

來源:互聯網
上載者:User
  1. class A {
  2. public function __toString() {
  3. return 'bar';
  4. }
  5. }
  6. $a = new A();
  7. define('foo', $a);
  8. echo foo;
  9. // 輸出bar
複製代碼

php中的define究竟是如何?的:

  1. ZEND_FUNCTION(define)

  2. {
  3. char *name;
  4. int name_len;
  5. zval *val;
  6. zval *val_free = NULL;
  7. zend_bool non_cs = 0;
  8. int case_sensitive = CONST_CS;
  9. zend_constant c;

  10. // 接收3個參數,string,zval,bool

  11. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) {
  12. return;
  13. }

  14. // 是否大小寫敏感

  15. if(non_cs) {
  16. case_sensitive = 0;
  17. }

  18. // 如果define類常量,則報錯

  19. if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) {
  20. zend_error(E_WARNING, "Class constants cannot be defined or redefined");
  21. RETURN_FALSE;
  22. }

  23. // 擷取真正的值,用val儲存

  24. repeat:
  25. switch (Z_TYPE_P(val)) {
  26. case IS_LONG:
  27. case IS_DOUBLE:
  28. case IS_STRING:
  29. case IS_BOOL:
  30. case IS_RESOURCE:
  31. case IS_NULL:
  32. break;
  33. case IS_OBJECT:
  34. if (!val_free) {
  35. if (Z_OBJ_HT_P(val)->get) {
  36. val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC);
  37. goto repeat;
  38. } else if (Z_OBJ_HT_P(val)->cast_object) {
  39. ALLOC_INIT_ZVAL(val_free);
  40. if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) {
  41. val = val_free;
  42. break;
  43. }
  44. }
  45. }
  46. /* no break */
  47. default:
  48. zend_error(E_WARNING,"Constants may only evaluate to scalar values");
  49. if (val_free) {
  50. zval_ptr_dtor(&val_free);
  51. }
  52. RETURN_FALSE;
  53. }
  54. // 構建常量
  55. c.value = *val;
  56. zval_copy_ctor(&c.value);
  57. if (val_free) {
  58. zval_ptr_dtor(&val_free);
  59. }
  60. c.flags = case_sensitive; /* non persistent */ // 如果大小寫不敏感,則為0,敏感則為1
  61. c.name = zend_strndup(name, name_len);
  62. c.name_len = name_len+1;
  63. c.module_number = PHP_USER_CONSTANT; // 標註非核心常量,而是使用者定義的常量
  64. // 註冊常量
  65. if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) {
  66. RETURN_TRUE;
  67. } else {
  68. RETURN_FALSE;
  69. }
  70. }

複製代碼

注意以repeat開始的一段迴圈,還用到了goto語句T_T

這段代碼的作用為:對於int,float,string,bool,resource,null,則實際定義的常量時直接使用這些值對於object,則需要將object轉成上述6個類型之一(如果轉型之後依然是object,則繼續轉型)如何將object成6個類型之一呢?從代碼上看有2種手段:

  1. if (Z_OBJ_HT_P(val)->get) {
  2. val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC);
  3. goto repeat;
  4. }
  5. // __toString()方法會在cast_object中被調用
  6. else if (Z_OBJ_HT_P(val)->cast_object) {
  7. ALLOC_INIT_ZVAL(val_free);
  8. if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS)
  9. {
  10. val = val_free;
  11. break;
  12. }
  13. }
複製代碼

1,Z_OBJ_HT_P(val)->get ,宏展開之後為(*val).value.obj.handlers->get

2,Z_OBJ_HT_P(val)->cast_object,宏展開之後為(*val).value.obj.handlers->cast_object

handlers是一個包含很多函數指標的結構體,具體定義參見_zend_object_handlers 。該結構體中的函數指標均用於操作object,比如讀取/修改對象屬性、擷取/調用對象方法等等...get和cast_object也是其中之一。

對於一般的對象,php提供了標準的cast_object函數zend_std_cast_object_tostring,代碼位於php-src/zend/zend-object-handlers.c中:

  1. ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */

  2. {
  3. zval *retval;
  4. zend_class_entry *ce;

  5. switch (type) {

  6. case IS_STRING:
  7. ce = Z_OBJCE_P(readobj);
  8. // 如果使用者的class中定義了__toString,則嘗試調用
  9. if (ce->__tostring &&
  10. (zend_call_method_with_0_params(&readobj, ce, &ce->__tostring, "__tostring", &retval) || EG(exception))) {
  11. ……
  12. }
  13. return FAILURE;
  14. ……
  15. }
  16. return FAILURE;
  17. }
複製代碼

從上述具體實現來看,預設的cast_object就是去尋找class中的__tostring方法然後調用...

回到剛開始的例子,define('foo', $a) ,由於$a是A的執行個體,並且class A中定義了__toString,因此實際上foo常量就等於toString的傳回值bar。

ps:繼續挖掘一點小細節.

1,define有傳回值通常我們定義常量直接寫成:define('foo', 123); 不過從define的實現上來看,它是有傳回值的。根據手冊上的描述:

成功時返回 TRUE, 或者在失敗時返回 FALSE。

什麼情況下define會失敗呢?舉個例子:

  1. define('PHP_INT_MAX', 1); // 返回FALSE

  2. define('FOO', 1); // 返回TRUE

  3. define('FOO', 2); // 返回FALSE
複製代碼

上面程式碼封裝含了兩種情況,一是我們嘗試重新定義php核心的預定義常量,比如PHP_INT_MAX,這顯然會失敗。第二種情況是我們曾經在代碼的某個位置定義過了一個常量FOO,然後又在接下來的程式中再次定義它,這也會造成失敗。因此,在編碼時最好將所有需要定義的常量寫在一起,以免造成name重複。

2,常量名沒有限制再次回顧一下define的實現,其中僅僅判斷name是否為XXX::YYY這種形式。

換句話說,define幾乎對其name不做任何要求,當然也不需要name是一個合法的php變數名。因此,我們可以讓define的常量取一些稀奇古怪的名稱。例如:

  1. define('>_<', 123); // 返回TRUE
  2. echo >_<; // syntax error
複製代碼

不過如果定義了這樣的常量,是沒法直接使用的,會報語法錯誤。正確的使用方法如下:

  1. define('>_<', 123); // 返回TRUE
  2. echo constant('>_<'); // 輸出123
複製代碼
  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.