[翻譯][php擴充開發和嵌入式]第10章-php4的對象

來源:互聯網
上載者:User
php4的對象

曾幾何時, 在很早的版本中, php還不支援任何的物件導向編程文法. 在php4中引入了Zend引擎(ZE1), 出現了幾個新的特性, 其中就包括對象資料類型.

php物件類型的演化

第一次的物件導向編程(OOP)支援僅實現了對象關聯的語義. 用一個php核心開發人員的話來說就是"php4的對象只是將一個數組和一些方法綁定到了一起". 它就是現在你要研究的php對象.

Zend引擎(ZE2)的第二個大版本發布是在php5中, 在php的OOP實現中引入了一些新的特性. 例如, 屬性和方法可以使用存取修飾詞標記它們在你的類定義外面的可見度, 函數的重載可以用來定義內部語言結構的自訂行為, 在多個類的調用鏈之間可以使用介面實施API標準化. 在你學習到第11章"php5對象"時, 你將通過在php5的類定義中實現這些特性來建立對這些知識的認知.

實作類別

在進入OOP的世界之前, 我們需要輕裝上陣. 因此, 請將你的擴充恢複到第5章"你的第一個擴充"中剛剛搭建好的骨架形態.

為了和你原有的習作獨立, 你可以將這個版本命名為sample2. 將下面的三個檔案放入到你php原始碼的ext/sample2目錄下:

config.m4

PHP_ARG_ENABLE(sample2,    [Whether to enable the "sample2" extension],    [  enable-sample2       Enable "sample2" extension support])    if test $PHP_SAMPLE2 != "no"; then    PHP_SUBST(SAMPLE2_SHARED_LIBADD)    PHP_NEW_EXTENSION(sample2, sample2.c, $ext_shared)  fi

php_saple2.h

#ifndef PHP_SAMPLE2_H  /* Prevent double inclusion */  #define PHP_SAMPLE2_H    /* Define Extension Properties */  #define PHP_SAMPLE2_EXTNAME    "sample2"  #define PHP_SAMPLE2_EXTVER    "1.0"    /* Import configure options    when building outside of    the PHP source tree */  #ifdef HAVE_CONFIG_H  #include "config.h"  #endif    /* Include PHP Standard Header */  #include "php.h"    /* Define the entry point symbol  * Zend will use when loading this module  */  extern zend_module_entry sample2_module_entry;  #define phpext_sample2_ptr &sample2_module_entry    #endif /* PHP_SAMPLE2_H */

sample2.c

#include "php_sample2.h"    static function_entry php_sample2_functions[] = {      { NULL, NULL, NULL }  };    PHP_MINIT_FUNCTION(sample2)  {      return SUCCESS;  }    zend_module_entry sample2_module_entry = {  #if ZEND_MODULE_API_NO >= 20010901      STANDARD_MODULE_HEADER,  #endif      PHP_SAMPLE2_EXTNAME,      php_sample2_functions,      PHP_MINIT(sample2),      NULL, /* MSHUTDOWN */      NULL, /* RINIT */      NULL, /* RSHUTDOWN */      NULL, /* MINFO */  #if ZEND_MODULE_API_NO >= 20010901      PHP_SAMPLE2_EXTVER,  #endif      STANDARD_MODULE_PROPERTIES  };    #ifdef COMPILE_DL_SAMPLE2  ZEND_GET_MODULE(sample2)  #endif

現在, 就像在第5章時一樣, 你可以執行phpize, ./configure, make去構建你的sample2.so擴充模組.

你之前的config.w32做與這裡給出的config.m4一樣的修改也可以正常工作.

定義類條目

在使用者空間中, 定義一個類如下:

<?php  class Sample2_FirstClass {  }  ?>

毫無疑問, 你會猜到, 在擴充中實現它還是有一點難度的. 首先, 你需要在你的原始碼檔案中, 像上一章定義int le_sample_descriptor一樣, 定義一個zend_class_entry指標:

zend_class_entry *php_sample2_firstclass_entry;

現在, 就可以在MINIT函數中初始化並註冊類了.

