Home > Article > Backend Development > PHP implements strongly typed function return value
During the development process, the return value type of the function should be determined and unchanged, but PHP is a weakly typed language,
So PHP does not have such syntax verification, and because of this, it has caused many pitfalls.
For example, the following code:
<?php function getArticles(…){ $arrData = array(); if($exp1){ return $arrData; }else if($exp2){ return 1; }else{ return false; } } $arrData =getArticles(…); foreach($arrData as $record){ //do something. …. } ?>
The function getArticles returns different types of values according to different conditions, including bool, int, and arrays. Normally, this type of function hopes to return an array, and then use the array to do some other operations.
But because the function return value type is not fixed, various unexpected pitfalls are likely to occur when calling,
So I thought, since it can’t be standardized, then just force it.
The function/method return value can be forced to type, as shown in the figure
Supports four mandatory type restrictions: int, array, bool, object. When the return value does not match the type in the function declaration, a warning is thrown. I originally wanted to throw an error, but I felt that
was too harsh and could only be regarded as an exception, not an error, so I used warning instead.
PHP itself does not support syntax like int function, so if you want to support it, you must first get the syntax parser. Regarding the syntax parser, you can go here to view
details, which I won’t go into here. Okay,
First modify the syntax to scan the Zend/zend_language_scanner.l file
Add the following code:
<ST_IN_SCRIPTING>”int” { return T_FUNCTION_RETURN_INT; } <ST_IN_SCRIPTING>”bool” { return T_FUNCTION_RETURN_OBJECT; } <ST_IN_SCRIPTING>”object” { return T_FUNCTION_RETURN_OBJECT; } <ST_IN_SCRIPTING>”resource” { return T_FUNCTION_RETURN_RESOURCE; }
The meaning is very simple. When the scanner scans the keywords int, bool, object, resource, and array, it returns the corresponding T_FUNCTION_*. This is a token.
scanner performs different processing according to different tokens. The token must first be defined in the Zend/zend_language_parser.y file
Add the following code
………. %token T_FUNCTION_RETURN_INT %token T_FUNCTION_RETURN_BOOL %token T_FUNCTION_RETURN_STRING %token T_FUNCTION_RETURN_OBJECT %token T_FUNCTION_RETURN_RESOURCE 1 然后增加token处理逻辑: 1 function: T_FUNCTION { $$.u.opline_num = CG(zend_lineno);$$.u.EA.var = 0; } | T_FUNCTION_RETURN_INT T_FUNCTION { $$.u.opline_num = CG(zend_lineno); $$.u.EA.var = IS_LONG; } | T_FUNCTION_RETURN_BOOL T_FUNCTION { $$.u.opline_num = CG(zend_lineno); $$.u.EA.var = IS_BOOL; } | T_FUNCTION_RETURN_STRING T_FUNCTION { $$.u.opline_num = CG(zend_lineno); $$.u.EA.var = IS_STRING; } | T_FUNCTION_RETURN_OBJECT T_FUNCTION { $$.u.opline_num = CG(zend_lineno); $$.u.EA.var = IS_OBJECT; } | T_FUNCTION_RETURN_RESOURCE T_FUNCTION { $$.u.opline_num = CG(zend_lineno); $$.u.EA.var = IS_RESOURCE; } | T_ARRAY T_FUNCTION { $$.u.opline_num = CG(zend_lineno); $$.u.EA.var = IS_ARRAY; }
$$.u.EA.var stores the function return type, and finally use it to match the return value type,
so that the syntax interpreter can process our new PHP syntax.
This is not enough, you also need to modify the processing logic defined by the function declaration
Zend/zend_compile.c ::zend_do_begin_function_declaration …… zend_op_array op_array; char *name = function_name->u.constant.value.str.val; int name_len = function_name->u.constant.value.str.len; int function_type = function_token->u.EA.var; //保存函数类型,在语法解释器中增加的: $$.u.EA.var = IS_LONG; int function_begin_line = function_token->u.opline_num; …… op_array.function_name = name; op_array.fn_type = function_type; //将类型保存到op_array中, op_array.return_reference = return_reference; op_array.fn_flags |= fn_flags; op_array.pass_rest_by_reference = 0; ……….
PHP first parses the PHP syntax to generate the corresponding opcode, saves the required environment and parameter information to the execute_data global variable, and finally executes them one by one through the execute function opcode,
So to do processing, you need to save the function type into opcode: op_array.fn_type = function_type;
op_array does not have fn_type. To modify the structure of op_array, add zend_uint fn_type;
(For opcode, you can Imagine converting from c to assembly, I also have related articles on my blog, you can refer to it)
Finally, you need to modify the destroy function of opcode. The return of the function will generate token T_RETURN, and T_RETURN will call different calback functions according to the returned type. :
ZEND_RETURN_SPEC_CONST_HANDLER ZEND_RETURN_SPEC_TMP_HANDLER ZEND_RETURN_SPEC_VAR_HANDLER
It has three callbacks. If the return value is a const type data, then ZEND_RETURN_SPEC_CONST_HANDLER
The return value is temporary data, such as: return 1, then ZEND_RETURN_SPEC_TMP_HANDLER
The return value is a variable, such as: return $a, then ZEND_RETURN_SPEC_VAR_HANDLER
So we need to add processing logic to these three callback functions:
Add the following code before the callback function return
if((EG(active_op_array)->fn_type > 0) && Z_TYPE_P(retval_ptr) != EG(active_op_array)->fn_type){ php_error_docref0(NULL TSRMLS_DC,E_WARNING, “function name %s return a wrong type.”, EG(active_op_array)->function_name ); }
fn_type to compare with the type of the return value. If there is no match, this warning will be thrown.
I have already patched it. Currently it only supports the php5.3 version. You can use it to play if you need it.
I don’t know why this syntax is not officially supported, but I think it is quite necessary.