ホームページ > バックエンド開発 > PHPチュートリアル > [翻訳] [php拡張機能の開発と組み込み] 第16章 - 興味深い流れ

[翻訳] [php拡張機能の開発と組み込み] 第16章 - 興味深い流れ

黄舟
リリース: 2023-03-05 16:36:01
オリジナル
1434 人が閲覧しました


興味深いストリーム

PHP のよく言われる機能の 1 つは、ストリーム コンテキストです。このオプションのパラメーターは、ユーザー空間のほとんどのストリーム作成関連関数でも利用でき、一般化フレームワークとして機能します。特定のラッパーまたはストリーム実装との間で追加情報を渡すためのメソッドです。

各ストリームのコンテキストには、2 つの内部メッセージ タイプが含まれています。これらの値は、コンテキスト オプションです。コンテキスト内の 2 次元配列に配置され、通常はストリーム ラッパーの初期化動作を変更するために使用されます。コンテキスト パラメーターもあります。これはラッパーには認識されず、現在フロー内でイベント通知を行う方法を提供します。ラッパー層。

php_stream_context *php_stream_context_alloc(void);
ログイン後にコピー

この API 呼び出しを通じてコン​​テキストを作成できます。これにより、ストレージ領域が割り当てられ、コンテキストのオプションとパラメーターを保存するためのハッシュテーブルが初期化されます。また、クリーンアップされるリクエスト終了リソースとして自動的に登録されます。後で

オプションを設定します

コンテキストオプションを設定するための内部APIとユーザー空間APIは同等です:

int php_stream_context_set_option(php_stream_context *context,
            const char *wrappername, const char *optionname,
            zval *optionvalue);
ログイン後にコピー

ユーザー空間のプロトタイプは次のとおりです:

bool stream_context_set_option(resource $context,
            string $wrapper, string $optionname,
            mixed $value);
ログイン後にコピー

それらの唯一の違いは、ユーザー空間と内部で必要なデータ型です。次の例では、これら 2 つの API 呼び出しを使用して、組み込みラッパーを通じて HTTP リクエストを開始し、コンテキスト オプションを通じてそれをオーバーライドします。 user_agent 設定が変更されています。 .

php_stream  *php_varstream_get_homepage(const char *alt_user_agent TSRMLS_DC)
{
    php_stream_context  *context;
    zval    tmpval;

    context = php_stream_context_alloc(TSRMLS_C);
    ZVAL_STRING(&tmpval, alt_user_agent, 0); 
    php_stream_context_set_option(context, "http", "user_agent", &tmpval);
    return php_stream_open_wrapper_ex("http://www.php.net", "rb", REPORT_ERRORS | ENFORCE_SAFE_MODE, NULL, context);
}
ログイン後にコピー

トランスレーターによって使用されるphp-5.4.10のphp_stream_context_alloc()にはスレッドセーフティ制御が追加されているため、サンプルはそれに応じて変更されています。テストする際には注意してください

ここで、tmpval は永続的なストレージ領域を割り当てず、その文字列値はコピーによって設定されることに注意してください。 php_stream_context_set_option() は、受信した zval コンテンツを自動的にコピーします。 コンテキスト オプションを取得するために使用される API 呼び出しは、対応する設定 API のミラー イメージです。

int php_stream_context_get_option(php_stream_context *context,
            const char *wrappername, const char *optionname,
            zval ***optionvalue);
ログイン後にコピー
前を思い出してください。コンテキスト オプションはネストされた HashTable に保存されます。HashTable から値を取得するときの一般的なアプローチは次のとおりです。 zval ** へのポインタを zend_hash_find() に渡します。 もちろん、php_stream_context_get_option() は zend_hash_find() の特別なプロキシであるため、セマンティクスは同じです。

これは、php_stream_context_get_option() を使用して user_agent を設定する単純な例です。 ) 組み込みの http ラッパーを使用します:

zval **ua_zval;
char *user_agent = "PHP/5.1.0";
if (context &&
    php_stream_context_get_option(context, "http",
                "user_agent", &ua_zval) == SUCCESS &&
                Z_TYPE_PP(ua_zval) == IS_STRING) {
    user_agent = Z_STRVAL_PP(ua_zval);
}
ログイン後にコピー
この場合、ユーザー エージェント文字列の場合、max_redirects などの他のコンテキスト オプションは無意味であるため、文字列以外の値は破棄されます。 、数値が必要です。文字列の zval に数値を格納することは普遍的ではないため、設定を有効にするために型変換を実行する必要があります。

