プロジェクトにはメンバーの有効期限が切れているかどうかを比較する機能があり、同僚のコードをレビューしたところ、書き方がかなり変であることがわかりましたが、オンライン上でのバグはありませんでした。
実績:
-
- $expireTime = "2014-05-01 00:00:00";
- $currentTime = date('Y-m-d H:i:s', time());
-
- if($currentTime < $expireTime) {
- return false;
- } else {
- return true;
- }
コードをコピー 2 つの時間を比較する必要がある場合、通常は 2 つの int 数値比較を使用して UNIX タイムスタンプに変換されます。この実装では、具体的には時間を文字列として表現し、2 つの文字列に対して比較演算を実行します。
書くことはさておき、PHP の内部で比較がどのように行われるのか非常に興味があります。
早速、ソースコードから追跡を開始しましょう。
編集期間
次のような構文が zend_ language_parse.y にあります。
=expr === Expr {zend_do_binary_op (ZEND_IS_IDENTICAL, & $ 1, & $ 3 TSRMLS_CC); } expr == expr { zend_IS_EQUAL, &$ $, &$1, &$3 TSRMLS_CC); }expr != expr { zend_is_NOT_EQUAL, &$$, &$1, &$3 TSRMLS_CC) } expr { zend_is_SMALLER, &$$, &$1 , &$3 TSRMLS_CC); } expr <= expr { zend_is_SMALLER_OR_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }- expr > TSRMLS_CC); }
- expr >= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$3, &$1 TSRMLS_CC); }
-
-
- コードをコピー
-
- 明らかに、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) _CC ); opline->opcode = op; opline->op1 = *op1; opline->opcode = op; opline->op1 = *op1; - opline->op2 = *op2;
- *result = opline->result;
- }
-
-
-
- コードをコピー
-
-
- この関数は特別な処理を行わず、単にオペコードとオペランド 1 とオペランドを保存するだけです。オペランド2。
-
- 実施期間
オペコードに従って、対応する処理関数: 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-> .u.var).tmp_var; Compare_function( result, &opline->op1.u.constant, &opline->op2.u.constant TSRMLS_CC);- ZVAL_BOOL(result, (Z_LVAL_P(result)
-
- ZEND_VM_NEXT_OPCODE();
- }
-
-
-
- コードをコピーします
-
-
- 2 つの zval の比較は、compare_function によって処理されることに注意してください。
-
-
-
-
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):
- ...
- ...
- // 2 つの文字列を比較します
- 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(結果, ZEND_NORMALIZE_BOOL(Z_LVAL_P(結果)));
- }
- }
-
コードをコピー
次に " 2014-05-01 缶 00: 00:00」を数値に変換できますか?
まだ is_numeric_string の実装規則を確認する必要があります。
-
- static inline 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 == 'r' || *str == 'f') {
- str++;
- length- -;
- }
- 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 || allowed_errors == 1)); digits++, ptr++) {
- check_digitals:
- 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 process_double;
- }
- }
- }
-
- Break;
- }
-
- if (base == 10) {
- if (数字 >= MAX_LENGTH_OF_LONG) {
- dp_or_e = -1;
- goto process_double;
- }
- } else if (!(数字 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 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;
-
- // 予期される 2 番目のパラメータは timestamp であり、long です
- // 上位層が呼び出されたときに文字列が誤って渡されたと仮定しても、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 を呼び出すときに、2 番目のパラメーターが文字列に渡されると、その効果は次のようになります。
-
- echo date('Y-m-d', '0-1-2');
-
- // Output
- PHP 注意: コマンドラインコードの行 1 で整形式でない数値が検出されました
- 1970- 01-01
-
コードをコピー
通知レベルのエラーが報告されましたが、「0-1-2」は引き続き 0 に正常に変換されました
|