はじめに
こんにちは、グリーでエンジニアをやっている梶原と申します。
今日は、以前PHPについて気になって調べたことについて、紹介したいと思います。
敬遠されがちなPHPですが、中身を知ると自然と愛着が湧いてきます。
empty()について
empty()と言えば、PHPが誇る7不思議関数のひとつです。
こちらにある通り、どうしてtrueになるのかfalseになるのか不明な点が多い関数です。
empty()が呼ばれる箇所を見てみたいと思います。
- php-5.3.2/Zend/zend_vm_execute.h
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に定義されています。
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上で使ってみると、
var_dump(TRUE);
var_dump(FALSE);
var_dump(NULL);
bool(true)
bool(false)
NULL
となって、論理型とNULLを定義する定数です。
こちらに書かれている通り定義済みの定数として知られています。
どこで定義されているかを見ていきましょう。
TRUE、FALSE、NULLはzend_constants.cのzend_register_standard_constants()内で定数として定義されています。
- php-5.3.2/Zend/zend_constants.c
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
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は大文字小文字を区別しない定数として定義されています。 そこで大文字小文字を混ぜて、
var_dump(TRUE);
var_dump(True);
var_dump(TrUe);
var_dump(true);
を実行すると
bool(true)
bool(true)
bool(true)
bool(true)
とすべて同じ値になることがわかります。
zend_register_constant()について
zend_register_constant()で定数が登録される処理を見てみます。
- php-5.3.2/Zend/zend_constatns.c
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
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関数について紹介したいと思います!