PHP_MINIT_FUNCTION(sample2)  {      zend_class_entry ce; /* 臨時變數 */        /* 註冊類 */      INIT_CLASS_ENTRY(ce, "Sample2_FirstClass", NULL);      php_sample2_firstclass_entry =              zend_register_internal_class(&ce TSRMLS_CC);        return SUCCESS;  }

構建這個擴充, 測試get_declared_classes(), 將會看到Sample2_FirstClass現在在使用者空間可用了.

定義方法的實現

此刻, 你實現的只是一個stdClass, 當然它是可用的. 但實際上你是希望你的類可以做一些事情的.

要達成這個目的, 你就需要回到第5章學到的另外一個知識點了. 將傳遞給INIT_CLASS_ENTRY()的NULL參數替換為php_sample2_firstclass_functions, 並直接在MINIT函數上面如下定義這個結構:

static function_entry php_sample2_firstclass_functions[] = {      { NULL, NULL, NULL }  };

看起來熟悉嗎? 當然. 這和你原來定義過程函數的結構相同. 甚至, 設定這個結構的方式也很相似:

PHP_NAMED_FE(method1, PHP_FN(Sample2_FirstClass_method1), NULL)

當然, 你也可以選用PHP_FE(method1, NULL). 不過回顧一下第5章, 這樣做期望找到的函數實現的名字是zif_method1, 它可能潛在的回合其他的method1()實現衝突. 為了函數的名字空間安全, 我們將類名作為方法名的首碼.

PHP_FALIAS(method1, Sample2_FirstClass_method1, NULL)的格式也是可以的; 但它有點不直觀, 你以後回過頭來看代碼的時候可能會產生疑問"為什麼當時沒有使用PHP_FE()?"

現在, 你已經將一個函數列表附加到類的定義上了, 是時候定義一些方法了. 在php_sample2_firstclass_functions結構上面建立下面的函數:

PHP_FUNCTION(Sample2_FirstClass_countProps)  {      RETURN_LONG(zend_hash_num_elements(Z_OBJPROP_P(getThis())));  }

相應的, 在它的函數列表中增加一條PHP_NAMED_FE()條目:

static function_entry php_sample2_firstclass_functions[] = {      PHP_NAMED_FE(countprops,              PHP_FN(Sample2_FirstClass_countProps), NULL)      { NULL, NULL, NULL }  };

要注意, 這裡暴露給使用者空間的函數名是全部小寫.為了確保方法和函數名都是大小寫不敏感的, 就要求內建函式給出全部小寫名字.

這裡唯一的新元素就是getThis(), 在所有的php版本中, 它都會被解析為一個宏, 展開是this_ptr. this_ptr從本質上來說就和使用者空間對象方法中的$this含義相同. 如果沒有可用的對象執行個體, 比如方法被靜態化調用, 則getThis()返回NULL.

對象方法的資料返回語義和過程函數一致, 參數接受以及arg_info都是同一套東西.