不幸的是这些变量是上下文拥有的, 因此它们不能直接转换; 而需要首先进行隔离再进行转换, 最终如果需要还要进行销毁:

long max_redirects = 20;
zval **tmpzval;
if (context &&
    php_stream_context_get_option(context, "http",
            "max_redirects", &tmpzval) == SUCCESS) {
    if (Z_TYPE_PP(tmpzval) == IS_LONG) {
        max_redirects = Z_LVAL_PP(tmpzval);
    } else {
        zval copyval = **tmpzval;
        zval_copy_ctor(©val);
        convert_to_long(©val);
        max_redirects = Z_LVAL(copyval);
        zval_dtor(©val);
    }
}
ログイン後にコピー

实际上, 在这个例子中, zval_dtor()并不是必须的. IS_LONG的变量并不需要zval容器之外的存储空间, 因此zval_dtor()实际上不会有真正的操作. 在这个例子中包含它是为了完整性考虑, 对于字符串, 数组, 对象, 资源以及未来可能的其他类型, 就需要这个调用了.

参数

虽然用户空间API中看起来参数和上下文选项是类似的, 但实际上在语言内部的php_stream_context结构体中它们被定义为不同的成员.

目前只支持一个上下文参数: 通知器. php_stream_context结构体中的这个元素可以指向下面的php_stream_notifier结构体:

typedef struct {
    php_stream_notification_func func;
    void (*dtor)(php_stream_notifier *notifier);
    void *ptr;
    int mask;
    size_t progress, progress_max;
} php_stream_notifier;
ログイン後にコピー

当将一个php_stream_notifier结构体赋值给context->notifier时, 它将提供一个回调函数func, 在特定的流上发生下表中的PHP_STREAM_NOTIFY_*代码表示的事件时被触发. 每个事件将会对应下面第二张表中的PHP_STREAM_NOTIFY_SEVERITY_*的级别:


事件代码

含义

RESOLVE

主机地址解析完成.多数基于套接字的包装器将在连接之前执行这个查询.

CONNECT

套接字流连接到远程资源完成.

AUTH_REQUIRED

要求されたリソースは利用できません理由はアクセス制御と認証の欠落です

MIME_TYPE_IS

-タイプ利用できません

FILE_SIZE_IS

リモートリソースの現在利用可能なサイズ

REDIRECT

元の URL リクエストにより、別の場所

PROGRESS

追加データの転送により、php_stream_notifier構造の進捗(可能 ) progress_max要素が更新されました (進捗情報CURLOPT_PROGRESSFUNCTIONphpマニュアルcurl_setoptを参照してください) および CURLOPT_NOPROGRESS オプション)

完了

ストリーム上で利用可能なデータはもうありません

FAILURE

リクエスト済みURLリソースが失敗したか不完全でした

AUTH_RESULT

リモートシステムが認可認証を処理しました



と同等です。


通知器实现提供了一个便利指针*ptr用于存放额外数据. 这个指针指向的空间必须在上下文析构时被释放, 因此必须指定一个dtor函数, 在上下文的最后一个引用离开它的作用域时调用这个dtor进行释放.

mask元素允许事件触发限定特定的安全级别. 如果发生的事件没有包含在mask中, 则通知器函数不会被触发.

最后两个元素progress和progress_max可以由流实现设置, 然而, 通知器函数应该避免使用这两个值, 除非它接收到PHP_STREAM_NOTIFY_PROGRESS或PHP_STREAM_NOTIFY_FILE_SIZE_IS事件通知.

下面是一个php_stream_notification_func()回调原型的示例:

void php_sample6_notifier(php_stream_context *context,
        int notifycode, int severity, char *xmsg, int xcode,
        size_t bytes_sofar, size_t bytes_max,
        void *ptr TSRMLS_DC)
{
    if (notifycode != PHP_STREAM_NOTIFY_FAILURE) {
        /* 忽略所有通知 */
        return;
    }
    if (severity == PHP_STREAM_NOTIFY_SEVERITY_ERR) {
        /* 分发到错误处理函数 */
        php_sample6_theskyisfalling(context, xcode, xmsg);
        return;
    } else if (severity == PHP_STREAM_NOTIFY_SEVERITY_WARN) {
        /* 日志记录潜在问题 */
        php_sample6_logstrangeevent(context, xcode, xmsg);
        return;
    }
}
ログイン後にコピー

