手把手撸PHP扩展 0x06: 协程创建(二)

这一篇文章,我们来实现一下我们创建协程的接口。

首先,我们需要对传给接口的参数进行解析。解析参数需要使用PHP提供给我们的宏来完成,分别是开头的和结尾的宏:

1、ZEND_PARSE_PARAMETERS_START
2、ZEND_PARSE_PARAMETERS_START_EX
3、ZEND_PARSE_PARAMETERS_END
4、ZEND_PARSE_PARAMETERS_END_EX

(其中,末尾的EXextended的缩写)

乍眼一看,好像这四个宏是两对,1、3一对,2、4一对。实际上这四个宏并不需要成对的使用。因为ZEND_PARSE_PARAMETERS_START只是ZEND_PARSE_PARAMETERS_START_EX的一种特殊情况,同理ZEND_PARSE_PARAMETERS_END也是ZEND_PARSE_PARAMETERS_END_EX的一种特殊情况。我们展开来看这几个宏比较直观。

ZEND_PARSE_PARAMETERS_START_EX

#define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { 
const int _flags = (flags);
int _min_num_args = (min_num_args);
int _max_num_args = (max_num_args);
int _num_args = EX_NUM_ARGS();
int _i;
zval *_real_arg, *_arg = NULL;
zend_expected_type _expected_type = Z_EXPECTED_LONG;
char *_error = NULL;
zend_bool _dummy;
zend_bool _optional = 0;
int error_code = ZPP_ERROR_OK;
((void)_i);
((void)_real_arg);
((void)_arg);
((void)_expected_type);
((void)_error);
((void)_dummy);
((void)_optional);

do {
if (UNEXPECTED(_num_args < _min_num_args) ||
(UNEXPECTED(_num_args > _max_num_args) &&
EXPECTED(_max_num_args >= 0))) {
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) {
if (_flags & ZEND_PARSE_PARAMS_THROW) {
zend_wrong_parameters_count_exception(_min_num_args, _max_num_args);
} else {
zend_wrong_parameters_count_error(_min_num_args, _max_num_args);
}
}
error_code = ZPP_ERROR_FAILURE;
break;
}
_i = 0;
_real_arg = ZEND_CALL_ARG(execute_data, 0);
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args) 
ZEND_PARSE_PARAMETERS_START_EX(0, min_num_args, max_num_args)

可以看到,ZEND_PARSE_PARAMETERS_START实际上就是让ZEND_PARSE_PARAMETERS_START_EX的第一个值默认为0了。我们重点来看看ZEND_PARSE_PARAMETERS_START_EX的三个参数:

flags:更改ZEND_PARSE_PARAMETERS_START的默认行为。flags可取的值有ZEND_PARSE_PARAMS_QUIETZEND_PARSE_PARAMS_THROW。我们来看看ZEND_PARSE_PARAMETERS_START_EX展开后与flags有关的地方:

if (UNEXPECTED(_num_args < _min_num_args) || 
(UNEXPECTED(_num_args > _max_num_args) &&
EXPECTED(_max_num_args >= 0))) {
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) {
if (_flags & ZEND_PARSE_PARAMS_THROW) {
zend_wrong_parameters_count_exception(_min_num_args, _max_num_args);
} else {
zend_wrong_parameters_count_error(_min_num_args, _max_num_args);
}
}
error_code = ZPP_ERROR_FAILURE;
break;
}

可以发现:

UNEXPECTED(_num_args < _min_num_args) || 
(UNEXPECTED(_num_args > _max_num_args) &&
EXPECTED(_max_num_args >= 0))

实际上就是判断传递个接口的参数个数合法,如果不合法就进入这个if分支。接着,

!(_flags & ZEND_PARSE_PARAMS_QUIET)

是判断flags是否设置了ZEND_PARSE_PARAMS_QUIET。如果没有设置ZEND_PARSE_PARAMS_QUIET,则往这个if分支走。然后:

_flags & ZEND_PARSE_PARAMS_THROW

是判断flags是否设置了ZEND_PARSE_PARAMS_THROW,如果设置了ZEND_PARSE_PARAMS_THROW,那么就抛出一个参数个数不对的异常,否则就报一个error

那么,如果flagsZEND_PARSE_PARAMS_QUIET的话,及时参数个数传递错误,也不会报错。

ZEND_PARSE_PARAMETERS_END_EX

