想必phper
对date()
函数不会陌生,date('Y-m-d H:i:s')
是常见的用法。date
扩展代码行数不少,而且一大块宏让人摸不着头脑。先根据自己的思路写一个吧,给PHP
添加一个打印当前时间格式化形式的函数。函数原型位于ext/standard/basic_functions.stub.php
,function pmydate(string $value): void {}
函数接收一个格式化字符串,仅支持Y
、m
、d
、H
、i
、s
几种格式,没有返回值,直接输出结果。
// ext/standard/basic_functions.stub.php
function pmydate(string $value): void {}
扩展代码:
// ext/standard/basic_functions.c
PHP_FUNCTION(pmydate)
{
zval *zv_ptr;
php_printf("passed %d parameters to the function: pmydate\n", ZEND_NUM_ARGS());
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv_ptr) == FAILURE) {
return;
}
if (Z_TYPE_P(zv_ptr) != IS_STRING) {
php_printf("Expect one string argument\n");
return;
}
time_t now = time(0);
struct tm *lt = localtime(&now);
char c;
int val;
char type;
char prev = '\0';
for (int i = 0; i < (*zv_ptr).value.str->len; i++) {
c = (*zv_ptr).value.str->val[i];
type = 's';
switch (c) {
case 'Y':
val = lt->tm_year + 1900;
type = 'i';
break;
case 'm':
val = lt->tm_mon + 1;
break;
case 'd':
val = lt->tm_mday;
break;
case 'H':
val = lt->tm_hour;
break;
case 'i':
val = lt->tm_min;
break;
case 's':
val = lt->tm_sec;
break;
case '\\':
val = c;
type = '\0';
break;
default:
val = c;
type = 'c';
}
if (prev != '\\') {
switch (type) {
case 's':
php_printf("%02d", val);
break;
case 'i':
php_printf("%d", val);
break;
case 'c':
php_printf("%c", val);
break;
default:;
}
} else {
php_printf("%c", c);
}
prev = c;
}
php_printf("\n");
}
用法如下:
$ ./output/bin/php -r 'pmydate("Y-m-d H:i:s \Y-\m-\d \H:\i:\s");'
passed 1 parameters to the function: pmydate
2025-01-08 00:00:00 Y-m-d H:i:s
- 定义函数: 使用
PHP_FUNCTION
宏封装函数名; - 解释参数:
zv_ptr
是一个指针变量,指向zval
类型,zval
是PHP
变量类型;php_printf("passed %d parameters to the function: pmydate\n", ZEND_NUM_ARGS())
仅用于查看函数参数数量;zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv_ptr)
解释函数参数到zv_ptr
指针,如果解释参数失败,直接返回; - 判断参数类型:
Z_TYPE_P(zv_ptr)
获取解释后的zv_ptr
指向的参数类型,如果不是IS_STRING
类型,返回; - 获取时间:使用
C
语言的时间函数获取当前时间,保存到指向struct tm
类型的指针中; - 声明临时变量:
c
保存当前指向的字节,val
保存格式串对应字节表示的时间,type
表示当前字节应该在输出时以什么类型来处理,prev
保存上一个字节; - 遍历字符串参数:由于要接收一个格式化字符串,因此遍历的数据位于
(*zv_ptr).value.str
中,(*zv_ptr)
就是格式化字符串;(*zv_ptr).value
是zend_value
2.类型,实际上就是一个union
,保存具体的参数;字符串参数当然就要通过zend_value
的str
字段来获取了; - 逐个获取字符串参数中的字节:
(*zv_ptr).value.str
是指针,指向zend_string
类型,zend_string
同时也是一个struct
,字符串的字节实际保存在zend_string
的val
字段中,因此(*zv_ptr).value.str->val[i]
指向单个字节; - 根据字符串参数的字节获取对应的时间,其中
\
字符特殊处理一下,前一个字节为\
时,当前字符不作解释; - 其中大片的
switch
分支不作详细解释,应该很好理解; - 为什么不使用
printf
而要使用php_printf
?因为PHP
有多种SAPI
,如果只是使用printf
只会默认在控制台输出,封装的php_printf
则会将数据保存到缓冲中,根据不同的SAPI
写入不同的输出流;