Maison  >  Article  >  développement back-end  >  Compréhension approfondie du développement d'extensions PHP

Compréhension approfondie du développement d'extensions PHP

零到壹度
零到壹度original
2018-04-14 16:54:231450parcourir

PHP est un langage très largement utilisé actuellement. On peut le voir sur divers sites Web de grande, moyenne et petite taille, de Facebook et Twitter étrangers aux Taobao, Tencent, Baidu nationaux et toutes sortes de sites Web de grande, moyenne et petite taille. l'Internet. Il faut dire que le succès de PHP repose en grande partie sur son mécanisme API d'extension ouvert et ses riches composants d'extension (PHP Extension). Ce sont ces composants d'extension qui permettent à PHP d'opérer depuis diverses opérations de base de données vers XML, JSON, le chiffrement, le traitement de fichiers, etc. Il est omnipotent dans des domaines tels que le traitement graphique et Socket. Parfois, les développeurs peuvent avoir besoin de développer leurs propres extensions PHP. Le mécanisme d'extension actuel de PHP5 est basé sur l'API Zend. L'API Zend fournit des interfaces riches et des définitions de macros, ainsi que des outils pratiques, ce qui rend le développement d'extensions PHP pas particulièrement difficile. Cet article présentera les connaissances de base sur le développement de composants d'extension PHP et démontrera le processus de base de développement d'extensions PHP à travers un exemple.

Le processus de développement des composants d'extension PHP est différent dans les environnements Unix et Windows, mais ils sont fondamentalement interopérables. Cet article sera basé sur l'environnement Unix (utilisant spécifiquement Linux). La lecture de cet article nécessite une compréhension simple de quelques connaissances de base de l'environnement Unix, du langage PHP et C. Tant que vous avez une compréhension simple, j'essaierai de ne pas aborder les fonctionnalités trop spécifiques du système d'exploitation et du langage, et de les expliquer si nécessaire. faciliter la lecture des lecteurs.

L'environnement de développement spécifique de cet article est Ubuntu 10.04 + PHP 5.3.3.

Télécharger le code source PHP

Pour développer des extensions PHP, la première étape consiste à télécharger le code source PHP, car il contient les outils nécessaires au développement d'extensions. Ce que j'ai téléchargé est la dernière version de PHP 5.3.3, au format du package compressé tar.bz2.

L'adresse de téléchargement est : http://cn.php.net/get/php-5.3.3.tar.bz2/from/a/mirror

Télécharger Enfin, déplacez le code source vers le répertoire approprié et décompressez-le. La commande de décompression est :

  1. tar -jxvf Nom du package source

Si vous avez téléchargé un package compressé tar.gz, la commande de décompression est

  1. tar - zxvf Nom du package source

Après décompression, il y a un répertoire ext dans le répertoire du code source, qui est le répertoire lié aux extensions PHP. Après être entré dans le répertoire et l'avoir consulté avec ls, vous pouvez voir de nombreuses extensions existantes. L'image ci-dessous est le résultat de sa visualisation dans mon environnement :

Les bleus sont tous des répertoires de packs d'extension, dans lesquels vous pouvez voir les fichiers mysql, iconv et gd familiers, etc. . ext_skel est un outil de script utilisé pour générer automatiquement un framework d'extension PHP dans un environnement Unix. Nous l'utiliserons bientôt. ext_skel_win32.php est le script correspondant sous Windows.

Développez votre propre extension PHP - say_hello

Maintenant, nous développons une extension PHP : say_hello. Cette extension est très simple, elle accepte simplement un paramètre de chaîne et affiche "Hello xxx!". Cet exemple vise uniquement à présenter le processus de développement des composants d'extension PHP et ne suppose pas de fonctions réelles.

Générer un cadre de composants d'extension

Le répertoire et les fichiers de développement des composants d'extension de PHP ont une structure organisationnelle fixe. Vous pouvez entrer dans un répertoire de composants d'extension existant et afficher tous ses fichiers. été éblouissant. Bien sûr, vous pouvez choisir de créer le framework manuellement, mais je pense que vous préféreriez que quelque chose le fasse pour vous. Le script ext_skel mentionné ci-dessus est un outil utilisé pour créer automatiquement le framework du package d'extension. La commande complète de ext_skel est :

  1. ext_skel --extname=module [--proto=fichier] [-- stubs=fichier] [--xml [=fichier]] [--skel=répertoire] [--complet-xml] [--non-aide]

En tant que débutants, nous n'avons pas besoin de connaître tous les paramètres de la commande. En fait, dans la plupart des cas, nous n'avons besoin de fournir que le premier paramètre, qui est. le nom du module d'extension. Par conséquent, nous tapons la commande suivante dans le répertoire ext :

  1. ./ext_skel -- extname =say_hello

(Si vous souhaitez en savoir plus sur les différents paramètres de commande de ext_skel, veuillez vous référer ici)

À ce stade, utilisez ls pour le visualiser, et vous constaterez qu'il existe un répertoire "say_hello" supplémentaire. Entrez dans ce répertoire et vous constaterez que ext_skel a établi le cadre de base de say_hello pour nous, comme. ci-dessous :

