PHPと仲良くなろう − empty()とdefine() −

はじめに

こんにちは、グリーでエンジニアをやっている梶原と申します。
今日は、以前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関数について紹介したいと思います!

Author: kajidai