PHP_FUNCTION(Sample2_FirstClass_sayHello)  {      char *name;      int name_len;      if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",                          &name, &name_len) == FAILURE) {          RETURN_NULL();      }      php_printf("Hello");      PHPWRITE(name, name_len);      php_printf("!\nYou called an object method!\n");      RETURN_TRUE;  }

構造器

你的類構造器可以和其他的普通類方法一樣實現, 它的命名遵循也遵循相同的規則. 特別之處在於你需要將構造器命名為類名. 其他兩個ZE1魔術方法__sleep()和__wakeup()也可以以這種方式實現.

繼承

php4中, 內部對象之間的繼承是不完善的, 最好避免使用. 如果你確實必須繼承其他對象, 需要複製下面的ZE1代碼:

void php_sample2_inherit_from_class(zend_class_entry *ce,                          zend_class_entry *parent_ce) {      zend_hash_merge(&ce->function_table,              &parent_ce->function_table, (void (*)(void *))function_add_ref,              NULL, sizeof(zval*), 0);      ce->parent = parent_ce;      if (!ce->handle_property_get) {          ce->handle_property_get =                  parent_ce->handle_property_get;      }      if (!ce->handle_property_set) {          ce->handle_property_set =                  parent_ce->handle_property_set;      }      if (!ce->handle_function_call) {          ce->handle_function_call =                  parent_ce->handle_function_call;      }      if (!zend_hash_exists(&ce->function_table,                  ce->name, ce->name_length + 1)) {          zend_function *fe;          if (zend_hash_find(&parent_ce->function_table,                  parent_ce->name, parent_ce->name_length + 1,                  (void**)fe) == SUCCESS) {              zend_hash_update(&ce->function_table,                  ce->name, ce->name_length + 1,                  fe, sizeof(zend_function), NULL);              function_add_ref(fe);          }      }  }

定義這樣一個函數, 你就可以在MINIT中zend_register_internal_class下面對其進行調用:

INIT_CLASS_ENTRY(ce, "Sample2_FirstClass", NULL);  /* 假定php_saple2_ancestor是一個已經註冊的zend_class_entry */  php_sample2_firstclass_entry =          zend_register_internal_class(&ce TSRMLS_CC);  php_sample2_inherit_from_class(php_sample2_firstclass_entry                              ,php_sample2_ancestor);

儘管這種方式的繼承可以工作, 但還是應該避免ZE1中的繼承, 因為它並沒有設計內部對象的繼承處理. 對於php中的多數OOP實踐, ZE2和它修訂的物件模型是健壯的, 鼓勵所有的OOP相關任務都直接使用它來處理.

使用執行個體工作

和其它使用者空間變數一樣, Object Storage Service在zval *容器中. 在ZE1中, zval *包含了一個HashTable *用於儲存屬性, 以及一個zend_class_entry *指標, 指向類的定義. 在ZE2中, 這些值被一個控制代碼表替代, 增加了一個數值的對象ID, 它和資源ID的用法類似.

很幸運, ZE1和ZE2的這些差異被第2章"變數的裡裡外外"中介紹的Z_*()族宏隱藏了, 因此在你的擴充中不需要關心這些. 下表10.1列出了兩個ZE1的宏, 與非OOP的相關宏一致, 它們也有對應的_P和_PP版本, 用來處理一級或兩級間訪.

建立執行個體

大部分時間, 你的擴充都不需要自己建立執行個體. 而是使用者空間調用new關鍵字建立執行個體並調用你的類構造器.

但你還是有可能需要建立執行個體, 比如在Factory 方法中, ZEND_API中的object_init_ex(zval *val, zend_class_entry *ce)函數可以用於將對象執行個體初始化到變數中.

要注意, object_init_ex()函數並不會調用構造器. 當在內建函式中執行個體化對象時, 構造器必須手動調用. 下面的過程函數重演了new關鍵字的功能邏輯:

PHP_FUNCTION(sample2_new)  {      int argc = ZEND_NUM_ARGS();      zval ***argv = safe_emalloc(sizeof(zval**), argc, 0);      zend_class_entry *ce;      if (argc == 0 ||          zend_get_parameters_array_ex(argc, argv) == FAILURE) {          efree(argv);          WRONG_PARAM_COUNT;      }      /* 第一個參數是類名 */      SEPARATE_ZVAL(argv[0]);      convert_to_string(*argv[0]);      /* 類名儲存為小寫 */      php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));      if (zend_hash_find(EG(class_table),              Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1,              (void**)&ce) == FAILURE) {          php_error_docref(NULL TSRMLS_CC, E_WARNING,              "Class %s does not exist.",              Z_STRVAL_PP(argv[0]));          zval_ptr_dtor(argv[0]);          efree(argv);          RETURN_FALSE;      }      object_init_ex(return_value, ce);      /* 如果有構造器則調用, 額外的參數將傳遞給構造器 */      if (zend_hash_exists(&ce->function_table,              Z_STRVAL_PP(argv[0]),Z_STRLEN_PP(argv[0]) + 1)) {          /* 對象有構造器 */          zval *ctor, *dummy = NULL;            /* 構造器名字是類名 */          MAKE_STD_ZVAL(ctor);          array_init(ctor);          zval_add_ref(argv[0]);          add_next_index_zval(ctor, *argv[0]);          zval_add_ref(argv[0]);          add_next_index_zval(ctor, *argv[0]);          if (call_user_function_ex(&ce->function_table,                  NULL, ctor,                  &dummy, /* 不關心傳回值 */                  argc - 1, argv + 1, /* 參數 */                  0, NULL TSRMLS_CC) == FAILURE) {              php_error_docref(NULL TSRMLS_CC, E_WARNING,                   "Unable to call constructor");          }          if (dummy) {              zval_ptr_dtor(&dummy);          }          zval_ptr_dtor(&ctor);      }      zval_ptr_dtor(argv[0]);      efree(argv);  }