Si vous êtes trop paresseux pour comprendre tout le contenu de la structure du répertoire du package d'extension PHP, alors il contient trois fichiers auxquels vous devez prêter attention. :

config.m4 : Il s'agit du fichier de configuration du système de construction dans l'environnement Unix, suivi de la configuration et l'installation sera générée via celui-ci.

php_say_hello.h : Ce fichier est le fichier d'en-tête du module d'extension. Suivant le style cohérent du langage C, certaines structures personnalisées, variables globales, etc. peuvent y être placées.

say_hello.c : Il s'agit du fichier programme principal du module d'extension. Les entrées de fonction finales du module d'extension sont ici. Bien sûr, vous pouvez y insérer tout le code du programme, ou vous pouvez suivre l'idée modulaire et placer chaque module fonctionnel dans des fichiers différents.

Le contenu suivant tourne principalement autour de ces trois fichiers.

Configuration du système de build Unix

La première étape du développement de composants d'extension PHP n'est pas d'écrire le code d'implémentation, mais de configurer d'abord les options du système de build. Puisque nous développons sous Linux, la configuration ici est principalement liée à config.m4.

Concernant la configuration du Build System, si je peux écrire beaucoup, et que cela est lié à beaucoup de choses dans le système Unix, même si je suis intéressé à l'écrire, je suppose que tout le monde ne sera pas intéressé à le lire , nous allons donc l'omettre ici et sélectionner uniquement Parlons des points clés. Pour plus de détails sur config.m4, veuillez vous référer ici.

Ouvrez le fichier config.m4 généré, le contenu est à peu près le suivant :

  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

Ne lisez pas trop, car tout commence par "dnl". Le début est constitué uniquement de commentaires, donc peu de lignes fonctionnent réellement. Tout ce qu'il faut configurer ici, ce sont les lignes suivantes :

  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])

Je pense que tout le monde peut le comprendre, ce qui signifie "si votre extension fait référence à des composants externes, utiliser..., sinon utiliser...". Notre extension say_hello ne fait pas référence à des composants externes, supprimez donc le "dnl" dans les trois lignes ci-dessous "Sinon, utilisez activer" et remplacez-le par :

  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])

Enregistrez et la configuration du système de construction est terminée.

Extension PHP et analyse structurelle Zend_Module

Ce qui précède peut être considéré comme une préparation au développement d'extensions PHP. Ensuite, nous écrirons le code de base. Comme mentionné ci-dessus, l'écriture d'extensions PHP est basée sur l'API Zend et certaines macros, donc si nous voulons écrire du code de base, nous devons d'abord comprendre la structure de l'extension PHP. Parce qu'une extension PHP est en fait une structure zend_module_entry au niveau du langage C, ce qui peut être confirmé à partir de "php_say_hello.h". Ouvrez "php_say_hello.h" et vous verrez une ligne à l'intérieur :

  1. extern zend_module_entry say_hello_module_entry;

say_hello_module_entry est l'élément correspondant en langage C de l'extension say_hello, et la définition de son type zend_module_entry se trouve dans le fichier "Zend/zend_modules.h" du code source PHP Ce qui suit. le code est zend_module_entry Définition :

  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;
    };

Cette structure peut sembler un peu déroutante, mais je veux quand même expliquer ce qu'il y a à l'intérieur. Parce qu'il s'agit du prototype de PHP Extension, si vous ne le comprenez pas, vous ne pourrez pas développer PHP Extension. Bien entendu, je n'expliquerai pas chaque champ un par un, je sélectionnerai uniquement les champs clés qui seront utilisés dans cet article, car de nombreux champs n'ont pas besoin d'être remplis manuellement, mais peuvent utiliser certaines macros de remplissage prédéfinies.

Le septième champ "name", ce champ est le nom de cette extension PHP, dans cet exemple il s'agit de "say_hello".

Le 8ème champ "fonctions" stockera les références aux fonctions que nous définissons dans cette extension. La structure spécifique ne sera pas analysée. Les amis intéressés peuvent lire le code source de _zend_function_entry. Il y aura ici des macros correspondantes lors de l'écriture de code spécifique.

Les 9ème à 12ème champs sont respectivement quatre pointeurs de fonction. Ces quatre fonctions seront appelées au timing correspondant, à savoir "lorsque le module d'extension est chargé", "lorsque le module d'extension est déchargé" et "chacun". requête "Au début" et "A la fin de chaque requête". Ces quatre fonctions peuvent être considérées comme un mécanisme d'interception, principalement utilisé pour l'allocation des ressources, la libération et d'autres opérations connexes à des moments correspondants.

Le 13ème champ "info_func" est également un pointeur de fonction. La fonction pointée par ce pointeur sera appelée lors de l'exécution de phpinfo() et est utilisée pour afficher les informations du module personnalisé.

Le 14ème champ "version" est la version du module.

(Pour une introduction plus détaillée à zend_module_entry, veuillez vous référer ici)

Après avoir introduit les champs ci-dessus, nous pouvons jeter un œil au code du framework "say_hello_module_entry" généré automatiquement dans "say_hello.c ".

  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 say_hello_func(&#39;Zhang Yang&#39;); ?>;

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

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

总结

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn