PHP 확장 개발에 대한 심층적인 이해

零到壹度
풀어 주다: 2023-03-23 21:16:02
원래의
1423명이 탐색했습니다.

PHP는 현재 해외 페이스북, 트위터부터 국내 타오바오, 텐센트, 바이두까지 인터넷상의 각종 크고 작은 웹사이트에서 많이 볼 수 있는 언어입니다. PHP의 성공은 주로 개방형 확장 API 메커니즘과 풍부한 확장 구성 요소(PHP 확장)에 달려 있습니다. PHP가 XML, JSON, 암호화, 파일 처리 등 다양한 데이터베이스 작업을 수행할 수 있게 해주는 것은 바로 이러한 확장 구성 요소입니다. 그래픽 처리, 소켓 등의 분야에서 만능입니다. 때때로 개발자는 자신의 PHP 확장을 개발해야 할 수도 있습니다. PHP5의 현재 확장 메커니즘은 Zend API를 기반으로 하며, 풍부한 인터페이스와 매크로 정의 및 몇 가지 실용적인 도구를 제공하므로 PHP 확장을 개발하는 것이 특별히 어렵지 않습니다. 이 기사에서는 PHP 확장 구성 요소 개발에 대한 기본 지식을 소개하고 예제를 통해 PHP 확장 개발의 기본 프로세스를 보여줍니다.

PHP 확장 구성 요소의 개발 프로세스는 Unix와 Windows 환경에서 다르지만 기본적으로 상호 운용 가능합니다. 이 기사에서는 Unix 환경(특히 Linux 사용)을 기반으로 합니다. 이 글을 읽으려면 유닉스 환경에 대한 간단한 이해와 PHP, C 언어에 대한 기본적인 지식이 필요합니다. 간단한 이해만 할 수 있다면 너무 구체적인 운영 체제와 언어 기능은 다루지 않고 필요한 부분만 설명하겠습니다. 독자의 독서를 촉진합니다.

이 글의 구체적인 개발 환경은 Ubuntu 10.04 + PHP 5.3.3입니다.

PHP 소스 코드 다운로드

PHP 확장을 개발하려면 첫 번째 단계는 PHP 소스 코드를 다운로드하는 것입니다. PHP 소스 코드에는 확장 개발에 필요한 도구가 포함되어 있기 때문입니다. 제가 다운로드한 것은 최신 버전의 PHP 5.3.3이며 tar.bz2 압축 패키지 형식입니다.

다운로드 주소는http://cn.php.net/get/php-5.3.3.tar.bz2/from/a/mirror

다운로드 후 소스코드를 해당 디렉토리로 이동한 후 압축을 푼다. 압축 해제 명령은 다음과 같습니다.

  1. tar-jxvf소스 패키지 이름

tar.gz 압축 패키지를 다운로드하는 경우 압축 해제 명령은

  1. tar입니다.-zxvf소스 패키지 이름

압축해제 후 소스코드 디렉터리에 ext 디렉터리가 있는데, 이는 PHP 확장 관련 디렉터리입니다. 디렉터리에 들어가서 ls로 보면 기존 확장이 많이 있는 것을 볼 수 있습니다. 아래 사진은 제 환경에서 본 결과입니다.

파란색은 모두 확장팩 디렉터리로, 익숙한 mysql, iconv, gd 등을 볼 수 있습니다. ext_skel은 Unix 환경에서 자동으로 PHP 확장 프레임워크를 생성하는 데 사용되는 스크립트 도구입니다. ext_skel_win32.php는 Windows에서 해당 스크립트입니다.

자신만의 PHP 확장 프로그램 개발 - say_hello

이제 PHP 확장 프로그램인 say_hello를 개발합니다. 이 확장은 매우 간단합니다. 문자열 매개변수를 받아들이고 "Hello xxx!"를 출력합니다. 이 예제는 PHP 확장 구성요소의 개발 과정을 소개하기 위한 것일 뿐 실제 기능을 가정하지는 않습니다.

확장 컴포넌트 프레임워크 생성

PHP의 확장 컴포넌트 개발 디렉터리와 파일은 고정된 조직 구조를 가지고 있습니다. 기존 확장 컴포넌트 디렉터리에 들어가서 모든 파일을 볼 수 있다는 사실에 놀라실 것입니다. 물론 수동으로 프레임워크를 구축하도록 선택할 수도 있지만, 저는 여러분이 대신해 줄 수 있는 무언가가 있는 것이 더 낫다고 믿습니다. 위에서 언급한 ext_skel 스크립트는 확장 패키지 프레임워크를 자동으로 빌드하는 데 사용되는 도구입니다. ext_skel의 전체 명령은 다음과 같습니다.

  1. ext_skel--extname=module[--proto=file][--스텁=file][--xml[=file]][--skel=dir][--full-xml][--no-help]

  2. 명령 매개변수, 실제로 대부분의 경우 첫 번째 매개변수만 필수이며 확장 모듈의 이름입니다. 따라서 ext 디렉토리에 다음 명령을 입력합니다:

  1. ./

    ext_skel--extname=say_hello

  2. ext_skel 매개변수의 명령은 여기를 참조하세요)

