|
| 1 | +# Intl extension error conventions |
| 2 | + |
| 3 | +The intl extension has particular conventions regarding error reporting. These |
| 4 | +conventions are enumerated in this document. |
| 5 | + |
| 6 | +* The last error is always stored globally. |
| 7 | + |
| 8 | +The global error code can be obtained in userland with `intl_get_error_code()`. |
| 9 | +This is a `U_*` error code defined by ICU, but it does not have necessarily to |
| 10 | +be returned obtained after a call to an ICU function. That is to say, the |
| 11 | +internal PHP wrapper functions can set these error codes when appropriate. For |
| 12 | +instance, in response to bad arguments (e.g. `zend_parse_parameters()` failure), |
| 13 | +the PHP wrapper function should set the global error code to |
| 14 | +`U_ILLEGAL_ARGUMENT_ERROR`). |
| 15 | + |
| 16 | +The error code (an integer) can be converter to the corresponding enum name |
| 17 | +string in userland with `intl_error_name()`. |
| 18 | + |
| 19 | +The associated message can be obtained with `intl_get_error_message()`. This is |
| 20 | +a message set by the PHP wrapping code, not by ICU. The message should include |
| 21 | +the name of the function that failed in order to make debugging easier (though |
| 22 | +if you activate warnings with `intl.error_level` or exceptions with |
| 23 | +`intl.use_exceptions` you get more fine-grained information about where the |
| 24 | +error occurred). |
| 25 | + |
| 26 | +The internal PHP code can set the global last error with: |
| 27 | + |
| 28 | +```c |
| 29 | +void intl_error_set_code(intl_error* err, UErrorCode err_code); |
| 30 | +void intl_error_set_custom_msg(intl_error* err, char* msg, int copyMsg); |
| 31 | +void intl_error_set(intl_error* err, UErrorCode code, char* msg, int copyMsg); |
| 32 | +``` |
| 33 | +
|
| 34 | +and by passing `NULL` as the first parameter. The last function is a combination |
| 35 | +of the first two. If the message is not a static buffer, `copyMsg` should be 1. |
| 36 | +This makes the message string be copied and freed when no longer needed. There's |
| 37 | +no way to pass ownership of the string without it being copied. |
| 38 | +
|
| 39 | +* The last is ALSO stored in the object whose method call triggered the error, |
| 40 | + unless the error is due to bad arguments, in which case only the global error |
| 41 | + should be set. |
| 42 | +
|
| 43 | +Objects store an intl_error structed in their private data. For instance: |
| 44 | +
|
| 45 | +```c |
| 46 | +typedef struct { |
| 47 | + zend_object zo; |
| 48 | + intl_error err; |
| 49 | + Calendar* ucal; |
| 50 | +} Calendar_object; |
| 51 | +``` |
| 52 | + |
| 53 | +The global error and the object error can be SIMULTANEOUSLY set with these |
| 54 | +functions: |
| 55 | + |
| 56 | +```c |
| 57 | +void intl_errors_set_custom_msg(intl_error* err, char* msg, int copyMsg); |
| 58 | +void intl_errors_set_code(intl_error* err, UErrorCode err_code); |
| 59 | +void intl_errors_set(intl_error* err, UErrorCode code, char* msg, int copyMsg); |
| 60 | +``` |
| 61 | +
|
| 62 | +by passing a pointer to the object's `intl_error` structed as the first parameter. |
| 63 | +Node the extra `s` in the functions' names (`errors`, not `error`). |
| 64 | +
|
| 65 | +Static methods should only set the global error. |
| 66 | +
|
| 67 | +* Intl classes that can be instantiated should provide `::getErrorCode()` and |
| 68 | + `getErrorMessage()` methods. |
| 69 | +
|
| 70 | +These methods are used to retrieve the error codes stored in the object's |
| 71 | +private `intl_error` structured and mirror the global `intl_get_error_code()` |
| 72 | +and `intl_get_error_message()`. |
| 73 | +
|
| 74 | +* Intl methods and functions should return `FALSE` on error (even argument |
| 75 | + parsing errors), not `NULL`. Constructors and factory methods are the |
| 76 | + exception; these should return `NULL`, not `FALSE`. |
| 77 | +
|
| 78 | +Not that constructors in Intl generally (always?) don't throws exceptions. They |
| 79 | +instead destroy the object to that the result of new `IntlClass()` can be |
| 80 | +`NULL`. This may be surprising. |
| 81 | +
|
| 82 | +* Intl functions and methods should reset the global error before doing anything |
| 83 | + else (even parse the arguments); instance methods should also reset the |
| 84 | + object's private error. |
| 85 | +
|
| 86 | +Errors should be lost after a function call. This is different from the way ICU |
| 87 | +operates, where functions return immediately if an error is set. |
| 88 | +
|
| 89 | +Error resetting can be done with: |
| 90 | +
|
| 91 | +```c |
| 92 | +void intl_error_reset(NULL); /* reset global error */ |
| 93 | +void intl_errors_reset(intl_error* err); /* reset global and object error */ |
| 94 | +``` |
| 95 | + |
| 96 | +In practice, `intl_errors_reset()` is not used because most classes have also |
| 97 | +plain functions mapped to the same internal functions as their instance methods. |
| 98 | +Fetching of the object is done with `zend_parse_method_parameters()` instead of |
| 99 | +directly using `getThis()`. Therefore, no reference to object is obtained until |
| 100 | +the arguments are fully parsed. Without a reference to the object, there's no |
| 101 | +way to reset the object's internal error code. Instead, resetting of the |
| 102 | +object's internal error code is done upon fetching the object from its zval. |
| 103 | + |
| 104 | +Example: |
| 105 | + |
| 106 | +```c |
| 107 | +U_CFUNC PHP_FUNCTION(breakiter_set_text) |
| 108 | +{ |
| 109 | + /* ... variable declations ... */ |
| 110 | + BREAKITER_METHOD_INIT_VARS; /* macro also resets global error */ |
| 111 | + object = getThis(); |
| 112 | + |
| 113 | + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", |
| 114 | + &text, &text_len) == FAILURE) { |
| 115 | + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, |
| 116 | + "breakiter_set_text: bad arguments", 0); |
| 117 | + RETURN_FALSE; |
| 118 | + } |
| 119 | + |
| 120 | + /* ... */ |
| 121 | + |
| 122 | + BREAKITER_METHOD_FETCH_OBJECT; /* macro also resets object's error */ |
| 123 | + |
| 124 | + /* ... */ |
| 125 | +} |
| 126 | +``` |
| 127 | +
|
| 128 | +Implementations of `::getErrorCode()` and `::getErrorMessage()` should not reset |
| 129 | +the object's error code. |
0 commit comments