默认上下文

在php5.0中, 当用户空间的流创建函数被调用时, 如果没有传递上下文参数, 请求一般会使用默认的上下文. 这个上下文变量存储在文件全局结构中: FG(default_context), 并且它可以和其他所有的php_stream_context变量一样访问. 当在用户空间脚本执行流的创建时, 更好的方式是允许用户指定一个上下文或者至少指定一个默认的上下文. 将用户空间的zval *解码得到php_stream_context可以使用php_steram_context_from_zval()宏完成, 比如下面改编自第14章"访问流"的例子:

PHP_FUNCTION(sample6_fopen)
{
    php_stream *stream;
    char *path, *mode;
    int path_len, mode_len;
    int options = ENFORCE_SAFE_MODE | REPORT_ERRORS;
    zend_bool use_include_path = 0;
    zval *zcontext = NULL;
    php_stream_context *context;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
            "ss|br", &path, &path_len, &mode, &mode_len,
                &use_include_path, &zcontext) == FAILURE) {
        return;
    }
    context = php_stream_context_from_zval(zcontext, 0);
    if (use_include_path) {
        options |= PHP_FILE_USE_INCLUDE_PATH;
    }
    stream = php_stream_open_wrapper_ex(path, mode, options,
                                    NULL, context);
    if (!stream) {
        RETURN_FALSE;
    }
    php_stream_to_zval(stream, return_value);
}
ログイン後にコピー

如果zcontext包含一个用户空间的上下文资源, 通过ZEND_FETCH_RESOURCE()调用获取到它关联的指针设置到context中. 否则, 如果zcontext为NULL并且php_stream_context_from_zval()的第二个参数设置为非0值, 这个宏则直接返回NULL. 这个例子以及几乎所有的核心流创建的用户空间函数中, 第二个参数都被设置为0, 此时将使用FG(default_context)的值.

过滤器

过滤器作为读写操作的流内容传输过程中的附加阶段. 要注意的是直到php 4.3中才加入了流过滤器, 在php 5.0对流过滤器的API设计做过较大的调整. 本章的内容遵循的是php 5的流过滤器规范.

在流上应用已有的过滤器

在一个打开的流上应用一个已有的过滤器只需要几行代码即可:

php_stream *php_sample6_fopen_read_ucase(const char *path
                                        TSRMLS_DC) {
    php_stream_filter *filter;
    php_stream *stream;

    stream = php_stream_open_wrapper_ex(path, "r",
                        REPORT_ERRORS | ENFORCE_SAFE_MODE,
                        NULL, FG(default_context));
    if (!stream) {
        return NULL;
    }

    filter = php_stream_filter_create("string.toupper", NULL,
                                        0 TSRMLS_CC);
    if (!filter) {
        php_stream_close(stream);
        return NULL;
    }
    php_stream_filter_append(&stream->readfilters, filter);

    return stream;
}
ログイン後にコピー

首先来看看这里引入的API函数以及它的兄弟函数:

php_stream_filter *php_stream_filter_create(
                const char *filtername, zval *filterparams,
                int persistent TSRMLS_DC);
void php_stream_filter_prepend(php_stream_filter_chain *chain,
                php_stream_filter *filter);
void php_stream_filter_append(php_stream_filter_chain *chain,
                php_stream_filter *filter);
ログイン後にコピー

php_stream_filter_create()的filterparams参数和用户空间对应的stream_filter_append()和stream_filter_prepend()函数的同名参数含义一致. 要注意, 所有传递到php_stream_filter_create()的zval *数据都不是过滤器所拥有的. 它们只是在过滤器创建期间被借用而已, 因此在调用作用域分配传入的所有内存空间都要手动释放.

如果过滤器要被应用到一个持久化流, 则必须设置persistent参数为非0值. 如果你不确认你要应用过滤器的流是否持久化的, 则可以使用php_stream_is_persistent()宏进行检查, 它只接受一个php_stream *类型的参数.

