3.4.5 魔术方法

PHP在类的成员方法中预留了一些特殊的方法,它们会在一些特殊的时机被调用(比如创建对象之初、访问成员属性时…),这类方法称为:魔术方法,包括:construct()、destruct()、call()、callStatic()、get()、set()、isset()、unset()、sleep()、wakeup()、toString()、invoke()、 set_state()、 clone() 和 __debugInfo(),关于这些方法的用法这里不作说明,不清楚的可以翻下官方文档。

魔术方法实际是PHP提供的一些特殊操作时的钩子函数,与普通成员方法无异,它们只是与一些操作的口头约定,并没有什么字段标识它们,比如我们定义了一个函数:my_function(),我们希望在这个函数处理对象时首先调用其成员方法my_magic(),那么my_magic()也可以认为是一个魔术方法。

魔术方法与普通成员方法一样保存在zend_class_entry.function_table中,另外针对一些内核常用到的成员方法在zend_class_entry中还有一些单独的指针指向具体的成员方法:

  1. struct _zend_class_entry {
  2. ...
  3. union _zend_function *constructor;
  4. union _zend_function *destructor;
  5. union _zend_function *clone;
  6. union _zend_function *__get;
  7. union _zend_function *__set;
  8. union _zend_function *__unset;
  9. union _zend_function *__isset;
  10. union _zend_function *__call;
  11. union _zend_function *__callstatic;
  12. union _zend_function *__tostring;
  13. union _zend_function *__debugInfo;
  14. ...
  15. }

在编译成员方法时如果发现与这些魔术方法名称一致,则除了插入zend_class_entry.function_table哈希表以外,还会设置zend_class_entry中对应的指针。

3.4.5 魔术方法 - 图1

具体在编译成员方法时设置:zend_begin_method_decl()。

  1. void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_bool has_body)
  2. {
  3. ...
  4. //插入类的function_table中
  5. if (zend_hash_add_ptr(&ce->function_table, lcname, op_array) == NULL) {
  6. zend_error_noreturn(..);
  7. }
  8. if (!in_trait && zend_string_equals_ci(lcname, ce->name)) {
  9. if (!ce->constructor) {
  10. ce->constructor = (zend_function *) op_array;
  11. }
  12. } else if (zend_string_equals_literal(lcname, ZEND_CONSTRUCTOR_FUNC_NAME)) {
  13. ce->constructor = (zend_function *) op_array;
  14. } else if (zend_string_equals_literal(lcname, ZEND_DESTRUCTOR_FUNC_NAME)) {
  15. ce->destructor = (zend_function *) op_array;
  16. } else if (zend_string_equals_literal(lcname, ZEND_CLONE_FUNC_NAME)) {
  17. ce->clone = (zend_function *) op_array;
  18. } else if (zend_string_equals_literal(lcname, ZEND_CALL_FUNC_NAME)) {
  19. ce->__call = (zend_function *) op_array;
  20. } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
  21. ce->__callstatic = (zend_function *) op_array;
  22. } else if (...){
  23. ...
  24. }
  25. ...
  26. }

除了这几个其它魔术方法都没有单独的指针指向,比如:sleep()、wakeup(),这两个主要是serialize()、unserialize()序列化、反序列化时调用的,它们是在这俩函数中写死的,我们简单看下serialize()的实现,这个函数是通过扩展提供的:

  1. //file: ext/standard/var.c
  2. PHP_FUNCTION(serialize)
  3. {
  4. zval *struc;
  5. php_serialize_data_t var_hash;
  6. smart_str buf = {0};
  7. if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &struc) == FAILURE) {
  8. return;
  9. }
  10. php_var_serialize(&buf, struc, &var_hash);
  11. ...
  12. }

最终由php_var_serialize_intern()处理,这个函数会根据不同的类型选择不同的处理方式:

  1. static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash)
  2. {
  3. ...
  4. switch (Z_TYPE_P(struc)) {
  5. case IS_FALSE:
  6. ...
  7. case IS_TRUE:
  8. ...
  9. case IS_NULL:
  10. ...
  11. case IS_LONG:
  12. ...
  13. }
  14. }

其中类型是对象时将先检查zend_class_function.function_table中是否定义了__sleep(),如果有的话则调用:

  1. //case IS_OBJEST:
  2. ...
  3. if (ce != PHP_IC_ENTRY && zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep")-1)) {
  4. ZVAL_STRINGL(&fname, "__sleep", sizeof("__sleep") - 1);
  5. //调用用户自定义的__sleep()方法
  6. res = call_user_function_ex(CG(function_table), struc, &fname, &retval, 0, 0, 1, NULL);
  7. if (res == SUCCESS) {
  8. if (Z_TYPE(retval) != IS_UNDEF) {
  9. if (HASH_OF(&retval)) {
  10. php_var_serialize_class(buf, struc, &retval, var_hash);
  11. } else {
  12. smart_str_appendl(buf,"N;", 2);
  13. }
  14. zval_ptr_dtor(&retval);
  15. }
  16. return;
  17. }
  18. }
  19. //后面会走到IS_ARRAY分支继续序列化处理
  20. ...

其它魔术方法与__sleep()类似,都是在一些特殊操作中固定调用的。