欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

php define常量详解

程序员文章站 2022-03-17 21:59:01
...
  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('>_echo >_
复制代码

不过如果定义了这样的常量,是没法直接使用的,会报语法错误。正确的使用方法如下:

  1. define('>_echo constant('>_
复制代码