From ea1ee1c4d6c25e31d265554cb71e08a2731f5532 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Thu, 8 May 2025 01:08:39 +0300 Subject: [PATCH 1/4] Intl: Add IntlListFormatter class --- ext/intl/config.m4 | 3 + ext/intl/listformatter/listformatter.stub.php | 49 ++++ .../listformatter/listformatter_arginfo.h | 77 +++++++ ext/intl/listformatter/listformatter_class.c | 217 ++++++++++++++++++ ext/intl/listformatter/listformatter_class.h | 55 +++++ ext/intl/listformatter/listformatter_data.c | 48 ++++ ext/intl/listformatter/listformatter_data.h | 35 +++ ext/intl/php_intl.c | 5 + .../listformatter/listformatter_basic.phpt | 32 +++ .../listformatter/listformatter_error.phpt | 36 +++ .../listformatter_with_paramaters.phpt | 169 ++++++++++++++ 11 files changed, 726 insertions(+) create mode 100644 ext/intl/listformatter/listformatter.stub.php create mode 100644 ext/intl/listformatter/listformatter_arginfo.h create mode 100644 ext/intl/listformatter/listformatter_class.c create mode 100644 ext/intl/listformatter/listformatter_class.h create mode 100644 ext/intl/listformatter/listformatter_data.c create mode 100644 ext/intl/listformatter/listformatter_data.h create mode 100644 ext/intl/tests/listformatter/listformatter_basic.phpt create mode 100644 ext/intl/tests/listformatter/listformatter_error.phpt create mode 100644 ext/intl/tests/listformatter/listformatter_with_paramaters.phpt diff --git a/ext/intl/config.m4 b/ext/intl/config.m4 index 6a64f0f718100..0feb0509b7eda 100644 --- a/ext/intl/config.m4 +++ b/ext/intl/config.m4 @@ -39,6 +39,8 @@ if test "$PHP_INTL" != "no"; then locale/locale_class.c locale/locale_methods.c locale/locale.c + listformatter/listformatter_class.c + listformatter/listformatter_data.c msgformat/msgformat_attr.c msgformat/msgformat_class.c msgformat/msgformat_data.c @@ -119,6 +121,7 @@ if test "$PHP_INTL" != "no"; then $ext_builddir/grapheme $ext_builddir/idn $ext_builddir/locale + $ext_builddir/listformatter $ext_builddir/msgformat $ext_builddir/normalizer $ext_builddir/resourcebundle diff --git a/ext/intl/listformatter/listformatter.stub.php b/ext/intl/listformatter/listformatter.stub.php new file mode 100644 index 0000000000000..e5b7815307c03 --- /dev/null +++ b/ext/intl/listformatter/listformatter.stub.php @@ -0,0 +1,49 @@ + | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_intl.h" +#include +#include "listformatter_arginfo.h" +#include "listformatter_class.h" +#include "intl_convert.h" + + +static zend_object_handlers listformatter_handlers; + +/* {{{ listformatter_free_obj */ +static void listformatter_free_obj(zend_object *object) +{ + ListFormatter_object *obj = php_intl_listformatter_fetch_object(object); + listformatter_data_free(&obj->lf_data); + zend_object_std_dtor(&obj->zo); +} +/* }}} */ + +/* {{{ listformatter_create_object */ +static zend_object *listformatter_create_object(zend_class_entry *class_type) +{ + ListFormatter_object *obj; + obj = zend_object_alloc(sizeof(ListFormatter_object), class_type); + listformatter_data_init(&obj->lf_data); + zend_object_std_init(&obj->zo, class_type); + object_properties_init(&obj->zo, class_type); + obj->zo.handlers = &listformatter_handlers; + return &obj->zo; +} +/* }}} */ + +/* {{{ listformatter_create_object */ +PHP_METHOD(IntlListFormatter, __construct) +{ + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + zend_string *locale; + zend_long type = ULISTFMT_TYPE_AND; + zend_long width = ULISTFMT_WIDTH_WIDE; + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STR(locale) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + Z_PARAM_LONG(width) + ZEND_PARSE_PARAMETERS_END(); + + if (strlen(uloc_getISO3Language(ZSTR_VAL(locale))) == 0) { + zend_argument_value_error(1, "\"%s\" is invalid", ZSTR_VAL(locale)); + RETURN_THROWS(); + } + + UErrorCode status = U_ZERO_ERROR; + if (U_ICU_VERSION_MAJOR_NUM >= 67) { + + LISTFORMATTER_OBJECT(obj) = ulistfmt_openForType(ZSTR_VAL(locale), type, width, &status); + } else { + if (type != ULISTFMT_TYPE_AND) { + zend_argument_value_error(2, "ICU 66 and below only support IntlListFormatter::TYPE_AND"); + RETURN_THROWS(); + } + + if (width != ULISTFMT_WIDTH_WIDE) { + zend_argument_value_error(3, "ICU 66 and below only support IntlListFormatter::WIDTH_WIDE"); + RETURN_THROWS(); + } + + LISTFORMATTER_OBJECT(obj) = ulistfmt_open(ZSTR_VAL(locale), &status); + } + if (U_FAILURE(status)) { + intl_error_set(NULL, status, "Constructor failed", 0); + zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0); + RETURN_THROWS(); + } +} +/* }}} */ + +/* {{{ listformatter_format */ +PHP_METHOD(IntlListFormatter, format) +{ + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + zval *strings; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(strings) + ZEND_PARSE_PARAMETERS_END(); + + if (!LISTFORMATTER_OBJECT(obj)) { + intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "ListFormatter not properly constructed", 0); + RETURN_FALSE; + } + + HashTable *ht = Z_ARRVAL_P(strings); + uint32_t count = zend_array_count(ht); + if (count == 0) { + RETURN_EMPTY_STRING(); + } + + const UChar **items = (const UChar **)safe_emalloc(count, sizeof(const UChar *), 0); + int32_t *itemLengths = (int32_t *)safe_emalloc(count, sizeof(int32_t), 0); + uint32_t i = 0; + zval *val; + + ZEND_HASH_FOREACH_VAL(ht, val) { + zend_string *str_val; + + str_val = zval_get_string(val); + + // Convert PHP string to UTF-16 + UChar *ustr = NULL; + int32_t ustr_len = 0; + UErrorCode status = U_ZERO_ERROR; + + intl_convert_utf8_to_utf16(&ustr, &ustr_len, ZSTR_VAL(str_val), ZSTR_LEN(str_val), &status); + if (U_FAILURE(status)) { + efree(items); + efree(itemLengths); + zend_string_release(str_val); + intl_error_set(NULL, status, "Failed to convert string to UTF-16", 0); + RETURN_FALSE; + } + + items[i] = ustr; + itemLengths[i] = ustr_len; + zend_string_release(str_val); + i++; + } ZEND_HASH_FOREACH_END(); + + UErrorCode status = U_ZERO_ERROR; + int32_t resultLength; + UChar *result = NULL; + + resultLength = ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, NULL, 0, &status); + + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { + intl_error_set(NULL, status, "Failed to format list", 0); + RETURN_FALSE; + } + + // Allocate buffer and try again + status = U_ZERO_ERROR; + result = (UChar *)emalloc((resultLength + 1) * sizeof(UChar)); + ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, result, resultLength, &status); + + // Clean up input strings + for (i = 0; i < count; i++) { + efree((void *)items[i]); + } + efree(items); + efree(itemLengths); + + if (U_FAILURE(status)) { + if (result) { + efree(result); + } + intl_error_set(NULL, status, "Failed to format list", 0); + RETURN_FALSE; + } + + // Convert result back to UTF-8 + zend_string *ret = intl_convert_utf16_to_utf8(result, resultLength, &status); + efree(result); + + if (!ret) { + intl_error_set(NULL, status, "Failed to convert result to UTF-8", 0); + RETURN_FALSE; + } + + RETURN_STR(ret); +} +/* }}} */ + +/* {{{ listformatter_getErrorCode */ +PHP_METHOD(IntlListFormatter, getErrorCode) +{ + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + + UErrorCode status = intl_error_get_code(LISTFORMATTER_ERROR_P(obj)); + + RETURN_LONG(status); +} +/* }}} */ + +/* {{{ listformatter_getErrorMessage */ +PHP_METHOD(IntlListFormatter, getErrorMessage) +{ + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + + zend_string *message = intl_error_get_message(LISTFORMATTER_ERROR_P(obj)); + RETURN_STR(message); +} +/* }}} */ + +/* {{{ listformatter_register_class */ +void listformatter_register_class(void) +{ + zend_class_entry *class_entry = register_class_IntlListFormatter(); + class_entry->create_object = listformatter_create_object; + + memcpy(&listformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + listformatter_handlers.offset = XtOffsetOf(ListFormatter_object, zo); + listformatter_handlers.free_obj = listformatter_free_obj; +} +/* }}} */ diff --git a/ext/intl/listformatter/listformatter_class.h b/ext/intl/listformatter/listformatter_class.h new file mode 100644 index 0000000000000..b6183c9f9eff9 --- /dev/null +++ b/ext/intl/listformatter/listformatter_class.h @@ -0,0 +1,55 @@ +/* + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Bogdan Ungureanu | + +----------------------------------------------------------------------+ +*/ + +#ifndef LISTFORMATTER_CLASS_H +#define LISTFORMATTER_CLASS_H + +#include + +#include "intl_common.h" +#include "intl_error.h" +#include "intl_data.h" +#include "listformatter_data.h" + +typedef struct { + listformatter_data lf_data; + zend_object zo; +} ListFormatter_object; + +static inline ListFormatter_object *php_intl_listformatter_fetch_object(zend_object *obj) { + return (ListFormatter_object *)((char*)(obj) - XtOffsetOf(ListFormatter_object, zo)); +} +#define Z_INTL_LISTFORMATTER_P(zv) php_intl_listformatter_fetch_object(Z_OBJ_P(zv)) + +#define LISTFORMATTER_ERROR(lfo) (lfo)->lf_data.error +#define LISTFORMATTER_ERROR_P(lfo) &(LISTFORMATTER_ERROR(lfo)) + +#define LISTFORMATTER_ERROR_CODE(lfo) INTL_ERROR_CODE(LISTFORMATTER_ERROR(lfo)) +#define LISTFORMATTER_ERROR_CODE_P(lfo) &(INTL_ERROR_CODE(LISTFORMATTER_ERROR(lfo))) + +#define LISTFORMATTER_METHOD_INIT_VARS INTL_METHOD_INIT_VARS(ListFormatter, lfo) +#define LISTFORMATTER_OBJECT(lfo) (lfo)->lf_data.ulistfmt +#define LISTFORMATTER_METHOD_FETCH_OBJECT_NO_CHECK INTL_METHOD_FETCH_OBJECT(INTL_LISTFORMATTER, lfo) +#define LISTFORMATTER_METHOD_FETCH_OBJECT \ + LISTFORMATTER_METHOD_FETCH_OBJECT_NO_CHECK; \ + if (LISTFORMATTER_OBJECT(lfo) == NULL) \ + { \ + zend_throw_error(NULL, "Found unconstructed ListFormatter"); \ + RETURN_THROWS(); \ + } + +void listformatter_register_class( void ); +extern zend_class_entry *ListFormatter_ce_ptr; + +#endif // LISTFORMATTER_CLASS_H \ No newline at end of file diff --git a/ext/intl/listformatter/listformatter_data.c b/ext/intl/listformatter/listformatter_data.c new file mode 100644 index 0000000000000..0cf1f08389db3 --- /dev/null +++ b/ext/intl/listformatter/listformatter_data.c @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Bogdan Ungureanu | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "listformatter_data.h" + +/* {{{ void listformatter_data_init( listformatter_data* lf_data ) + * Initialize internals of listformatter_data. + */ +void listformatter_data_init( listformatter_data* lf_data ) +{ + if( !lf_data ) + return; + + lf_data->ulistfmt = NULL; + intl_error_reset( &lf_data->error ); +} +/* }}} */ + +/* {{{ void listformatter_data_free( listformatter_data* lf_data ) + * Clean up mem allocated by internals of listformatter_data + */ +void listformatter_data_free( listformatter_data* lf_data ) +{ + if( !lf_data ) + return; + + if( lf_data->ulistfmt ) + ulistfmt_close( lf_data->ulistfmt ); + + lf_data->ulistfmt = NULL; + intl_error_reset( &lf_data->error ); +} +/* }}} */ diff --git a/ext/intl/listformatter/listformatter_data.h b/ext/intl/listformatter/listformatter_data.h new file mode 100644 index 0000000000000..ae558b79f3306 --- /dev/null +++ b/ext/intl/listformatter/listformatter_data.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Bogdan Ungureanu | + +----------------------------------------------------------------------+ +*/ + +#ifndef LISTFORMATTER_DATA_H +#define LISTFORMATTER_DATA_H + +#include + +#include + +#include "intl_error.h" + +typedef struct { + // error handling + intl_error error; + + // formatter handling + UListFormatter* ulistfmt; +} listformatter_data; + +void listformatter_data_init( listformatter_data* lf_data ); +void listformatter_data_free( listformatter_data* lf_data ); + +#endif // LISTFORMATTER_DATA_H \ No newline at end of file diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index cba18f5ae07b1..68fd2dedfba85 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -41,6 +41,8 @@ #include "locale/locale.h" #include "locale/locale_class.h" +#include "listformatter/listformatter_class.h" + #include "dateformat/dateformat.h" #include "dateformat/dateformat_class.h" #include "dateformat/dateformat_data.h" @@ -156,6 +158,9 @@ PHP_MINIT_FUNCTION( intl ) /* Register 'NumberFormatter' PHP class */ formatter_register_class( ); + /* Register 'ListFormatter' PHP class */ + listformatter_register_class( ); + /* Register 'Normalizer' PHP class */ normalizer_register_Normalizer_class( ); diff --git a/ext/intl/tests/listformatter/listformatter_basic.phpt b/ext/intl/tests/listformatter/listformatter_basic.phpt new file mode 100644 index 0000000000000..d9130efe86d15 --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_basic.phpt @@ -0,0 +1,32 @@ +--TEST-- +IntlListFormatter: Basic functionality +--EXTENSIONS-- +intl +--FILE-- +format([1,2,3]); +echo PHP_EOL; +$formatter = new IntlListFormatter('EN_US'); +echo $formatter->format([1,2,3]); + +echo PHP_EOL . 'FR' . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR'); +echo $formatter->format([1,2,3]); +echo PHP_EOL; + +--EXPECT-- +EN_US +1, 2, and 3 +1, 2, and 3 +FR +1, 2 et 3 +1, 2 et 3 \ No newline at end of file diff --git a/ext/intl/tests/listformatter/listformatter_error.phpt b/ext/intl/tests/listformatter/listformatter_error.phpt new file mode 100644 index 0000000000000..36c943bd61dca --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_error.phpt @@ -0,0 +1,36 @@ +--TEST-- +IntlListFormatter: error messages +--EXTENSIONS-- +intl +--FILE-- +getMessage(); +} + +echo PHP_EOL; + +try { + $formatter = new IntlListFormatter('ro', IntlListFormatter::TYPE_AND, 23); +} catch (IntlException $exception) { + echo $exception->getMessage() .PHP_EOL; + echo intl_get_error_code(); +} + +$formatter = new IntlListFormatter('ro', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE); + +echo PHP_EOL; +try { + echo $formatter->format([new stdClass()]); +} catch(Error $error) { + echo $error->getMessage(); +} + +--EXPECT-- +IntlListFormatter::__construct(): Argument #1 ($locale) "f" is invalid +Constructor failed +1 +Object of class stdClass could not be converted to string \ No newline at end of file diff --git a/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt b/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt new file mode 100644 index 0000000000000..a20267f64498e --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt @@ -0,0 +1,169 @@ +--TEST-- +IntlListFormatter: Test AND, OR and Width parameters +--EXTENSIONS-- +intl +--FILE-- +format([1,2,3]); +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL . 'GB' . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL . 'FR' . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]); + +echo PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); +--EXPECT-- +EN_US +1, 2, and 3 +1, 2, & 3 +1, 2, 3 +1, 2, or 3 +1, 2, or 3 +1, 2, or 3 +1, 2, 3 +1, 2, 3 +1 2 3 +GB +1, 2 and 3 +1, 2 and 3 +1, 2, 3 +1, 2 or 3 +1, 2 or 3 +1, 2 or 3 +1, 2, 3 +1, 2, 3 +1 2 3 +FR +1, 2 et 3 +1, 2 et 3 +1, 2, 3 +1, 2 ou 3 +1, 2 ou 3 +1, 2 ou 3 +1, 2 et 3 +1, 2 et 3 +1 2 3 \ No newline at end of file From 9236166f72d2d924d4ea46ab39238641b2f69f4c Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Thu, 8 May 2025 01:25:12 +0300 Subject: [PATCH 2/4] Skip test if ICU is older than 67 --- ext/intl/tests/listformatter/listformatter_with_paramaters.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt b/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt index a20267f64498e..792baa4095cba 100644 --- a/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt +++ b/ext/intl/tests/listformatter/listformatter_with_paramaters.phpt @@ -2,6 +2,8 @@ IntlListFormatter: Test AND, OR and Width parameters --EXTENSIONS-- intl +--SKIPIF-- += 0) die('skip for ICU < 67.0'); ?> --FILE-- Date: Fri, 9 May 2025 00:55:09 +0300 Subject: [PATCH 3/4] Call ZEND_PARSE_PARAMETERS_NONE in getErrorCode and getErrorMessage --- ext/intl/listformatter/listformatter_class.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/intl/listformatter/listformatter_class.c b/ext/intl/listformatter/listformatter_class.c index cf0529399e908..bd153388e043c 100644 --- a/ext/intl/listformatter/listformatter_class.c +++ b/ext/intl/listformatter/listformatter_class.c @@ -186,6 +186,8 @@ PHP_METHOD(IntlListFormatter, format) /* {{{ listformatter_getErrorCode */ PHP_METHOD(IntlListFormatter, getErrorCode) { + ZEND_PARSE_PARAMETERS_NONE(); + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); UErrorCode status = intl_error_get_code(LISTFORMATTER_ERROR_P(obj)); @@ -197,6 +199,8 @@ PHP_METHOD(IntlListFormatter, getErrorCode) /* {{{ listformatter_getErrorMessage */ PHP_METHOD(IntlListFormatter, getErrorMessage) { + ZEND_PARSE_PARAMETERS_NONE(); + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); zend_string *message = intl_error_get_message(LISTFORMATTER_ERROR_P(obj)); From a2dd8e9c1c1dbdb71fe19a89611a6e6afcbcad9e Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Fri, 9 May 2025 01:04:10 +0300 Subject: [PATCH 4/4] Make IntlListFormatter final, not serializable and use strict properties --- ext/intl/listformatter/listformatter.stub.php | 5 +++-- ext/intl/listformatter/listformatter_arginfo.h | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ext/intl/listformatter/listformatter.stub.php b/ext/intl/listformatter/listformatter.stub.php index e5b7815307c03..10a7491156bf5 100644 --- a/ext/intl/listformatter/listformatter.stub.php +++ b/ext/intl/listformatter/listformatter.stub.php @@ -2,8 +2,9 @@ /** @generate-class-entries */ - -class IntlListFormatter { +/** @not-serializable */ +/** @strict-properties */ +final class IntlListFormatter { /** @cvalue ULISTFMT_TYPE_OR */ public const int TYPE_OR = UNKNOWN; diff --git a/ext/intl/listformatter/listformatter_arginfo.h b/ext/intl/listformatter/listformatter_arginfo.h index aaef19b113a73..a66951076b4b8 100644 --- a/ext/intl/listformatter/listformatter_arginfo.h +++ b/ext/intl/listformatter/listformatter_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f76592ef6796059904d6b23b194d789d506823d2 */ + * Stub hash: e4296eb4864ae04804515cf597878e3efb4660ca */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_IntlListFormatter___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) @@ -35,7 +35,7 @@ static zend_class_entry *register_class_IntlListFormatter(void) zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "IntlListFormatter", class_IntlListFormatter_methods); - class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); zval const_TYPE_OR_value; ZVAL_LONG(&const_TYPE_OR_value, ULISTFMT_TYPE_OR);