그런 다음 ls를 사용하여 보면 추가 "say_hello" 디렉터리가 있음을 알 수 있습니다. 이 디렉터리에 들어가면 ext_skel이 say_hello의 기본 프레임워크를 설정한 것을 알 수 있습니다.

PHP 확장 패키지 디렉토리 구조의 전체 내용을 파악하기에는 너무 게으른 경우 주의해야 할 파일이 세 개 있습니다:

config.m4: 이것은 나중에 구성 및 설치가 생성되는 Unix 환경의 Build System 구성 파일입니다.

php_say_hello.h: 이 파일은 확장 모듈의 헤더 파일입니다. C 언어의 일관된 스타일에 따라 일부 사용자 정의 구조, 전역 변수 등이 여기에 배치될 수 있습니다.

say_hello.c: 확장 모듈의 주요 프로그램 파일입니다. 확장 모듈의 각 기능에 대한 최종 진입점이 여기에 있습니다. 물론 모든 프로그램 코드를 여기에 넣을 수도 있고, 모듈식 아이디어를 따라 각 기능 모듈을 다른 파일에 넣을 수도 있습니다.

다음 내용은 주로 이 세 파일을 중심으로 전개됩니다.

Unix 빌드 시스템 구성

PHP 확장 구성 요소 개발의 첫 번째 단계는 구현 코드를 작성하는 것이 아니라 먼저 빌드 시스템 옵션을 구성하는 것입니다. 우리는 Linux에서 개발 중이므로 여기서 구성은 주로 config.m4와 관련됩니다.

빌드 시스템 구성에 관해 제가 많이 쓸 수 있고, 유닉스 시스템의 많은 것들과 관련되어 있다면, 제가 쓰고 싶어도 다들 관심을 갖지 않을 것 같으니, 여기서는 생략하고 핵심만 집중적으로 설명하겠습니다. config.m4에 대한 자세한 내용은 여기를 참조하세요.

생성된 config.m4 파일을 엽니다. 내용은 대략 다음과 같습니다.

  1. dnl $Id$ dnl config.m4 for extension say_hello dnl Comments in this file start with the string 'dnl'. dnl Remove where necessary. This file will not work dnl without editing. dnl If your extension references something external, use with: dnl PHP_ARG_WITH(say_hello, for say_hello support, dnl Make sure that the comment is aligned: dnl [ --with-say_hello Include say_hello support]) dnl Otherwise use enable: dnl PHP_ARG_ENABLE(say_hello, whether to enable say_hello support, dnl Make sure that the comment is aligned: dnl [ --enable-say_hello Enable say_hello support]) if test "$PHP_SAY_HELLO" != "no"; then dnl Write more examples of tests here... dnl # --with-say_hello -> check with-path dnl SEARCH_PATH="/usr/local /usr" # you might want to change this dnl SEARCH_FOR="/include/say_hello.h" # you most likely want to change this dnl if test -r $PHP_SAY_HELLO/$SEARCH_FOR; then # path given as parameter dnl SAY_HELLO_DIR=$PHP_SAY_HELLO dnl else # search default path list dnl AC_MSG_CHECKING([for say_hello files in default path]) dnl for i in $SEARCH_PATH ; do dnl if test -r $i/$SEARCH_FOR; then dnl SAY_HELLO_DIR=$i dnl AC_MSG_RESULT(found in $i) dnl fi dnl done dnl fi dnl dnl if test -z "$SAY_HELLO_DIR"; then dnl AC_MSG_RESULT([not found]) dnl AC_MSG_ERROR([Please reinstall the say_hello distribution]) dnl fi dnl # --with-say_hello -> add include path dnl PHP_ADD_INCLUDE($SAY_HELLO_DIR/include) dnl # --with-say_hello -> check for lib and symbol presence dnl LIBNAME=say_hello # you may want to change this dnl LIBSYMBOL=say_hello # you most likely want to change this dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, dnl [ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SAY_HELLO_DIR/lib, SAY_HELLO_SHARED_LIBADD) dnl AC_DEFINE(HAVE_SAY_HELLOLIB,1,[ ]) dnl ],[ dnl AC_MSG_ERROR([wrong say_hello lib version or lib not found]) dnl ],[ dnl -L$SAY_HELLO_DIR/lib -lm dnl ]) dnl dnl PHP_SUBST(SAY_HELLO_SHARED_LIBADD) PHP_NEW_EXTENSION(say_hello, say_hello.c, $ext_shared) fi
    로그인 후 복사

