A case for comparing a "date" string. There is a function in the project to compare whether the member has expired. the code of the review colleague found that the writing method is odd, but there were no bugs online. The implementation is roughly as follows: $ expireTime2014-05 project has a function is to compare whether the member has expired, review colleagues' code, found that the writing is odd, but there is no bug in the online.
The implementation is roughly as follows:
$expireTime = "2014-05-01 00:00:00";$currentTime = date('Y-m-d H:i:s', time());if($currentTime < $expireTime) { return false;} else { return true;}
If two times need to be compared, it is usually converted to a unix timestamp and compared with two int-type numbers. This implementation specifically expresses the time as a string and then compares the two strings.
Aside from writing, I'm curious about how php compares internally.
In other words, we should keep track of the source code.
Compilation phase
In zend_language_parse.y, you can find a syntax similar to the following:
=== expr { zend_do_binary_op(ZEND_IS_IDENTICAL, &$$, &$, &$ !== { zend_do_binary_op(ZEND_IS_NOT_IDENTICAL, &$$, &$, &$ == { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$, &$ != { zend_do_binary_op(ZEND_IS_NOT_EQUAL, &$$, &$, &$ < { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$, &$ <= { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$, &$ > { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$, &$ >= { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$, &$ TSRMLS_CC); }
Obviously, zend_do_binary_op is used to compile opcode here.
void zend_do_binary_op(zend_uchar op, znode *result, const znode *op1, const znode *op2 TSRMLS_DC) /* {{{ */{zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);opline->opcode = op;opline->result.op_type = IS_TMP_VAR;opline->result.u.var = get_temporary_variable(CG(active_op_array));opline->op1 = *op1;opline->op2 = *op2;*result = opline->result;}
This function does not do anything special, but simply saves opcode, operand 1, and operand 2.
Execution period
According to the opcode, jump to the corresponding processing function: ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER.
static int ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS){zend_op *opline = EX(opline);zval *result = &EX_T(opline->result.u.var).tmp_var;compare_function(result,&opline->op1.u.constant,&opline->op2.u.constant TSRMLS_CC);ZVAL_BOOL(result, (Z_LVAL_P(result) < 0));ZEND_VM_NEXT_OPCODE();}
Note that the comparison between the two zval operations uses compare_function for processing.
ZEND_API int compare_function (zval * result, zval * op1, zval * op2 TSRMLS_DC)/* {*/{int ret; int converted = 0; zval op1_copy, op2_copy; zval * op_free; while (1) {switch (TYPE_PAIR (Z_TYPE_P (op1), Z_TYPE_P (op2) {case TYPE_PAIR (IS_LONG, IS_LONG ):... case TYPE_PAIR (IS_DOUBLE, IS_LONG ):... case TYPE_PAIR (IS_DOUBLE, IS_DOUBLE ):...... // compare two strings: case TYPE_PAIR (IS_STRING, IS_STRING): zendi_smart_strcmp (result, op1, op2); return SUCCESS ;...}}}
This function illustrates several cases. based on the case in this article, we will go to zendi_smart_strcmp to find out what is going on:
ZEND_API void zendi_smart_strcmp (zval * result, zval * s1, zval * s2)/* {*/{int ret1, ret2; long lval1, lval2; double dval1, Dval; // convert the string to the numeric type if (ret1 = is_numeric_string (Z_STRVAL_P (s1), Z_STRLEN_P (s1), & lval1, & dval1, 0 )) & (ret2 = is_numeric_string (Z_STRVAL_P (s2), Z_STRLEN_P (s2), & lval2, & Dval, 0) {// compare numbers ...} else {// cannot all be converted into numbers // call zend_binary_zval_strcmp // A layer encapsulation of Z_LVAL_P (result) = zend_binary_zval_strcmp (s1, s2); ZVAL_LONG (result, ZEND_NORMALIZE_BOOL (Z_LVAL_P (result )));}}
Can "00:00:00" be converted into numbers?
Take a look at the implementation rules of is_numeric_string.
Static inline zend_uchar is_numeric_string (const char * str, int length, long * lval, double * dval, int allow_errors) {const char * ptr; int base = 10, digits = 0, dp_or_e = 0; double local_dval; zend_uchar type; if (! Length) {return 0 ;} /* trim drops the blank part starting with a string */while (* str = ''| * str = '\ t' | * str =' \ n' | | * str = '\ r' | * str =' \ V' | * str = '\ f ') {str ++; length --;} ptr = str; if (* ptr = '-' | * ptr = '+') {ptr ++ ;} if (ZEND_IS_DIGIT (* ptr )) {/* determine whether it is in hexadecimal format */if (length> 2 & * str = '0' & (str [1] = 'X' | str [1] = 'x ')) {base = 16; ptr + = 2;}/* ignore some subsequent 0 */while (* ptr = '0') {ptr ++ ;} /* calculate the number of digits and decide whether it is an integer or a floating point */ For (type = IS_LONG ;! (Digits> = MAX_LENGTH_OF_LONG & (dval | allow_errors = 1); digits ++, ptr ++) {check_digits: if (ZEND_IS_DIGIT (* ptr) | (base = 16 & ZEND_IS_XDIGIT (* ptr) {continue;} else if (base = 10) {if (* ptr = '. '& dp_or_e <1) {goto process_double;} else if (* ptr = 'e' | * ptr = 'e') & dp_or_e <2) {const char * e = ptr + 1; if (* e = '-' | * e = '+') {ptr = e ++ ;} if (ZEND_IS_DIGIT (* e) {goto proce Ss_double ;}}} break;} if (base = 10) {if (digits >= MAX_LENGTH_OF_LONG) {dp_or_e =-1; goto process_double ;}} else if (! (Digits <SIZEOF_LONG * 2 | (digits = SIZEOF_LONG * 2 & ptr [-digits] <= '7') {if (dval) {local_dval = zend_hex_strtodd (str, (char **) & ptr);} type = IS_DOUBLE ;}} else if (* ptr = '. '& ZEND_IS_DIGIT (ptr [1]) {// process floating point number} else {return 0 ;}// if Fault tolerance is not allowed, an error is returned and exit if (ptr! = Str + length) {if (! Allow_errors) {return 0;} if (allow_errors =-1) {zend_error (E_NOTICE, "A non well formed numeric value encountered") ;}// Fault tolerance is allowed, convert str to the number if (type = IS_LONG) {if (digits = MAX_LENGTH_OF_LONG-1) {int cmp = strcmp (& ptr [-digits], long_min_digits ); if (! (Cmp <0 | (cmp = 0 & * str = '-') {if (dval) {* dval = zend_strtodd (str, NULL );} return IS_DOUBLE ;}}if (lval) {* lval = strtol (str, NULL, base);} return IS_LONG;} else {if (dval) {* dval = local_dval ;} return IS_DOUBLE ;}}
The code is long, but read it carefully. The rules for converting str to num are still very clear.
Note that the allow_errors parameter directly determines that "00:00:00" cannot be converted to a number in this example.
Therefore, the running of "00:00:00" <"00:00:00" is the memcmp branch.
Since it is memcmp, it is not difficult to understand why the writing mentioned at the beginning of the article can also run correctly.
Fault tolerance conversion
When is allow_errors true? An excellent example is zend_parse_parameters. the implementation of zend_parse_parameters is not described in detail. interested readers can study it on their own. When is_numeric_string is called, allow_errors is set to-1.
For example:
Static void php_date (INTERNAL_FUNCTION_PARAMETERS, int localtime) {char * format; int format_len; long ts; char * string; // the expected second parameter is timestamp, long // if the upper-layer call is input by mistake, zend_parse_parameters tries to resolve the string to longif (zend_parse_parameters (ZEND_NUM_ARGS () TSRMLS_CC, "s | l ", & format, & format_len, & ts) = FAILURE) {RETURN_FALSE;} if (ZEND_NUM_ARGS () = 1) {ts = time (NULL );} string = php_format_date (format, format_len, ts, localtime TSRMLS_CC); RETVAL_STRING (string, 0 );}
This is an internal implementation of the date function in php.
When we call date, if we pass the second parameter into string, the effect is as follows:
Echo date ('Y-m-D', '0-1-2 '); // output PHP Notice: A non well formed numeric value encountered in Command line code on line 11970-01-01
Although a notice-level error is reported, '0-1-2 'is successfully converted to 0.
Bytes. The implementation is roughly as follows: $ expireTime = "2014-05...