프로젝트에 회원 만료 여부를 비교하는 기능이 있습니다. 동료의 코드를 검토한 결과 작성 방법이 다소 이상한 것을 발견했지만 온라인에서는 버그가 없었습니다.
구현:
-
- $expireTime = "2014-05-01 00:00:00";
- $currentTime = 날짜('Y-m-d H:i:s' , time());
-
- if($currentTime < $expireTime) {
- return false;
- } else {
- return true;
- }
코드 복사 두 개의 시간을 비교해야 하는 경우 일반적으로 유닉스 타임스탬프로 변환하여 두 개의 int 유형 숫자와 비교합니다. 이 구현은 구체적으로 시간을 문자열로 표현한 다음 두 문자열에 대해 비교 작업을 수행합니다.
작성방법은 차치하고, PHP 내부에서는 어떻게 비교가 이루어지는지 매우 궁금합니다.
더 이상 고민하지 말고 소스 코드부터 추적을 시작해 보겠습니다.
편집 기간
다음과 유사한 구문은 zend_언어_parse.y에서 찾을 수 있습니다.
- expr === expr { zend_do_binary_op(ZEND_IS_IDENTICAL, &$$, &$1, &$3 TSRMLS_CC) }
- expr !== expr { zend_do_binary_op(ZEND_IS_NOT_IDENTICAL, &$$, &$1, &$3 TSRMLS_CC) }
- expr == expr { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$1, &$3 TSRMLS_CC) }
- expr != expr { zend_do_binary_op(ZEND_IS_NOT_EQUAL, &$$, &$1, &$3 TSRMLS_CC) }
- expr < expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$1, &$3 TSRMLS_CC) }
- expr <= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$1, &$3 TSRMLS_CC) }
- expr > expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$3, &$1 TSRMLS_CC) }
- expr >= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$3, &$1 TSRMLS_CC) }
코드 복사
분명히 여기에서 opcode로 컴파일하는 것이 편리합니다. zend_do_binary_op.
-
- 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;
- }
-
코드 복사
이 함수는 특별한 처리를 수행하지 않고 단순히 opcode, 피연산자 1, 피연산자 2만 저장합니다.
집행기간
Opcode에 따라 해당 처리 함수인 ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER로 점프합니다.
-
- 정적 정수 ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
- {
- zend_op *opline = EX(opline);
-
- z 발 *결과 = &EX_T(opline->result.u.var).tmp_var;
-
- Compare_function(result,
- &opline->op1.u.constant,
- &opline->op2.u. 상수 TSRMLS_CC);
- ZVAL_BOOL(결과, (Z_LVAL_P(결과) < 0));
-
-
- ZEND_VM_NEXT_OPCODE();
- }
-
코드 복사 두 zval의 비교는 Compare_function을 사용하여 처리됩니다.
- ZEND_API int Compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
- {
- int ret;
- int 변환 = 0;
- zval op1_copy, op2_copy;
- zval *op_free;
-
- while (1) {
- 스위치 (TYPE_PAIR(Z_TYPE_P(op1)), Z_TYPE_P( op2))) {
- 케이스 TYPE_PAIR(IS_LONG, IS_LONG):
- ...
- 케이스 TYPE_PAIR(IS_DOUBLE, IS_LONG):
- ...
- 케이스 TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
- ...
- ...
- // 두 문자열 비교
- case TYPE_PAIR(IS_STRING, IS_STRING):
- zendi_smart_strcmp(result, op1, op2);
- return SUCCESS;
- ...
- }
- }
- }
-
코드 복사이 함수는 이 기사의 사례에 따라 몇 가지 상황을 예시합니다. 자세히 살펴보려면 zendi_smart_strcmp를 입력하세요.
- ZEND_API void zendi_smart_strcmp(zval *result, zval *s1, zval *s2) /* {{{ */
- {
- int ret1 , ret2;
- long lval1, lval2;
- double dval1, dval2;
-
- // 문자열을 숫자 유형으로 변환해 봅니다.
- 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, &dval2, 0))) {
- // 숫자 간 변환 비교
- ...
- } else {
- // 모두 숫자로 변환할 수 없습니다
- // 그런 다음 zend_binary_zval_strcmp를 호출합니다
- // 기본적으로 memcmp의 1계층 캡슐화
- Z_LVAL_P(결과) = zend_binary_zval_strcmp(s1, s2);
- ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(result)));
- }
- }
-
코드 복사'2014-05-01 00:00:00'을 숫자로 변환할 수 있나요?
is_numeric_string의 구현 규칙을 살펴봐야 합니다.
- 정적 인라인 zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, intallow_errors)
- {
- const char *ptr;
- int base = 10, 숫자 = 0, dp_or_e = 0;
- double local_dval;
- zend_uchar type;
-
- if (!length) {
- return 0;
- }
-
- /* 문자열 시작 부분의 공백 부분을 잘라냅니다*/
- while (*str == ' ' || *str == 't' || *str = = 'n' || *str == 'r' || *str == 'v' || *str == 'f') {
- str ;
- 길이--;
- }
- ptr = str;
-
- if (*ptr == '-' || *ptr == ' ') {
- ptr ;
- }
-
- if (ZEND_IS_DIGIT( *ptr) ) {
- /* 16진수인지 확인*/
- if (length > 2 && *str == '0' && (str[1] == 'x' || str[1 ] = = 'X')) {
- base = 16;
- ptr = 2;
- }
-
- /* 다음 0은 무시 */
- while (*ptr == ' 0') {
- ptr ;
- }
-
- /* 자릿수를 계산하여 정수인지 부동 소수점인지 결정합니다*/
- for (type = IS_LONG; !(digits > ;= MAX_LENGTH_OF_LONG && (dval || 허용_오류 == 1)); 숫자 , ptr ) {
- check_digits:
- if (ZEND_IS_DIGIT(*ptr) || (base == 16 && ZEND_IS_XDIGIT(*ptr)) ) {
- 계속;
- } 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 process_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_strtod(str, (char **)&ptr);
- }
- type = IS_DOUBLE;
- }
- } else if (*ptr = = '.' && ZEND_IS_DIGIT(ptr[1])) {
- // 부동 소수점 숫자 처리
- } else {
- return 0;
- }
-
- // 내결함성인 경우 허용되지 않는 경우 오류를 보고하고 종료합니다.
- if (ptr != str length) {
- if (!allow_errors) {
- return 0;
- }
- if (allow_errors == - 1) {
- zend_error (E_NOTICE, "잘못된 형식의 숫자 값이 발생했습니다.");
- }
- }
-
- // 내결함성을 허용하려면 str을 숫자로 변환해 보세요
- 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_strtod(str, NULL);
- }
-
- return IS_DOUBLE;
- }
- }
-
- if (lval) {
- *lval = strtol(str, NULL, base);
- }
-
- return IS_LONG;
- } else {
- if (dval) {
- *dval = local_dval;
- }
-
- return IS_DOUBLE;
- }
- }
-
코드 복사
코드는 비교적 길지만 주의 깊게 읽어보면 str을 num으로 변환하는 규칙은 여전히 매우 명확합니다.
이 예에서 "2014-05-01 00:00:00"을 숫자로 변환할 수 없음을 직접 결정하는 Allow_errors 매개변수에 특히 주의하세요.
결국 "2014-04-17 00:00:00"
memcmp이기 때문에 글 시작 부분에서 언급한 작성 방법도 왜 제대로 실행되는지 이해하는 것은 어렵지 않습니다.
내결함성 변환
Allow_errors는 언제 true인가요? 훌륭한 예는 zend_parse_parameters입니다. zend_parse_parameters의 구현은 자세히 설명하지 않습니다. 관심 있는 독자는 스스로 연구할 수 있습니다. is_numeric_string을 호출하면allow_errors가 -1로 설정됩니다.
예:
-
- static void php_date(INTERNAL_FUNCTION_PARAMETERS, int localtime)
- {
- char *format;
- int format_len;
- long ts;
- char *string;
-
- // 예상되는 두 번째 매개변수는 긴 타임스탬프입니다.
- // 상위 계층이 호출될 때 문자열이 실수로 전달되었다고 가정하면 zend_parse_parameters는 여전히 그만큼 시도합니다. 가능한 한 긴 문자열로 구문 분석
- if (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);
- }
-
코드 복사
PHP 날짜 함수의 내부 구현입니다.
date를 호출할 때 두 번째 매개변수를 string으로 전달하면 효과는 다음과 같습니다.
-
- echo date('Y-m-d', '0-1-2');
-
- // 출력
- PHP 공지사항 : 1행의 명령줄 코드에서 잘못된 형식의 숫자 값이 발견되었습니다.
- 1970-01-01
-
코드 복사
알림은 다음과 같습니다. 레벨 오류가 보고되었지만 여전히 '0-1-2'를 0으로 성공적으로 변환했습니다
|