너무 많이 읽지 마세요. "dnl"로 시작하는 모든 내용은 주석이므로 내용이 몇 줄밖에 없습니다. 실제로 작동합니다. 여기에서 구성해야 하는 모든 줄은 다음과 같습니다.

  1. dnl If your extension references something external, use with: dnl PHP_ARG_WITH(say_hello, for say_hello support, dnl Make sure that the comment is aligned: dnl [ --with-say_hello Include say_hello support]) dnl Otherwise use enable: dnl PHP_ARG_ENABLE(say_hello, whether to enable say_hello support, dnl Make sure that the comment is aligned: dnl [ --enable-say_hello Enable say_hello support])
    로그인 후 복사

모든 사람이 이해할 수 있다고 생각합니다. 이는 "확장 프로그램이 외부 구성 요소를 참조하는 경우...를 사용하고, 그렇지 않으면..."을 의미합니다. say_hello 확장은 외부 구성 요소를 참조하지 않으므로 "Otherwise use 활성화" 아래 세 줄에서 "dnl"을 제거하고

  1. dnl Otherwise use enable: PHP_ARG_ENABLE(say_hello, whether to enable say_hello support, Make sure that the comment is aligned: [ --enable-say_hello Enable say_hello support])
    로그인 후 복사

Save로 변경하면 빌드 시스템 구성이 완료됩니다.

PHP Extension 및 Zend_Module Structural Analysis

위 내용은 PHP Extension 개발을 위한 준비 단계로 볼 수 있으며, 핵심 코드는 다음에 작성하겠습니다. 위에서 언급했듯이 PHP Extension 작성은 Zend API와 일부 매크로를 기반으로 하기 때문에 핵심 코드를 작성하려면 먼저 PHP Extension의 구조를 파악해야 합니다. PHP 확장은 실제로 C 언어 수준의 zend_module_entry 구조이기 때문에 "php_say_hello.h"에서 확인할 수 있습니다. "php_say_hello.h"를 열면 다음 줄이 표시됩니다:

  1. externzend_module_entry say_hello_module_entry;

say_hello_module_entry는 say_hello 확장의 C 언어 해당 요소입니다. 유형의 정의 zend_module_entry PHP 소스 코드의 "Zend/zend_modules.h" 파일에서 찾을 수 있습니다. 다음 코드는 zend_module_entry의 정의입니다.

  1. typedef struct _zend_module_entry zend_module_entry; struct _zend_module_entry { unsigned short size; unsigned int zend_api; unsigned char zend_debug; unsigned char zts; const struct _zend_ini_entry *ini_entry; const struct _zend_module_dep *deps; const char *name; const struct _zend_function_entry *functions; int (*module_startup_func)(INIT_FUNC_ARGS); int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); int (*request_startup_func)(INIT_FUNC_ARGS); int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); const char *version; size_t globals_size; #ifdef ZTS ts_rsrc_id* globals_id_ptr; #else void* globals_ptr; #endif void (*globals_ctor)(void *global TSRMLS_DC); void (*globals_dtor)(void *global TSRMLS_DC); int (*post_deactivate_func)(void); int module_started; unsigned char type; void *handle; int module_number; char *build_id; };
    로그인 후 복사

이 구조는 다소 혼란스러워 보일 수 있지만 그래도 해야 합니다. 내용을 설명하세요. 이는 PHP Extension의 프로토타입이기 때문에 이해하지 못하면 PHP Extension을 개발할 수 없습니다. 물론 각 필드를 하나씩 설명하지는 않겠습니다. 많은 필드가 수동으로 채워질 필요는 없지만 미리 정의된 특정 매크로를 사용할 수 있기 때문에 이 기사에서 사용할 핵심 필드만 선택하겠습니다.

7번째 필드 "name", 이 필드는 이 PHP 확장의 이름이며, 이 경우에는 "say_hello"입니다.

8번째 필드 "functions"는 이 확장에서 정의한 함수에 대한 참조를 저장합니다. 특정 구조는 분석되지 않습니다. 관심 있는 친구는 _zend_function_entry의 소스 코드를 읽을 수 있습니다. 특정 코드를 작성할 때 여기에 해당하는 매크로가 있습니다.

