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