不要忘了在php_sample2_functions中增加一個引用. 它是你的擴充的過程函數列表, 而不是類方法的列表. 為了使用php_strtolower()函數, 還需要增加#include "ext/standard/php_string.h".

這個函數是目前你實現的最複雜的一個, 其中有幾個全新的特性. 首先就是SEPARATE_ZVAL(), 實際上它的功能你已經實現過很多次, 利用zval_copy_ctor()賦值值到一個臨時的結構體, 避免修改原始的內容. 不過它是一個宏版本的封裝.

php_strtolower()用於將類名轉換為小寫, 這樣做是為了達到php類名和函數名不區分大小寫目的. 這隻是附錄B中列出的眾多PHPAPI工具函數的其中一個.

EG(class_table)是一個全域變數, 所有的zend_class_entry定義都註冊到它裡面. 要注意的是在ZE1(php4)中這個HashTable儲存了一級間訪的zend_class_entry *結構體. 而在ZE2(php5)中, 它被儲存為兩級間訪. 這應該不會是一個問題, 因為對這個HashTable的直接存取並不常見, 但知道這一點總歸是有好處的.

call_user_function_ex()是你將在第20章"進階嵌入式"中看到的ZENDAPI調用的一部分. 這裡你將從zend_get_parameters_ex()接收到的zval **參數棧第一個元素拿走, 這樣做就是為了原封不動的將剩餘的參數傳遞給構造器.

譯註: 原著中的代碼在譯者的環境(php-5.4.9)中不能運行, 需要將zend_class_entry *ce修改為二級間訪. 下面給出譯者測試通過的代碼.

PHP_FUNCTION(sample_new)  {      int                 argc    = ZEND_NUM_ARGS();      zval                ***argv = safe_emalloc(sizeof(zval **), argc, 0);       zend_class_entry    **ce;       /* 譯註: 這裡在譯者的環境(php-5.4.9)是二級間訪 */        /* 數組方式讀取所有傳入參數 */      if ( argc == 0 ||                zend_get_parameters_array_ex(argc, argv) == FAILURE ) {           efree(argv);          WRONG_PARAM_COUNT;      }           /* 隔離第一個參數(隔離為了使下面的類型轉換不影響未經處理資料) */      SEPARATE_ZVAL(argv[0]);      /* 將第一個參數轉換為字串類型, 並轉為小寫(因為php的類名是不區分大小寫) */      convert_to_string(*argv[0]);      php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));      /* 在類的HashTable中尋找提供的類是否存在, 如果存在, ce中就得到了對應的zend_class_entry * */      if ( zend_hash_find(EG(class_table), Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1, (void **)&ce) == FAILURE ) {           php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class %s does not exist.", Z_STRVAL_PP(argv[0]));          zval_ptr_dtor(argv[0]);          efree(argv);          RETURN_FALSE;      }           /* 將傳回值初始化為尋找到的類的對象 */      object_init_ex(return_value, *ce);      /* 檢查類是否有構造器 */      if ( zend_hash_exists(&(*ce)->function_table, Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1) ) {           zval    *ctor, *dummy = NULL;            /* 將ctor構造為一個數組, 對應的使用者空間形式為: array(argv[0], argv[0]),           * 實際上對應於使用者空間調用類的靜態方法時$funcname的參數形式:          * array(類名, 方法名)          */          MAKE_STD_ZVAL(ctor);          array_init(ctor);          zval_add_ref(argv[0]);          add_next_index_zval(ctor, *argv[0]);          zval_add_ref(argv[0]);          add_next_index_zval(ctor, *argv[0]);          /* 調用函數 */          if ( call_user_function_ex(&(*ce)->function_table, NULL, ctor, &dummy, argc - 1, argv + 1, 0, NULL TSRMLS_CC) == FAILURE ) {               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to call constructor");          }             /* 如果有傳回值直接析構丟棄 */          if ( dummy ) {              zval_ptr_dtor(&dummy);          }          /* 析構掉臨時使用(用來描述所調用方法名)的數組 */          zval_ptr_dtor(&ctor);      }      /* 析構臨時隔離出來的第一個參數(類名) */      zval_ptr_dtor(argv[0]);      /* 釋放實參列資料表空間 */      efree(argv);  }