9~12번째 필드는 각각 4개의 함수 포인터입니다. 이 4개의 함수는 "확장 모듈이 로드될 때", "확장 모듈이 언로드될 때", "각 요청이 시작될 때" 및 해당 타이밍에 호출됩니다. "각 요청이 끝날 때". 이 네 가지 기능은 차단 메커니즘으로 간주될 수 있으며 주로 해당 시점에 자원 할당, 해제 및 기타 관련 작업에 사용됩니다.

13번째 필드 "info_func"도 함수 포인터입니다. 이 포인터가 가리키는 함수는 phpinfo()를 실행할 때 호출되어 사용자 정의 모듈 정보를 표시합니다.

14번째 필드 "version"은 모듈의 버전입니다.

(zend_module_entry에 대한 자세한 소개는 여기를 참고하세요)

위의 필드를 소개한 후, "say_hello.c"에서 자동 생성된 "say_hello_module_entry" 프레임워크 코드를 살펴보겠습니다.

  1. /* {{{ say_hello_module_entry */ zend_module_entry say_hello_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "say_hello", say_hello_functions, PHP_MINIT(say_hello), PHP_MSHUTDOWN(say_hello), PHP_RINIT(say_hello), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(say_hello), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(say_hello), #if ZEND_MODULE_API_NO >= 20010901 "0.1", /* Replace with version number for your extension */ #endif STANDARD_MODULE_PROPERTIES }; /* }}} */
    로그인 후 복사

首先,宏“STANDARD_MODULE_HEADER”会生成前6个字段,“STANDARD_MODULE_PROPERTIES ”会生成“version”后的字段,所以现在我们还不用操心。而我们关心的几个字段,也都填写好或由宏生成好了,并且在“say_hello.c”的相应位置也生成了几个函数的框架。这里要注意,几个宏的参数均为“say_hello”,但这并不表示几个函数的名字全为“say_hello”,C语言中也不可能存在函数名重载机制。实际上,在开发PHP Extension的过程中,几乎处处都要用到Zend里预定义的各种宏,从全局变量到函数的定义甚至返回值,都不能按照“裸写”的方式来编写C语言,这是因为PHP的运行机制可能会导致命名冲突等问题,而这些宏会将函数等元素变换成一个内部名称,但这些对程序员都是透明的(除非你去阅读那些宏的代码),我们通过各种宏进行编程,而宏则为我们处理很多内部的东西。

写到这里,我们的任务就明了了:第一,如果需要在相应时机处理一些东西,那么需要填充各个拦截函数内容;第二,编写say_hello的功能函数,并将引用添加到say_hello_functions中。

编写phpinfo()回调函数

因为say_hello扩展在各个生命周期阶段并不需要做操作,所以我们只编写info_func的内容,上文说过,这个函数将在phpinfo()执行时被自动调用,用于显示扩展的信息。编写这个函数会用到四个函数:

php_info_print_table_start()——开始phpinfo表格。无参数。

php_info_print_table_header()——输出表格头。第一个参数是整形,指明头的列数,然后后面的参数是与列数等量的(char*)类型参数用于指定显示的文字。

php_info_print_table_row()——输出表格内容。第一个参数是整形,指明这一行的列数,然后后面的参数是与列数等量的(char*)类型参数用于指定显示的文字。

php_info_print_table_end()——结束phpinfo表格。无参数。

下面是“say_hello.c”中需要编写的info_func的具体代码:

  1. /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(say_hello) { php_info_print_table_start(); php_info_print_table_header(2, "say_hello support", "enabled"); php_info_print_table_row(2, "author", "Zhang Yang"); /* Replace with your name */ php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ } /* }}} */
    로그인 후 복사

可以看到我们编写了两行内容、组件是否可用以及作者信息。

编写核心函数

编写核心函数,总共分为三步:1、使用宏PHP_FUNCTION定义函数体;2、使用宏ZEND_BEGIN_ARG_INFO和ZEND_END_ARG_INFO定义参数信息;3、使用宏PHP_FE将函数加入到say_hello_functions中。下面分步说明。

使用宏PHP_FUNCTION定义函数体

  1. PHP_FUNCTION(say_hello_func) { char *name; int name_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) { return; } php_printf("Hello %s!", name); RETURN_TRUE; }
    로그인 후 복사

上文说过,编写PHP扩展时几乎所有东西都不能裸写,而是必须使用相应的宏。从上面代码可以清楚看到这一点。总体来说,核心函数代码一般由如下几部分构成:

定义函数,这一步通过宏PHP_FUNCTION实现,函数的外部名称就是宏后面括号里面的名称。