如在前面例子中看到的, 流过滤器被隔离到两个独立的链条中. 一个用于写操作中对php_stream_write()调用响应时的stream->ops->write()调用之前. 另外一个用于读操作中对stream->ops->read()取回的所有数据进行处理.

在这个例子中你使用&stream->readfilters指示读的链条. 如果你想要在写的链条上应用一个过滤器, 则可以使用&stream->writefilters.

定义一个过滤器实现

注册过滤器实现和注册包装器遵循相同的基础规则. 第一步是在MINIT阶段向php中引入你的过滤器, 与之匹配的是在MSHUTDOWN阶段移除它. 下面是需要调用的API原型以及两个注册过滤器工厂的示例:

int php_stream_filter_register_factory(
            const char *filterpattern,
            php_stream_filter_factory *factory TSRMLS_DC);
int php_stream_filter_unregister_factory(
            const char *filterpattern TSRMLS_DC);

PHP_MINIT_FUNCTION(sample6)
{
    php_stream_filter_register_factory("sample6",
            &php_sample6_sample6_factory TSRMLS_CC);
    php_stream_filter_register_factory("sample.*",
            &php_sample6_samples_factory TSRMLS_CC);
    return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(sample6)
{
    php_stream_filter_unregister_factory("sample6" TSRMLS_CC);
    php_stream_filter_unregister_factory("sample.*"
                                        TSRMLS_CC);
    return SUCCESS;
}
ログイン後にコピー

这里注册的第一个工厂定义了一个具体的过滤器名sample6; 第二个则利用了流包装层内部的基本匹配规则. 为了进行演示, 下面的用户空间代码, 每行都将尝试通过不同的名字实例化php_sample6_samples_factory.

<?php
    stream_filter_append(STDERR, &#39;sample.one&#39;);
    stream_filter_append(STDERR, &#39;sample.3&#39;);
    stream_filter_append(STDERR, &#39;sample.filter.thingymabob&#39;);
    stream_filter_append(STDERR, &#39;sample.whatever&#39;);
?>
ログイン後にコピー

php_sample6_samples_factory的定义如下面代码, 你可以将这些代码放到你的MINIT块上面:

#include "ext/standard/php_string.h"

typedef struct {
    char    is_persistent;
    char    *tr_from;
    char    *tr_to;
    int     tr_len;
} php_sample6_filter_data;

/* 过滤逻辑 */
static php_stream_filter_status_t php_sample6_filter(
        php_stream *stream, php_stream_filter *thisfilter, 
        php_stream_bucket_brigade *buckets_in, 
        php_stream_bucket_brigade *buckets_out, 
        size_t *bytes_consumed, int flags TSRMLS_DC) 
{
    php_stream_bucket       *bucket;
    php_sample6_filter_data *data       = thisfilter->abstract;
    size_t                  consumed    = 0;

    while ( buckets_in->head ) { 
        bucket      = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
        php_strtr(bucket->buf, bucket->buflen, data->tr_from, data->tr_to, data->tr_len);
        consumed    += bucket->buflen;
        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
    }   
    if ( bytes_consumed ) { 
        *bytes_consumed = consumed;
    }   
    return PSFS_PASS_ON;
}

/* 过滤器的释放 */
static void php_sample6_filter_dtor(php_stream_filter *thisfilter TSRMLS_DC)
{
    php_sample6_filter_data *data   = thisfilter->abstract;
    pefree(data, data->is_persistent);
}

/* 流过滤器操作表 */
static php_stream_filter_ops php_sample6_filter_ops = { 
    php_sample6_filter, 
    php_sample6_filter_dtor, 
    "sample.*",
};

/* 字符翻译使用的表 */
#define PHP_SAMPLE6_ALPHA_UCASE     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define PHP_SAMPLE6_ALPHA_LCASE     "abcdefghijklmnopqrstuvwxyz"
#define PHP_SAMPLE6_ROT13_UCASE     "NOPQRSTUVWXYZABCDEFGHIJKLM"
#define PHP_SAMPLE6_ROT13_LCASE     "nopqrstuvwxyzabcdefghijklm"

/* 创建流过滤器实例的过程 */
static php_stream_filter *php_sample6_filter_create(
        const char *name, zval *param, int persistent TSRMLS_DC)
{
    php_sample6_filter_data *data;
    char                    *subname;

    /* 安全性检查 */
    if ( strlen(name) < sizeof("sample.") || strncmp(name, "sample.", sizeof("sample.") - 1) ) { 
        return NULL;
    }   

    /* 分配流过滤器数据 */
    data    = pemalloc(sizeof(php_sample6_filter_data), persistent);

    if ( !data ) { 
        return NULL;
    }   

    /* 设置持久性 */
    data->is_persistent = persistent;

    /* 根据调用时的名字, 对过滤器数据进行适当初始化 */
    subname = (char *)name + sizeof("sample.") - 1;
    if ( strcmp(subname, "ucase") == 0 ) { 
        data->tr_from   = PHP_SAMPLE6_ALPHA_LCASE;
        data->tr_to     = PHP_SAMPLE6_ALPHA_UCASE;
    } else if ( strcmp(subname, "lcase") == 0 ) { 
        data->tr_from   = PHP_SAMPLE6_ALPHA_UCASE;
        data->tr_to     = PHP_SAMPLE6_ALPHA_LCASE;
    } else if ( strcmp(subname, "rot13") == 0 ) { 
        data->tr_from   = PHP_SAMPLE6_ALPHA_LCASE
                        PHP_SAMPLE6_ALPHA_UCASE;;
        data->tr_to     = PHP_SAMPLE6_ROT13_LCASE
                        PHP_SAMPLE6_ROT13_UCASE;
    } else {
        /* 不支持 */
        pefree(data, persistent);
        return NULL;
    }   

    /* 节省未来使用时每次的计算 */
    data->tr_len    = strlen(data->tr_from);

    /* 分配一个php_stream_filter结构并按指定参数初始化 */
    return php_stream_filter_alloc(&php_sample6_filter_ops, data, persistent);
}

/* 流过滤器工厂, 用于创建流过滤器实例(php_stream_filter_append/prepend的时候) */
static php_stream_filter_factory php_sample6_samples_factory = { 
    php_sample6_filter_create
};
ログイン後にコピー

译注: 下面是译者对整个流程的分析

1. MINIT阶段的register操作将在stream_filters_hash这个HashTable中注册一个php_stream_filter_factory结构, 它只有一个成员create_filter, 用来创建过滤器实例.

2. 用户空间代码stream_filter_append(STDERR, 'sapmple.one');在内部的实现是apply_filter_to_stream()函数(ext/standard/streamsfuncs.c中), 这里有两步操作, 首先创建过滤器, 然后将过滤器按照参数追加到流的readfilters/writefilters相应链中;

2.1 创建过滤器(php_stream_filter_create()): 首先直接按照传入的名字精确的从stream_filters_hash(或FG(stream_filters))中查找, 如果没有, 从右向左替换句点后面的内容为星号"*"进行查找, 直到找到注册的过滤器工厂或错误返回. 一旦找到注册的过滤器工厂, 就调用它的create_filter成员, 创建流过滤器实例.

2.2 直接按照参数描述放入流的readfilters/writefilters相应位置.

3. 用户向该流进行写入或读取操作时(以写为例): 此时内部将调用_php_stream_write(), 在这个函数中, 如果流的writefilters非空, 则调用流过滤器的fops->filter()执行过滤, 并根据返回状态做相应处理.

4. 当流的生命周期结束, 流被释放的时候, 将会检查流的readfilters/writefilters是否为空, 如果非空, 相应的调用php_stream_filter_remove()进行释放, 其中就调用了fops->fdtor对流过滤器进行释放.

上一章我们已经熟悉了流包装器的实现, 你可能能够识别这里的基本结构. 工厂函数(php_sample6_samples_filter_create)被调用分配一个过滤器实例, 并赋值给一个操作集合和抽象数据. 这上面的例子中, 你的工厂为所有的过滤器类型赋值了相同的ops结构, 但使用了不同的初始化数据.

调用作用域将得到这里分配的过滤器, 并将它赋值给流的readfilters链或writefilters链. 接着, 当流的读/写操作被调用时, 过滤器链将数据放入到一个或多个php_stream_bucket结构体, 并将这些bucket组织到一个队列php_stream_bucket_brigade中传递给过滤器.

这里, 你的过滤器实现是前面的php_sample6_filter, 它取出输入队列bucket中的数据, 使用php_sample6_filter_create中确定的字符表执行字符串翻译, 并将修改后的bucket放入到输出队列.

由于这个过滤器的实现并没有其他内部缓冲, 因此几乎不可能出错, 因此它总是返回PSFS_PASS_ON, 告诉流包装层有数据被过滤器存放到了输出队列中. 如果过滤器执行了内部缓冲消耗了所有的输入数据而没有产生输出, 就需要返回PSFS_FEED_ME标识过滤器循环周期在没有其他输入数据时暂时停止. 如果过滤器碰到了关键性的错误, 它应该返回PSFS_ERR_FATAL, 它将指示流包装层, 过滤器链处于不稳定状态. 这将导致流被关闭.

用于维护bucket和bucket队列的API函数如下:

php_stream_bucket *php_stream_bucket_new(php_stream *stream,
                      char *buf, size_t buflen, int own_buf,
                      int buf_persistent TSRMLS_DC);
ログイン後にコピー

创建一个php_stream_bucket用于存放到输出队列. 如果own_buf被设置为非0值, 流包装层可以并且通常都会修改它的内容或在某些点释放分配的内存. buf_persistent的非0值标识buf使用的内存是否持久分配的:

int php_stream_bucket_split(php_stream_bucket *in,
        php_stream_bucket **left, php_stream_bucket **right,
        size_t length TSRMLS_DC);
ログイン後にコピー

这个函数将in这个bucket的内容分离到两个独立的bucket对象中. left这个bucket将包含in中的前length个字符, 而right则包含剩下的所有字符.

void php_stream_bucket_delref(php_stream_bucket *bucket
                                                 TSRMLS_DC);
void php_stream_bucket_addref(php_stream_bucket *bucket);
ログイン後にコピー

Bucket使用和zval以及资源相同的引用计数系统. 通常, 一个bucket仅属于一个上下文, 也就是它依附的队列.

void php_stream_bucket_prepend(
                    php_stream_bucket_brigade *brigade,
                    php_stream_bucket *bucket TSRMLS_DC);
void php_stream_bucket_append(
        php_stream_bucket_brigade *brigade,
        php_stream_bucket *bucket TSRMLS_DC);
ログイン後にコピー

这两个函数扮演了过滤器子系统的苦力, 用于附加bucket到队列的开始(prepend)或末尾(append)

void php_stream_bucket_unlink(php_stream_bucket *bucket
                                                 TSRMLS_DC);
ログイン後にコピー

在过滤器逻辑应用处理完成后, 旧的bucket必须使用这个函数从它的输入队列删除(unlink).

php_stream_bucket *php_stream_bucket_make_writeable(
        php_stream_bucket *bucket TSRMLS_DC);
ログイン後にコピー

将一个bucket从它所依附的队列中移除, 并且如果需要, 赋值bucket->buf的内部缓冲区, 这样就使得它的内容可修改. 在某些情况下, 比如当输入bucket的引用计数大于1时, 返回的bucket将会是不同的实例, 而不是传入的实例. 因此, 我们要保证在调用作用域使用的是返回的bucket, 而不是传入的bucket.

小结

过滤器和上下文可以让普通的流类型行为被修改, 或通过INI设置影响整个请求, 而不需要直接的代码修改. 使用本章设计的计数, 你可以使你自己的包装器实现更加强大, 并且可以对其他包装器产生的数据进行改变.

次に、PHP API の作業を離れて、PHP ビルド システムの仕組みに戻り、他のアプリケーションにリンクするためのより複雑な拡張機能を生成し、ツールセットを使用して反復的なタスクを処理する簡単な方法を見つけます。

上記は、[翻訳] [php 拡張機能の開発と組み込み] 第 16 章 - 興味深い流れの内容です。その他の関連コンテンツについては、PHP 中国語 Web サイト (m.sbmmt.com) に注目してください。


関連ラベル:
php
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート

セキュリティコード


情報

情報更新.は1つに相当しますE_NOTICEError

WARN

小さなエラー状態.E_WARNINGと同等ですエラー

ERR

割り込みエラー条件.は、E_ERRORerror.