#define ZEND_PARSE_PARAMETERS_END_EX(failure) 
} while (0);
if (UNEXPECTED(error_code != ZPP_ERROR_OK)) {
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) {
if (error_code == ZPP_ERROR_WRONG_CALLBACK) {
if (_flags & ZEND_PARSE_PARAMS_THROW) {
zend_wrong_callback_exception(_i, _error);
} else {
zend_wrong_callback_error(_i, _error);
}
} else if (error_code == ZPP_ERROR_WRONG_CLASS) {
if (_flags & ZEND_PARSE_PARAMS_THROW) {
zend_wrong_parameter_class_exception(_i, _error, _arg);
} else {
zend_wrong_parameter_class_error(_i, _error, _arg);
}
} else if (error_code == ZPP_ERROR_WRONG_ARG) {
if (_flags & ZEND_PARSE_PARAMS_THROW) {
zend_wrong_parameter_type_exception(_i, _expected_type, _arg);
} else {
zend_wrong_parameter_type_error(_i, _expected_type, _arg);
}
}
}
failure;
}
} while (0)
#define ZEND_PARSE_PARAMETERS_END() 
ZEND_PARSE_PARAMETERS_END_EX(return)
		} while (0);

是对ZEND_PARSE_PARAMETERS_START_EX或者ZEND_PARSE_PARAMETERS_START的那个do {进行闭合。error_code是在真正去解析参数本身是否合法的宏里面设置的,如果参数个数传递正确的情况下并且每个参数本身都是合法的,就不会进入后面的代码了,直接到了ZEND_PARSE_PARAMETERS_END()后面的代码。如果参数本身不合法,例如本来是要接收一个整型,但是传递了一个数组,那么就会设置error_code为对应的值。然后逐个if进行判断,抛出对应的异常。

而这个failure则是我们可以在error_code不为ZPP_ERROR_OK的时候,做一些操作。例如,返回一个falsePHP脚本。反正,你可以在这里做任何的操作。

实现参数解析

有了前面的基础,解析参数的代码就好理解多了:

PHP_METHOD(study_coroutine_util, create)
{
zend_fcall_info fci = empty_fcall_info;
zend_fcall_info_cache fcc = empty_fcall_info_cache;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_FUNC(fci, fcc)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
}

zend_fcall_info就是用来接收我们创建协程的时候传递的那个函数。我们使用ZEND_PARSE_PARAMETERS_END_EX的原因是因为我们希望在解析参数失败的时候,会向PHP返回一个false

RETURN_FALSE展开如下:

#define RETURN_FALSE  					{ RETVAL_FALSE; return; }
#define RETVAL_FALSE ZVAL_FALSE(return_value)

所以,RETURN_FALSE就是设置了变量return_value的值为false,然后在从这个接口return。但是,不是returnPHP脚本里面。原因很简单,是虚拟机去解析的PHP代码。

编译测试

我们编写如下PHP脚本:

<?php

StudyCoroutine::create();

然后编译、安装扩展:

~/codeDir/cppCode/study # ./make.sh

然后,执行脚本:

~/codeDir/cppCode/study # php test.php

Warning: StudyCoroutine::create() expects exactly 1 parameter, 0 given in /root/codeDir/cppCode/study/test.php on line 3
~/codeDir/cppCode/study #

给出了warning,说是需要传递一个参数。

然后再修改PHP脚本:

<?php

StudyCoroutine::create(1);

然后执行:

~/codeDir/cppCode/study # php test.php

Fatal error: Uncaught TypeError: Argument 1 passed to StudyCoroutine::create() must be callable, int given in /root/codeDir/cppCode/study/test.php:3
Stack trace:
#0 /root/codeDir/cppCode/study/test.php(3): StudyCoroutine::create(1)
#1 {main}
thrown in /root/codeDir/cppCode/study/test.php on line 3
~/codeDir/cppCode/study #

给出了Fetal,说是参数必须是可调用的,int被传递了。

再次修改脚本:

<?php

StudyCoroutine::create(function() {
echo 1;
});

执行:

~/codeDir/cppCode/study # php test.php
~/codeDir/cppCode/study #

没报错,参数解析成功。

下一篇:协程创建(三)

———-伟大的分割线———–


PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!


饭米粒只发原创或授权发表的文章,不转载网上的文章


所发的文章,均可找到原作者进行沟通。


也希望各位多多打赏(算作稿费给文章作者),更希望大家多多投搞。


投稿请联系:


shenzhe163@gmail.com


本文由 codinghuang 授权 饭米粒 发布,转载请注明本来源信息和以下的二维码(长按可识别二维码关注)

手把手撸PHP扩展 0x06: 协程创建(二)

未经允许不得转载:PHP100中文网 - 中国第一档PHP资源分享门户 » 手把手撸PHP扩展 0x06: 协程创建(二)

赞 (0) 打赏

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