声明并定义局部变量。

解析参数,这一步通过zend_parse_parameters函数实现,这个函数的作用是从函数用户的输入栈中读取数据,然后转换成相应的函数参数填入变量以供后面核心功能代码使用。zend_parse_parameters的第一个参数是用户传入参数的个数,可以由宏“ZEND_NUM_ARGS() TSRMLS_CC”生成;第二个参数是一个字符串,其中每个字母代表一个变量类型,我们只有一个字符串型变量,所以第二个参数是“s”;最后各个参数需要一些必要的局部变量指针用于存储数据,下表给出了不同变量类型的字母代表及其所需要的局部变量指针。

参数解析完成后就是核心功能代码,我们这里只是输出一行字符,php_printf是Zend版本的printf。

最后的返回值也是通过宏实现的。RETURN_TRUE宏是返回布尔值“true”。

使用宏ZEND_BEGIN_ARG_INFO和ZEND_END_ARG_INFO定义参数信息

参数信息是函数所必要部分,这里不做深究,直接给出相应代码:

  1. ZEND_BEGIN_ARG_INFO(arginfo_say_hello_func, 0) ZEND_END_ARG_INFO()
    로그인 후 복사

如需了解具体信息请阅读相关宏定义。

使用宏PHP_FE将函数加入到say_hello_functions中

最后,我们需要将刚才定义的函数和参数信息加入到say_hello_functions数组里,代码如下:

  1. const zend_function_entry say_hello_functions[] = { PHP_FE(say_hello_func, arginfo_say_hello_func) {NULL, NULL, NULL} };
    로그인 후 복사

这一步就是通过PHP_EF宏实现,注意这个数组最后一行必须是{NULL, NULL, NULL} ,请不要删除。

下面是编写完成后的say_hello.c全部代码:

  1. /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2010 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: ZhangYang | +----------------------------------------------------------------------+ */ /* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "php_say_hello.h" /* If you declare any globals in php_say_hello.h uncomment this: ZEND_DECLARE_MODULE_GLOBALS(say_hello) */ /* True global resources - no need for thread safety here */ static int le_say_hello; /* {{{ PHP_FUNCTION */ PHP_FUNCTION(say_hello_func) { char *name; int name_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) { return; } php_printf("Hello %s!", name); RETURN_TRUE; } ZEND_BEGIN_ARG_INFO(arginfo_say_hello_func, 0) ZEND_END_ARG_INFO() /* }}} */ /* {{{ say_hello_functions[] * * Every user visible function must have an entry in say_hello_functions[]. */ const zend_function_entry say_hello_functions[] = { PHP_FE(say_hello_func, arginfo_say_hello_func) {NULL, NULL, NULL} /* Must be the last line in say_hello_functions[] */ }; /* }}} */ /* {{{ say_hello_module_entry */ zend_module_entry say_hello_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "say_hello", say_hello_functions, NULL, NULL, NULL, NULL, PHP_MINFO(say_hello), #if ZEND_MODULE_API_NO >= 20010901 "0.1", /* Replace with version number for your extension */ #endif STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_SAY_HELLO ZEND_GET_MODULE(say_hello) #endif /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(say_hello) { php_info_print_table_start(); php_info_print_table_header(2, "say_hello support", "enabled"); php_info_print_table_row(2, "author", "Zhang Yang"); /* Replace with your name */ php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ } /* }}} */
    로그인 후 복사

编译并安装扩展

在say_hello目录下输入下面命令:

  1. /usr/bin/phpize

  2. ./configure

  3. make

  4. make install

这样就完成了say_hello扩展的安装(如果没有报错的话)。

这时如果你去放置php扩展的目录下,会发现多了一个say_hello.so的文件。如下图所示:

下面就是将其加入到php.ini配置中,然后重启Apache(如果需要的话)。这些都是PHP基本配置的内容,我就不详述了。

扩展测试

如果上面顺利完成,这时运行phpinfo(),应该能看到如下信息:

这说明扩展已经安装成功了。然后我们编写一个测试用PHP脚本:

  1. ;
    로그인 후 복사

执行这个脚本,结果如下:

说明扩展已经正常工作了。

总结

这篇文章主要用示例方法介绍PHP Extension的开发基础。在PHP的使用中,也许是因为需要支持新的组件(如新的数据库),又或是业务需要或性能需要,几乎都会遇到需要开发PHP扩展的地方。后续如果有机会,我会写文章介绍一些关于扩展开发较为深入的东西,如扩展模块生命周期、INI使用以及编写面向对象的扩展模块等等。

위 내용은 PHP 확장 개발에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!