接受執行個體

有時你的函數或方法需要接受使用者空間的對象參數. 對於這種目的, zend_parse_parameters()提供了兩種格式的修飾符. 第一種是o(小寫字母o), 它將驗證傳遞的參數是否是對象, 並將它設定到傳遞的zval **中. 下面是這種方式的一個簡單的使用者空間函數樣本, 它返回傳入對象的類名.

PHP_FUNCTION(sample2_class_getname)  {      zval *objvar;      zend_class_entry *objce;      if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o",                                  &objvar) == FAILURE) {          RETURN_NULL();      }      objce = Z_OBJCE_P(objvar);      RETURN_STRINGL(objce->name, objce->name_length, 1);  }

第二種修飾符是O(大寫字母O), 它不僅允許zend_parse_parameters()驗證zval *的類型, 還可以驗證所傳遞對象的類. 要做到這一點, 就需要傳遞一個zval **容易以及一個zend_class_entry *用來驗證, 比如下面的實現就期望傳入的是Sample2_FirstClass類的執行個體:

PHP_FUNCTION(sample2_reload)  {      zval *objvar;      if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O",          &objvar, php_sample2_firstclass_entry) == FAILURE) {          RETURN_NULL();      }      /* 調用假想的"reload"函數 */      RETURN_BOOL(php_sample2_fc_reload(objvar TSRMLS_CC));  }

訪問屬性

你已經看到了, 類方法可以通過getThis()擷取到當前對象執行個體. 將這個宏的結果或其它包含對象執行個體的zval *與Z_OBJPROP_P()宏組合, 得到的HashTable *就包含了該對象的所有屬性.

對象的屬性列表是一個包含zval *的HashTable *, 它只是另外一种放在特殊位置的使用者空間變數列表. 和使用zend_hash_find(EG(active_symbol_table), ...)從當前範圍擷取變數一樣, 你也可以使用第8章"在數組和HashTable上工作"中學習的zend_hash-API去擷取或設定對象的屬性.

例如, 假設在變數rcvdclass這個zval *中包含的是Sample2_FirstClass的執行個體, 下面的代碼塊就可以從它的標準屬性HashTable中取到屬性foo.

zval **fooval;  if (zend_hash_find(Z_OBJPROP_P(rcvdclass),          "foo", sizeof("foo"), (void**)&fooval) == FAILURE) {      /* $rcvdclass->foo doesn't exist */      return;  }

要向屬性工作表中增加元素, 則是這個過程的逆向過程, 調用zend_hash_add()去增加元素, 或者也可以將第8章介紹數組時介紹的add_assoc_*()族函數的assoc替換為property來處理對象.

下面的構造器函數為Sample2_FirstClass的執行個體提供了一些預先設定的預設屬性:

PHP_NAMED_FUNCTION(php_sample2_fc_ctor)  {      /* 為了簡潔, 同時示範函數名可以是任意的, 這裡實現的函數名並不是類名 */      zval *objvar = getThis();        if (!objvar) {          php_error_docref(NULL TSRMLS_CC, E_WARNING,                          "Constructor called statically!");          RETURN_FALSE;      }        add_property_long(objvar, "life", 42);      add_property_double(objvar, "pi", 3.1415926535);      /* 構造器的傳回值會被忽略(請回顧前面構造器的例子) */  }

現在可以通過php_sample2_firstclass_functions列表將它串連到對象的構造器:

PHP_NAMED_FE(sample2_firstclass, php_sample2_fc_ctor, NULL)

譯註: 由於前面的sample_new()工廠函數在call_user_function_ex()調用構造器時使用的是靜態方法的調用格式, 因此, 如果是使用這個工廠函數觸發的構造器調用, getThis()就不會有期望的結果. 因此譯者對例子進行了相應的修改, 讀者如果在這塊遇到問題可以參考譯者的代碼.

PHP_FUNCTION(sample_new)  {      int                 argc    = ZEND_NUM_ARGS();      zval                ***argv = safe_emalloc(sizeof(zval **), argc, 0);       zend_class_entry    **ce;       /* 譯註: 這裡在譯者的環境(php-5.4.9)是二級間訪 */        /* 數組方式讀取所有傳入參數 */      if ( argc == 0 ||                zend_get_parameters_array_ex(argc, argv) == FAILURE ) {           efree(argv);          WRONG_PARAM_COUNT;      }           /* 隔離第一個參數(隔離為了使下面的類型轉換不影響未經處理資料) */      SEPARATE_ZVAL(argv[0]);      /* 將第一個參數轉換為字串類型, 並轉為小寫(因為php的類名是不區分大小寫) */      convert_to_string(*argv[0]);      php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));      /* 在類的HashTable中尋找提供的類是否存在, 如果存在, ce中就得到了對應的zend_class_entry * */      if ( zend_hash_find(EG(class_table), Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1, (void **)&ce) == FAILURE ) {           php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class %s does not exist.", Z_STRVAL_PP(argv[0]));          zval_ptr_dtor(argv[0]);          efree(argv);          RETURN_FALSE;      }           /* 將傳回值初始化為尋找到的類的對象 */      object_init_ex(return_value, *ce);      /* 檢查類是否有構造器 */      if ( zend_hash_exists(&(*ce)->function_table, Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1) ) {   #define DYNAMIC_CONSTRUCTOR  #ifndef DYNAMIC_CONSTRUCTOR          zval    *ctor;  #endif          zval    *dummy = NULL;    #ifndef DYNAMIC_CONSTRUCTOR          /* 將ctor構造為一個數組, 對應的使用者空間形式為: array(argv[0], argv[0]),           * 實際上對應於使用者空間調用類的靜態方法時$funcname的參數形式:          * array(類名, 方法名)          */          MAKE_STD_ZVAL(ctor);          array_init(ctor);          zval_add_ref(argv[0]);          add_next_index_zval(ctor, *argv[0]);          zval_add_ref(argv[0]);          add_next_index_zval(ctor, *argv[0]);  #endif          /* 調用函數 */          if ( call_user_function_ex(&(*ce)->function_table,  #ifndef DYNAMIC_CONSTRUCTOR                  NULL, ctor,  #else                  &return_value, *argv[0],  #endif                  &dummy, argc - 1, argv + 1, 0, NULL TSRMLS_CC) == FAILURE ) {              php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to call constructor");          }          /* 如果有傳回值直接析構丟棄 */          if ( dummy ) {              zval_ptr_dtor(&dummy);          }  #ifndef DYNAMIC_CONSTRUCTOR          /* 析構掉臨時使用(用來描述所調用方法名)的數組 */          zval_ptr_dtor(&ctor);  #endif      }      /* 析構臨時隔離出來的第一個參數(類名) */      zval_ptr_dtor(argv[0]);      /* 釋放實參列資料表空間 */      efree(argv);  }

譯註: 現在, 就可以用函數中是否定義DYNAMIC_CONSTRUCTOR這個宏來切換構造器的調用方式, 以方便讀者理解.

小結

儘管ZE1/php4提供的類功能最好少用, 但是由於當前php4在產品環境下還是廣泛使用的, 因此做這個相容還是有好處的. 本章涉及的技術可以讓你靈活的編寫各種功能的代碼, 它們現在可以編譯運行, 並且未來也將繼續可以工作.

下一章, 你將看到php5中真正的物件導向, 如果你想要OOP, 從中你就可以得到升級的理由, 並且, 升級後你肯定再也不願回頭.

以上就是 [翻譯][php擴充開發和嵌入式]第10章-php4的對象的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.