PHPと仲良くなろう − empty()とdefine() −
はじめに
こんにちは、グリーでエンジニアをやっている梶原と申します。
今日は、以前PHPについて気になって調べたことについて、紹介したいと思います。
敬遠されがちなPHPですが、中身を知ると自然と愛着が湧いてきます。
empty()について
empty()と言えば、PHPが誇る7不思議関数のひとつです。
こちらにある通り、どうしてtrueになるのかfalseになるのか不明な点が多い関数です。
empty()が呼ばれる箇所を見てみたいと思います。
- php-5.3.2/Zend/zend_vm_execute.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
22701 static int ZEND_FASTCALL ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) 22751 switch (opline->extended_value & ZEND_ISSET_ISEMPTY_MASK) { 22752 case ZEND_ISSET: 22753 if (isset && Z_TYPE_PP(value) == IS_NULL) { 22754 Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 0; 22755 } else { 22756 Z_LVAL(EX_T(opline->result.u.var).tmp_var) = isset; 22757 } 22758 break; 22759 case ZEND_ISEMPTY: 22760 if (!isset || !i_zend_is_true(*value)) { 22761 Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 1; 22762 } else { 22763 Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 0; 22764 } 22765 break; 22766 } |
22759行目からemptyの処理になります。
isset()がfalseなものはempty()ではtrueになります。
isset()でtrueなものには、i_zend_is_true()でチェックが行われます。
i_zend_is_true()について
i_zend_is_true()を見てみましょう。
- php-5.3.2/Zend/zend_execute.h
i_zend_is_true()はzend_execute.hに定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
80 static inline int i_zend_is_true(zval *op) 81 { 82 int result; 83 84 switch (Z_TYPE_P(op)) { 85 case IS_NULL: 86 result = 0; 87 break; 88 case IS_LONG: 89 case IS_BOOL: 90 case IS_RESOURCE: 91 result = (Z_LVAL_P(op)?1:0); 92 break; 93 case IS_DOUBLE: 94 result = (Z_DVAL_P(op) ? 1 : 0); 95 break; 96 case IS_STRING: 97 if (Z_STRLEN_P(op) == 0 98 || (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) { 99 result = 0; 100 } else { 101 result = 1; 102 } 103 break; 104 case IS_ARRAY: 105 result = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0); 106 break; 107 case IS_OBJECT: 108 if(IS_ZEND_STD_OBJECT(*op)) { 109 TSRMLS_FETCH(); 110 111 if (Z_OBJ_HT_P(op)->cast_object) { 112 zval tmp; 113 if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_BOOL TSRMLS_CC) == SUCCESS) { 114 result = Z_LVAL(tmp); 115 break; 116 } 117 } else if (Z_OBJ_HT_P(op)->get) { 118 zval *tmp = Z_OBJ_HT_P(op)->get(op TSRMLS_CC); 119 if(Z_TYPE_P(tmp) != IS_OBJECT) { 120 /* for safety - avoid loop */ 121 convert_to_boolean(tmp); 122 result = Z_LVAL_P(tmp); 123 zval_ptr_dtor(&tmp); 124 break; 125 } 126 } 127 } 128 result = 1; 129 break; 130 default: 131 result = 0; 132 break; 133 } 134 return result; 135 } |
関数を見てみると、変数の型別にチェックしてるのがわかります。
i_zend_is_true()はempty()では反転して評価されることを踏まえて、
配列の場合、105行目でzend_hash_num_elementsが空であればempty()はtrueになったり、
文字列の場合、97、98行目で文字列の長さが0のときと文字列の長さが1のときでも中身が'0'であればempty()はtrueになったりという実装になっています。
定数の話とTRUE, FALSE, NULLについて
次は定数の話とTRUE, FALSE, NULLがなにものかについてです。
PHP上で使ってみると、
1 2 3 |
var_dump(TRUE); var_dump(FALSE); var_dump(NULL); |
1 2 3 |
bool(true) bool(false) NULL |
となって、論理型とNULLを定義する定数です。
こちらに書かれている通り定義済みの定数として知られています。
どこで定義されているかを見ていきましょう。
TRUE、FALSE、NULLはzend_constants.cのzend_register_standard_constants()内で定数として定義されています。
- php-5.3.2/Zend/zend_constants.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
96 void zend_register_standard_constants(TSRMLS_D) 97 { 118 zend_constant c; 119 120 c.flags = CONST_PERSISTENT | CONST_CT_SUBST; 121 c.module_number = 0; 122 123 c.name = zend_strndup(ZEND_STRL("TRUE")); 124 c.name_len = sizeof("TRUE"); 125 c.value.value.lval = 1; 126 c.value.type = IS_BOOL; 127 zend_register_constant(&c TSRMLS_CC); 128 129 c.name = zend_strndup(ZEND_STRL("FALSE")); 130 c.name_len = sizeof("FALSE"); 131 c.value.value.lval = 0; 132 c.value.type = IS_BOOL; 133 zend_register_constant(&c TSRMLS_CC); 134 135 c.name = zend_strndup(ZEND_STRL("NULL")); 136 c.name_len = sizeof("NULL"); 137 c.value.type = IS_NULL; 138 zend_register_constant(&c TSRMLS_CC); |
関数の中身を覗いてみると、zend_constantに 123、129、135行目でそれぞれの定数の名前をnameに、 またそれぞれのvalue、typeを入れて zend_register_constant()を通して定数として定義されます。
zend_register_constant()は定数の登録を行う関数です。
define()について
ここでPHP上で定数を定義する際に用いるdefine関数について見てみます。
- php-5.3.2/Zend/zend_builtin_functions.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
623 ZEND_FUNCTION(define) 633 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) { 634 return; 635 } 637 if(non_cs) { 638 case_sensitive = 0; 639 } 678 c.value = *val; 679 zval_copy_ctor(&c.value); 680 if (val_free) { 681 zval_ptr_dtor(&val_free); 682 } 683 c.flags = case_sensitive; /* non persistent */ 684 c.name = zend_strndup(name, name_len); 685 c.name_len = name_len+1; 686 c.module_number = PHP_USER_CONSTANT; 687 if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { 688 RETURN_TRUE; 689 } else { 690 RETURN_FALSE; 691 } |
define()でも同じく、zend_register_constant()を通して定数の定義を行っています。 先程と違うところはzend_constant.flagsに大文字小文字を区別するかのフラグがdefine()の引数によりセットできます。 そのため、TRUE, FALSE, NULLは大文字小文字を区別しない定数として定義されています。 そこで大文字小文字を混ぜて、
1 2 3 4 |
var_dump(TRUE); var_dump(True); var_dump(TrUe); var_dump(true); |
を実行すると
1 2 3 4 |
bool(true) bool(true) bool(true) bool(true) |
とすべて同じ値になることがわかります。
zend_register_constant()について
zend_register_constant()で定数が登録される処理を見てみます。
- php-5.3.2/Zend/zend_constatns.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
411 ZEND_API int zend_register_constant(zend_constant *c TSRMLS_DC) 412 { 413 char *lowercase_name = NULL; 414 char *name; 415 int ret = SUCCESS; 416 417 #if 0 418 printf("Registering constant for module %d\n", c->module_number); 419 #endif 420 421 if (!(c->flags & CONST_CS)) { 422 /* keep in mind that c->name_len already contains the '\0' */ 423 lowercase_name = estrndup(c->name, c->name_len-1); 424 zend_str_tolower(lowercase_name, c->name_len-1); 425 name = lowercase_name; 426 } else { 427 char *slash = strrchr(c->name, '\\'); 428 if(slash) { 429 lowercase_name = estrndup(c->name, c->name_len-1); 430 zend_str_tolower(lowercase_name, slash-c->name); 431 name = lowercase_name; 432 } else { 433 name = c->name; 434 } 435 } 438 zend_hash_add(EG(zend_constants), name, c->name_len, (void *) c, sizeof(zend_constant), NULL)==FAILURE) { |
zend_constatnt->flagsにCONST_CSがついていないと定数名はzend_str_tolowerを通して小文字に変換されます。
最後にzend_hash_addを使ってzend_constantsに定数が登録されます。
zend_get_constant()について
- php-5.3.2/Zend/zend_get_constant.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
224 ZEND_API int zend_get_constant(const char *name, uint name_len, zval *result TSRMLS_DC) 225 { 226 zend_constant *c; 227 int retval = 1; 228 char *lookup_name; 229 230 if (zend_hash_find(EG(zend_constants), name, name_len+1, (void **) &c) == FAILURE) { 231 lookup_name = zend_str_tolower_dup(name, name_len); 232 233 if (zend_hash_find(EG(zend_constants), lookup_name, name_len+1, (void **) &c)==SUCCESS) { 234 if (c->flags & CONST_CS) { 235 retval=0; 236 } 237 } |
定数を参照するときはzend_get_constant()が呼ばれてます。
まずは渡された定数名で検索し、ヒットしなければ小文字に変換して再度検索を行ないます。
このときヒットしたものについてはCONST_CSフラグが立っていれば小文字変換後の検索は無効になってます。
さいごに
今回はPHP上での定数の話とempty()について簡単に紹介しました。
次回はarray関数について紹介したいと思います!