From a0a407b610410ff5fc0bb8d3c81493305d31498e Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Fri, 25 Jul 2025 01:43:26 +0300 Subject: [PATCH 1/7] Intl: Add IntlNumberRangeFormatter --- ext/intl/config.m4 | 2 + ext/intl/config.w32 | 3 + ext/intl/php_intl.c | 4 + .../rangeformatter/rangeformatter.stub.php | 40 ++++ .../rangeformatter/rangeformatter_arginfo.h | 86 +++++++++ .../rangeformatter/rangeformatter_class.cpp | 180 ++++++++++++++++++ .../rangeformatter/rangeformatter_class.h | 54 ++++++ ext/intl/tests/rangeformatter/basic.phpt | 20 ++ 8 files changed, 389 insertions(+) create mode 100644 ext/intl/rangeformatter/rangeformatter.stub.php create mode 100644 ext/intl/rangeformatter/rangeformatter_arginfo.h create mode 100644 ext/intl/rangeformatter/rangeformatter_class.cpp create mode 100644 ext/intl/rangeformatter/rangeformatter_class.h create mode 100644 ext/intl/tests/rangeformatter/basic.phpt diff --git a/ext/intl/config.m4 b/ext/intl/config.m4 index d60068e5ad480..d89edf4e96e20 100644 --- a/ext/intl/config.m4 +++ b/ext/intl/config.m4 @@ -68,6 +68,7 @@ if test "$PHP_INTL" != "no"; then dateformat/datepatterngenerator_class.cpp \ dateformat/datepatterngenerator_methods.cpp \ msgformat/msgformat_helpers.cpp \ + rangeformatter/rangeformatter_class.cpp \ timezone/timezone_class.cpp \ timezone/timezone_methods.cpp \ calendar/calendar_class.cpp \ @@ -123,6 +124,7 @@ if test "$PHP_INTL" != "no"; then $ext_builddir/listformatter $ext_builddir/msgformat $ext_builddir/normalizer + $ext_builddir/rangeformatter $ext_builddir/resourcebundle $ext_builddir/spoofchecker $ext_builddir/timezone diff --git a/ext/intl/config.w32 b/ext/intl/config.w32 index dcec448c4621f..c4475a548f285 100644 --- a/ext/intl/config.w32 +++ b/ext/intl/config.w32 @@ -63,6 +63,9 @@ if (PHP_INTL != "no") { normalizer_class.c \ normalizer_normalize.c \ ", "intl"); + ADD_SOURCES(configure_module_dirname + "/rangeformatter", "\ + rangeformatter_class.cpp \ + ", "intl"); ADD_SOURCES(configure_module_dirname + "/dateformat", "\ dateformat.c \ dateformat_class.c \ diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index 68fd2dedfba85..7a92390990d7b 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -42,6 +42,7 @@ #include "locale/locale_class.h" #include "listformatter/listformatter_class.h" +#include "rangeformatter/rangeformatter_class.h" #include "dateformat/dateformat.h" #include "dateformat/dateformat_class.h" @@ -161,6 +162,9 @@ PHP_MINIT_FUNCTION( intl ) /* Register 'ListFormatter' PHP class */ listformatter_register_class( ); + /* Register 'NumberRangeFormatter' PHP class */ + rangeformatter_register_class( ); + /* Register 'Normalizer' PHP class */ normalizer_register_Normalizer_class( ); diff --git a/ext/intl/rangeformatter/rangeformatter.stub.php b/ext/intl/rangeformatter/rangeformatter.stub.php new file mode 100644 index 0000000000000..2cd947ecab07d --- /dev/null +++ b/ext/intl/rangeformatter/rangeformatter.stub.php @@ -0,0 +1,40 @@ + | + +----------------------------------------------------------------------+ +*/ + +#include +#include +#include +#include +#include "../intl_convertcpp.h" + +extern "C" { + #include "php.h" + #include "../php_intl.h" + #include "../intl_data.h" + #include "rangeformatter_arginfo.h" + #include "rangeformatter_class.h" +#include "intl_convert.h" +} + +using icu::number::NumberRangeFormatter; +using icu::number::NumberFormatter; +using icu::number::UnlocalizedNumberFormatter; +using icu::number::LocalizedNumberRangeFormatter; +using icu::UnicodeString; +using icu::MeasureUnit; + +static zend_object_handlers rangeformatter_handlers; +zend_class_entry *class_entry_IntlNumberRangeFormatter; + +zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce) +{ + IntlNumberRangeFormatter_object* intern; + + intern = (IntlNumberRangeFormatter_object*)zend_object_alloc(sizeof(IntlNumberRangeFormatter_object), ce); + zend_object_std_init(&intern->zo, ce); + object_properties_init(&intern->zo, ce); + + return &intern->zo; +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct) +{ + +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) +{ + char* skeleton; + char* locale; + size_t locale_len; + size_t skeleton_len; + zend_long collapse; + zend_long identityFallback; + + ZEND_PARSE_PARAMETERS_START(4,4) + Z_PARAM_STRING(skeleton, skeleton_len) + Z_PARAM_STRING(locale, locale_len) + Z_PARAM_LONG(collapse) + Z_PARAM_LONG(identityFallback) + ZEND_PARSE_PARAMETERS_END(); + + if (locale_len == 0) { + locale = (char *)intl_locale_get_default(); + } + + if (skeleton_len == 0) { + zend_argument_value_error(1, "Skeleton string cannot be empty"); + RETURN_THROWS(); + } + + if (locale_len > INTL_MAX_LOCALE_LEN) { + zend_argument_value_error(2, "Locale string too long, should be no longer than %d characters", INTL_MAX_LOCALE_LEN); + RETURN_THROWS(); + } + + if (strlen(uloc_getISO3Language(locale)) == 0) { + zend_argument_value_error(2, "\"%s\" is invalid", locale); + RETURN_THROWS(); + } + + if (collapse != UNUM_RANGE_COLLAPSE_AUTO && collapse != UNUM_RANGE_COLLAPSE_NONE && collapse != UNUM_RANGE_COLLAPSE_UNIT && collapse != UNUM_RANGE_COLLAPSE_ALL) { + zend_argument_value_error(3, "Invalid collapse value"); + RETURN_THROWS(); + } + + if (identityFallback != UNUM_IDENTITY_FALLBACK_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY && identityFallback != UNUM_IDENTITY_FALLBACK_RANGE) { + zend_argument_value_error(4, "Invalid identity fallback value"); + RETURN_THROWS(); + } + + UErrorCode error = U_ZERO_ERROR; + + UnicodeString skeleton_ustr(skeleton, skeleton_len); + + UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton_ustr, error); + + LocalizedNumberRangeFormatter* nrf = new LocalizedNumberRangeFormatter( + NumberRangeFormatter::with() + .locale(locale) + .numberFormatterBoth(nf) + .collapse(static_cast(collapse)) + .identityFallback(static_cast(identityFallback)) + ); + + zend_object* obj = IntlNumberRangeFormatter_object_create(class_entry_IntlNumberRangeFormatter); + obj->handlers = &rangeformatter_handlers; + + RANGEFORMATTER_OBJECT(php_intl_numberrangeformatter_fetch_object(obj)) = nrf; + + RETURN_OBJ(obj); + +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) +{ + double start; + double end; + + IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS); + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_DOUBLE(start) + Z_PARAM_DOUBLE(end) + ZEND_PARSE_PARAMETERS_END(); + + UErrorCode error = U_ZERO_ERROR; + + UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start, end, error).toString(error); + + if (U_FAILURE(error)) { + intl_error_set(NULL, error, "Failed to format number range", 0); + + return; + } + + zend_string *ret = intl_charFromString(result, &error); + + if (!ret) { + intl_error_set(NULL, error, "Failed to convert result to UTF-8", 0); + // RETVAL_FALSE; + return; + } + + RETVAL_NEW_STR(ret); +} + +void IntlNumberRangeFormatter_object_free(zend_object *object) +{ + IntlNumberRangeFormatter_object* nfo = php_intl_numberrangeformatter_fetch_object(object); + + if (nfo->nrf_data.unumrf) { + delete nfo->nrf_data.unumrf; + nfo->nrf_data.unumrf = nullptr; + } + + intl_error_reset(&nfo->nrf_data.error); + + zend_object_std_dtor(&nfo->zo); +} + +void rangeformatter_register_class(void) +{ + class_entry_IntlNumberRangeFormatter = register_class_IntlNumberRangeFormatter(); + class_entry_IntlNumberRangeFormatter->create_object = IntlNumberRangeFormatter_object_create; + + memcpy(&rangeformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + rangeformatter_handlers.offset = XtOffsetOf(IntlNumberRangeFormatter_object, zo); + rangeformatter_handlers.free_obj = IntlNumberRangeFormatter_object_free; + rangeformatter_handlers.clone_obj = NULL; +} diff --git a/ext/intl/rangeformatter/rangeformatter_class.h b/ext/intl/rangeformatter/rangeformatter_class.h new file mode 100644 index 0000000000000..cf8a08aefb4eb --- /dev/null +++ b/ext/intl/rangeformatter/rangeformatter_class.h @@ -0,0 +1,54 @@ +/* + +----------------------------------------------------------------------+ + | 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 RANGEFORMATTER_CLASS_H +#define RANGEFORMATTER_CLASS_H + +#include + +#ifdef __cplusplus +using icu::number::LocalizedNumberRangeFormatter; +#else +typedef void LocalizedNumberRangeFormatter; +#endif + +typedef struct { + // error handling + intl_error error; + + // formatter handling + LocalizedNumberRangeFormatter* unumrf; +} rangeformatter_data; + +typedef struct { + rangeformatter_data nrf_data; + zend_object zo; +} IntlNumberRangeFormatter_object; + +static inline IntlNumberRangeFormatter_object *php_intl_numberrangeformatter_fetch_object(zend_object *obj) { + return (IntlNumberRangeFormatter_object *)((char*)(obj) - XtOffsetOf(IntlNumberRangeFormatter_object, zo)); +} + +#define Z_INTL_RANGEFORMATTER_P(zv) php_intl_numberrangeformatter_fetch_object(Z_OBJ_P(zv)) + +#define RANGEFORMATTER_ERROR(nfo) (nfo)->nrf_data.error +#define RANGEFORMATTER_ERROR_P(nfo) &(RANGEFORMATTER_ERROR(nfo)) + +#define RANGEFORMATTER_OBJECT(nfo) (nfo)->nrf_data.unumrf + + +void rangeformatter_register_class(void); + + +#endif \ No newline at end of file diff --git a/ext/intl/tests/rangeformatter/basic.phpt b/ext/intl/tests/rangeformatter/basic.phpt new file mode 100644 index 0000000000000..42f29ba12c708 --- /dev/null +++ b/ext/intl/tests/rangeformatter/basic.phpt @@ -0,0 +1,20 @@ +--TEST-- +Basic test for IntlNumberRangeFormatter +--EXTENSIONS-- +intl +--FILE-- +format(1.1, 2.2)); +var_dump($nrf->format(100, 200)); +var_dump($nrf->format(-5, 5)); +?> +--EXPECT-- +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" From 0e083db7fe800e7e1fbffa3ce263213b388e70ba Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Sun, 3 Aug 2025 01:12:48 +0300 Subject: [PATCH 2/7] Add error methods, update error messages and tests --- .../rangeformatter/rangeformatter.stub.php | 23 +- .../rangeformatter/rangeformatter_arginfo.h | 64 ++- .../rangeformatter/rangeformatter_class.cpp | 77 +++- ext/intl/tests/rangeformatter/basic.phpt | 383 +++++++++++++++++- .../rangeformatter/rangeformatter_clone.phpt | 26 ++ .../rangeformatter/rangeformatter_errors.phpt | 63 +++ .../rangeformatter_icu63_compatibility.phpt | 24 ++ 7 files changed, 636 insertions(+), 24 deletions(-) create mode 100644 ext/intl/tests/rangeformatter/rangeformatter_clone.phpt create mode 100644 ext/intl/tests/rangeformatter/rangeformatter_errors.phpt create mode 100644 ext/intl/tests/rangeformatter/rangeformatter_icu63_compatibility.phpt diff --git a/ext/intl/rangeformatter/rangeformatter.stub.php b/ext/intl/rangeformatter/rangeformatter.stub.php index 2cd947ecab07d..eae40b5a56c23 100644 --- a/ext/intl/rangeformatter/rangeformatter.stub.php +++ b/ext/intl/rangeformatter/rangeformatter.stub.php @@ -7,7 +7,7 @@ * @strict-properties */ final class IntlNumberRangeFormatter { - +#if U_ICU_VERSION_MAJOR_NUM >= 63 /** @cvalue UNUM_RANGE_COLLAPSE_AUTO */ public const int COLLAPSE_AUTO = UNKNOWN; @@ -31,10 +31,31 @@ final class IntlNumberRangeFormatter { /** @cvalue UNUM_IDENTITY_FALLBACK_RANGE */ public const int IDENTITY_FALLBACK_RANGE = UNKNOWN; +#else + public const int COLLAPSE_AUTO = 0; + + public const int COLLAPSE_NONE = 1; + + public const int COLLAPSE_UNIT = 2; + + public const int COLLAPSE_ALL = 3; + + public const int IDENTITY_FALLBACK_SINGLE_VALUE = 0; + + public const int IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE = 1; + + public const int IDENTITY_FALLBACK_APPROXIMATELY = 2; + + public const int IDENTITY_FALLBACK_RANGE = 3; +#endif private function __construct() {} public static function createFromSkeleton(string $skeleton, string $locale, int $collapse, int $identityFallback): IntlNumberRangeFormatter {} public function format(float|int $start, float|int $end): string {} + + public function getErrorCode(): int {} + + public function getErrorMessage(): string {} } diff --git a/ext/intl/rangeformatter/rangeformatter_arginfo.h b/ext/intl/rangeformatter/rangeformatter_arginfo.h index 9b3234bd8d31c..00e923702be11 100644 --- a/ext/intl/rangeformatter/rangeformatter_arginfo.h +++ b/ext/intl/rangeformatter/rangeformatter_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c70c84f9487bfa60f66e22bde4c2c6e9dcc02d64 */ + * Stub hash: 7029642524e32984e893e1e050a5e0bbf275c416 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_IntlNumberRangeFormatter___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -16,14 +16,24 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_IntlNumberRangeFormatter_f ZEND_ARG_TYPE_MASK(0, end, MAY_BE_DOUBLE|MAY_BE_LONG, NULL) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_IntlNumberRangeFormatter_getErrorCode, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_IntlNumberRangeFormatter_getErrorMessage, 0, 0, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_METHOD(IntlNumberRangeFormatter, __construct); ZEND_METHOD(IntlNumberRangeFormatter, createFromSkeleton); ZEND_METHOD(IntlNumberRangeFormatter, format); +ZEND_METHOD(IntlNumberRangeFormatter, getErrorCode); +ZEND_METHOD(IntlNumberRangeFormatter, getErrorMessage); static const zend_function_entry class_IntlNumberRangeFormatter_methods[] = { ZEND_ME(IntlNumberRangeFormatter, __construct, arginfo_class_IntlNumberRangeFormatter___construct, ZEND_ACC_PRIVATE) ZEND_ME(IntlNumberRangeFormatter, createFromSkeleton, arginfo_class_IntlNumberRangeFormatter_createFromSkeleton, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(IntlNumberRangeFormatter, format, arginfo_class_IntlNumberRangeFormatter_format, ZEND_ACC_PUBLIC) + ZEND_ME(IntlNumberRangeFormatter, getErrorCode, arginfo_class_IntlNumberRangeFormatter_getErrorCode, ZEND_ACC_PUBLIC) + ZEND_ME(IntlNumberRangeFormatter, getErrorMessage, arginfo_class_IntlNumberRangeFormatter_getErrorMessage, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -33,6 +43,7 @@ static zend_class_entry *register_class_IntlNumberRangeFormatter(void) INIT_CLASS_ENTRY(ce, "IntlNumberRangeFormatter", class_IntlNumberRangeFormatter_methods); class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); +#if U_ICU_VERSION_MAJOR_NUM >= 63 zval const_COLLAPSE_AUTO_value; ZVAL_LONG(&const_COLLAPSE_AUTO_value, UNUM_RANGE_COLLAPSE_AUTO); @@ -81,6 +92,57 @@ static zend_class_entry *register_class_IntlNumberRangeFormatter(void) zend_string *const_IDENTITY_FALLBACK_RANGE_name = zend_string_init_interned("IDENTITY_FALLBACK_RANGE", sizeof("IDENTITY_FALLBACK_RANGE") - 1, 1); zend_declare_typed_class_constant(class_entry, const_IDENTITY_FALLBACK_RANGE_name, &const_IDENTITY_FALLBACK_RANGE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_IDENTITY_FALLBACK_RANGE_name); +#endif +#if !(U_ICU_VERSION_MAJOR_NUM >= 63) + + zval const_COLLAPSE_AUTO_value; + ZVAL_LONG(&const_COLLAPSE_AUTO_value, 0); + zend_string *const_COLLAPSE_AUTO_name = zend_string_init_interned("COLLAPSE_AUTO", sizeof("COLLAPSE_AUTO") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_COLLAPSE_AUTO_name, &const_COLLAPSE_AUTO_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_COLLAPSE_AUTO_name); + + zval const_COLLAPSE_NONE_value; + ZVAL_LONG(&const_COLLAPSE_NONE_value, 1); + zend_string *const_COLLAPSE_NONE_name = zend_string_init_interned("COLLAPSE_NONE", sizeof("COLLAPSE_NONE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_COLLAPSE_NONE_name, &const_COLLAPSE_NONE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_COLLAPSE_NONE_name); + + zval const_COLLAPSE_UNIT_value; + ZVAL_LONG(&const_COLLAPSE_UNIT_value, 2); + zend_string *const_COLLAPSE_UNIT_name = zend_string_init_interned("COLLAPSE_UNIT", sizeof("COLLAPSE_UNIT") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_COLLAPSE_UNIT_name, &const_COLLAPSE_UNIT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_COLLAPSE_UNIT_name); + + zval const_COLLAPSE_ALL_value; + ZVAL_LONG(&const_COLLAPSE_ALL_value, 3); + zend_string *const_COLLAPSE_ALL_name = zend_string_init_interned("COLLAPSE_ALL", sizeof("COLLAPSE_ALL") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_COLLAPSE_ALL_name, &const_COLLAPSE_ALL_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_COLLAPSE_ALL_name); + + zval const_IDENTITY_FALLBACK_SINGLE_VALUE_value; + ZVAL_LONG(&const_IDENTITY_FALLBACK_SINGLE_VALUE_value, 0); + zend_string *const_IDENTITY_FALLBACK_SINGLE_VALUE_name = zend_string_init_interned("IDENTITY_FALLBACK_SINGLE_VALUE", sizeof("IDENTITY_FALLBACK_SINGLE_VALUE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_IDENTITY_FALLBACK_SINGLE_VALUE_name, &const_IDENTITY_FALLBACK_SINGLE_VALUE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_IDENTITY_FALLBACK_SINGLE_VALUE_name); + + zval const_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE_value; + ZVAL_LONG(&const_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE_value, 1); + zend_string *const_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE_name = zend_string_init_interned("IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE", sizeof("IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE_name, &const_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE_name); + + zval const_IDENTITY_FALLBACK_APPROXIMATELY_value; + ZVAL_LONG(&const_IDENTITY_FALLBACK_APPROXIMATELY_value, 2); + zend_string *const_IDENTITY_FALLBACK_APPROXIMATELY_name = zend_string_init_interned("IDENTITY_FALLBACK_APPROXIMATELY", sizeof("IDENTITY_FALLBACK_APPROXIMATELY") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_IDENTITY_FALLBACK_APPROXIMATELY_name, &const_IDENTITY_FALLBACK_APPROXIMATELY_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_IDENTITY_FALLBACK_APPROXIMATELY_name); + + zval const_IDENTITY_FALLBACK_RANGE_value; + ZVAL_LONG(&const_IDENTITY_FALLBACK_RANGE_value, 3); + zend_string *const_IDENTITY_FALLBACK_RANGE_name = zend_string_init_interned("IDENTITY_FALLBACK_RANGE", sizeof("IDENTITY_FALLBACK_RANGE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_IDENTITY_FALLBACK_RANGE_name, &const_IDENTITY_FALLBACK_RANGE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_IDENTITY_FALLBACK_RANGE_name); +#endif return class_entry; } diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp index 09368a51c0eac..eb44d1342aeb5 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.cpp +++ b/ext/intl/rangeformatter/rangeformatter_class.cpp @@ -20,6 +20,9 @@ extern "C" { #include "php.h" + #include "zend_API.h" + #include "../intl_common.h" + #include "../intl_error.h" #include "../php_intl.h" #include "../intl_data.h" #include "rangeformatter_arginfo.h" @@ -45,16 +48,28 @@ zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce) zend_object_std_init(&intern->zo, ce); object_properties_init(&intern->zo, ce); + // Initialize rangeformatter_data structure + intl_error_init(&intern->nrf_data.error); + intern->nrf_data.unumrf = nullptr; + + intern->zo.handlers = &rangeformatter_handlers; + return &intern->zo; } U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct) { - + ZEND_PARSE_PARAMETERS_NONE(); + zend_throw_error(NULL, "Cannot directly construct %s, use createFromSkeleton method instead", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); } U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) { + #if U_ICU_VERSION_MAJOR_NUM < 63 + zend_throw_error(NULL, "IntlNumberRangeFormatter is not available in ICU 62 and earlier"); + RETURN_THROWS(); + #endif + char* skeleton; char* locale; size_t locale_len; @@ -74,12 +89,12 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) } if (skeleton_len == 0) { - zend_argument_value_error(1, "Skeleton string cannot be empty"); + zend_argument_must_not_be_empty_error(1); RETURN_THROWS(); } if (locale_len > INTL_MAX_LOCALE_LEN) { - zend_argument_value_error(2, "Locale string too long, should be no longer than %d characters", INTL_MAX_LOCALE_LEN); + zend_argument_value_error(2, "must be no longer than %d characters", INTL_MAX_LOCALE_LEN); RETURN_THROWS(); } @@ -89,20 +104,26 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) } if (collapse != UNUM_RANGE_COLLAPSE_AUTO && collapse != UNUM_RANGE_COLLAPSE_NONE && collapse != UNUM_RANGE_COLLAPSE_UNIT && collapse != UNUM_RANGE_COLLAPSE_ALL) { - zend_argument_value_error(3, "Invalid collapse value"); + zend_argument_value_error(3, "must be one of IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::COLLAPSE_NONE, IntlNumberRangeFormatter::COLLAPSE_UNIT, or IntlNumberRangeFormatter::COLLAPSE_ALL"); RETURN_THROWS(); } if (identityFallback != UNUM_IDENTITY_FALLBACK_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY && identityFallback != UNUM_IDENTITY_FALLBACK_RANGE) { - zend_argument_value_error(4, "Invalid identity fallback value"); + zend_argument_value_error(4, "must be one of IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, or IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE"); RETURN_THROWS(); } - UErrorCode error = U_ZERO_ERROR; + UErrorCode status = U_ZERO_ERROR; UnicodeString skeleton_ustr(skeleton, skeleton_len); - UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton_ustr, error); + UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton_ustr, status); + + if (U_FAILURE(status)) { + intl_error_set(NULL, status, "Failed to create the number skeleton", 0); + zend_throw_exception(IntlException_ce_ptr, "Failed to create the number skeleton", 0); + RETURN_THROWS(); + } LocalizedNumberRangeFormatter* nrf = new LocalizedNumberRangeFormatter( NumberRangeFormatter::with() @@ -113,7 +134,6 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) ); zend_object* obj = IntlNumberRangeFormatter_object_create(class_entry_IntlNumberRangeFormatter); - obj->handlers = &rangeformatter_handlers; RANGEFORMATTER_OBJECT(php_intl_numberrangeformatter_fetch_object(obj)) = nrf; @@ -123,37 +143,58 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) { - double start; - double end; + zval *start; + zval *end; IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS); ZEND_PARSE_PARAMETERS_START(2, 2) - Z_PARAM_DOUBLE(start) - Z_PARAM_DOUBLE(end) + Z_PARAM_NUMBER(start) + Z_PARAM_NUMBER(end) ZEND_PARSE_PARAMETERS_END(); UErrorCode error = U_ZERO_ERROR; - UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start, end, error).toString(error); + icu::Formattable start_formattable(Z_TYPE_P(start) == IS_DOUBLE ? Z_DVAL_P(start) : Z_LVAL_P(start)); + icu::Formattable end_formattable(Z_TYPE_P(end) == IS_DOUBLE ? Z_DVAL_P(end) : Z_LVAL_P(end)); + + UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start_formattable, end_formattable, error).toString(error); if (U_FAILURE(error)) { intl_error_set(NULL, error, "Failed to format number range", 0); - - return; + zend_throw_exception(IntlException_ce_ptr, "Failed to format number range", 0); + RETURN_THROWS(); } zend_string *ret = intl_charFromString(result, &error); - if (!ret) { + if (U_FAILURE(error)) { intl_error_set(NULL, error, "Failed to convert result to UTF-8", 0); - // RETVAL_FALSE; - return; + zend_throw_exception(IntlException_ce_ptr, "Failed to convert result to UTF-8", 0); + RETURN_THROWS(); } RETVAL_NEW_STR(ret); } +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, getErrorCode) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS); + + RETURN_LONG(intl_error_get_code(&obj->nrf_data.error)); +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, getErrorMessage) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS); + + RETURN_STR(intl_error_get_message(&obj->nrf_data.error)); +} + void IntlNumberRangeFormatter_object_free(zend_object *object) { IntlNumberRangeFormatter_object* nfo = php_intl_numberrangeformatter_fetch_object(object); diff --git a/ext/intl/tests/rangeformatter/basic.phpt b/ext/intl/tests/rangeformatter/basic.phpt index 42f29ba12c708..70aae282614bc 100644 --- a/ext/intl/tests/rangeformatter/basic.phpt +++ b/ext/intl/tests/rangeformatter/basic.phpt @@ -2,19 +2,394 @@ Basic test for IntlNumberRangeFormatter --EXTENSIONS-- intl +--SKIPIF-- + --FILE-- IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, + 'IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE' => IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, + 'IDENTITY_FALLBACK_APPROXIMATELY' => IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, + 'IDENTITY_FALLBACK_RANGE' => IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE +]; + +$collapses = [ + 'COLLAPSE_AUTO' => IntlNumberRangeFormatter::COLLAPSE_AUTO, + 'COLLAPSE_NONE' => IntlNumberRangeFormatter::COLLAPSE_NONE, + 'COLLAPSE_UNIT' => IntlNumberRangeFormatter::COLLAPSE_UNIT, + 'COLLAPSE_ALL' => IntlNumberRangeFormatter::COLLAPSE_ALL +]; + +foreach ($languages as $language) { + foreach ($identityFallbacks as $iName => $identityFallback) { + foreach ($collapses as $cName => $collapse) { + echo PHP_EOL . $language . ' - ' . $cName . ' - ' . $iName . PHP_EOL; + $nrf = IntlNumberRangeFormatter::createFromSkeleton( + 'measure-unit/length-meter', + $language, + $collapse, + $identityFallback + ); + + var_dump($nrf->format(1.1, 2.2)); + var_dump($nrf->format(100, 200)); + var_dump($nrf->format(-5, 5)); + var_dump($nrf->format(5, 5)); + var_dump($nrf->format(5.0001, 5.0001)); + + } + } +} +echo 'en_GB - COLLAPSE_AUTO - IDENTITY_FALLBACK_SINGLE_VALUE' . PHP_EOL; $nrf = IntlNumberRangeFormatter::createFromSkeleton( - 'measure-unit/length-meter', 'ro', + 'measure-unit/length-meter', + 'en_GB', IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE ); -var_dump($nrf->format(1.1, 2.2)); -var_dump($nrf->format(100, 200)); -var_dump($nrf->format(-5, 5)); ?> --EXPECT-- +en_US - COLLAPSE_AUTO - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_NONE - IDENTITY_FALLBACK_SINGLE_VALUE +string(15) "1.1 m – 2.2 m" +string(15) "100 m – 200 m" +string(12) "-5 m – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_UNIT - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_ALL - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_AUTO - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_NONE - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(15) "1.1 m – 2.2 m" +string(15) "100 m – 200 m" +string(12) "-5 m – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_UNIT - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_ALL - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +en_US - COLLAPSE_AUTO - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(4) "~5 m" +string(9) "~5.0001 m" + +en_US - COLLAPSE_NONE - IDENTITY_FALLBACK_APPROXIMATELY +string(15) "1.1 m – 2.2 m" +string(15) "100 m – 200 m" +string(12) "-5 m – 5 m" +string(4) "~5 m" +string(9) "~5.0001 m" + +en_US - COLLAPSE_UNIT - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(4) "~5 m" +string(9) "~5.0001 m" + +en_US - COLLAPSE_ALL - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(4) "~5 m" +string(9) "~5.0001 m" + +en_US - COLLAPSE_AUTO - IDENTITY_FALLBACK_RANGE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(7) "5–5 m" +string(17) "5.0001–5.0001 m" + +en_US - COLLAPSE_NONE - IDENTITY_FALLBACK_RANGE +string(15) "1.1 m – 2.2 m" +string(15) "100 m – 200 m" +string(12) "-5 m – 5 m" +string(11) "5 m – 5 m" +string(21) "5.0001 m – 5.0001 m" + +en_US - COLLAPSE_UNIT - IDENTITY_FALLBACK_RANGE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(7) "5–5 m" +string(17) "5.0001–5.0001 m" + +en_US - COLLAPSE_ALL - IDENTITY_FALLBACK_RANGE +string(11) "1.1–2.2 m" +string(11) "100–200 m" +string(10) "-5 – 5 m" +string(7) "5–5 m" +string(17) "5.0001–5.0001 m" + +RO - COLLAPSE_AUTO - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_NONE - IDENTITY_FALLBACK_SINGLE_VALUE +string(13) "1,1 m - 2,2 m" +string(13) "100 m - 200 m" +string(10) "-5 m - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_UNIT - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_ALL - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_AUTO - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_NONE - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(13) "1,1 m - 2,2 m" +string(13) "100 m - 200 m" +string(10) "-5 m - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_UNIT - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_ALL - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(3) "5 m" +string(8) "5,0001 m" + +RO - COLLAPSE_AUTO - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(4) "~5 m" +string(9) "~5,0001 m" + +RO - COLLAPSE_NONE - IDENTITY_FALLBACK_APPROXIMATELY +string(13) "1,1 m - 2,2 m" +string(13) "100 m - 200 m" +string(10) "-5 m - 5 m" +string(4) "~5 m" +string(9) "~5,0001 m" + +RO - COLLAPSE_UNIT - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(4) "~5 m" +string(9) "~5,0001 m" + +RO - COLLAPSE_ALL - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(4) "~5 m" +string(9) "~5,0001 m" + +RO - COLLAPSE_AUTO - IDENTITY_FALLBACK_RANGE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(7) "5 - 5 m" +string(17) "5,0001 - 5,0001 m" + +RO - COLLAPSE_NONE - IDENTITY_FALLBACK_RANGE +string(13) "1,1 m - 2,2 m" +string(13) "100 m - 200 m" +string(10) "-5 m - 5 m" +string(9) "5 m - 5 m" +string(19) "5,0001 m - 5,0001 m" + +RO - COLLAPSE_UNIT - IDENTITY_FALLBACK_RANGE +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m" +string(7) "5 - 5 m" +string(17) "5,0001 - 5,0001 m" + +RO - COLLAPSE_ALL - IDENTITY_FALLBACK_RANGE string(11) "1,1 - 2,2 m" string(11) "100 - 200 m" string(8) "-5 - 5 m" +string(7) "5 - 5 m" +string(17) "5,0001 - 5,0001 m" + +JA - COLLAPSE_AUTO - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_NONE - IDENTITY_FALLBACK_SINGLE_VALUE +string(15) "1.1 m ~ 2.2 m" +string(15) "100 m ~ 200 m" +string(12) "-5 m ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_UNIT - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_ALL - IDENTITY_FALLBACK_SINGLE_VALUE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_AUTO - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_NONE - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(15) "1.1 m ~ 2.2 m" +string(15) "100 m ~ 200 m" +string(12) "-5 m ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_UNIT - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_ALL - IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(3) "5 m" +string(8) "5.0001 m" + +JA - COLLAPSE_AUTO - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(6) "約5 m" +string(11) "約5.0001 m" + +JA - COLLAPSE_NONE - IDENTITY_FALLBACK_APPROXIMATELY +string(15) "1.1 m ~ 2.2 m" +string(15) "100 m ~ 200 m" +string(12) "-5 m ~ 5 m" +string(6) "約5 m" +string(11) "約5.0001 m" + +JA - COLLAPSE_UNIT - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(6) "約5 m" +string(11) "約5.0001 m" + +JA - COLLAPSE_ALL - IDENTITY_FALLBACK_APPROXIMATELY +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(6) "約5 m" +string(11) "約5.0001 m" + +JA - COLLAPSE_AUTO - IDENTITY_FALLBACK_RANGE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(7) "5~5 m" +string(17) "5.0001~5.0001 m" + +JA - COLLAPSE_NONE - IDENTITY_FALLBACK_RANGE +string(15) "1.1 m ~ 2.2 m" +string(15) "100 m ~ 200 m" +string(12) "-5 m ~ 5 m" +string(11) "5 m ~ 5 m" +string(21) "5.0001 m ~ 5.0001 m" + +JA - COLLAPSE_UNIT - IDENTITY_FALLBACK_RANGE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(7) "5~5 m" +string(17) "5.0001~5.0001 m" + +JA - COLLAPSE_ALL - IDENTITY_FALLBACK_RANGE +string(11) "1.1~2.2 m" +string(11) "100~200 m" +string(10) "-5 ~ 5 m" +string(7) "5~5 m" +string(17) "5.0001~5.0001 m" +en_GB - COLLAPSE_AUTO - IDENTITY_FALLBACK_SINGLE_VALUE diff --git a/ext/intl/tests/rangeformatter/rangeformatter_clone.phpt b/ext/intl/tests/rangeformatter/rangeformatter_clone.phpt new file mode 100644 index 0000000000000..f8a0dd30e9da0 --- /dev/null +++ b/ext/intl/tests/rangeformatter/rangeformatter_clone.phpt @@ -0,0 +1,26 @@ +--TEST-- +Test IntlNumberRangeFormatter cannot be cloned +--SKIPIF-- + +--FILE-- +getMessage(); +} +?> +--EXPECT-- +Trying to clone an uncloneable object of class IntlNumberRangeFormatter \ No newline at end of file diff --git a/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt b/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt new file mode 100644 index 0000000000000..e4bd3edbb18fd --- /dev/null +++ b/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt @@ -0,0 +1,63 @@ +--TEST-- +errors for IntlNumberRangeFormatter +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; +} + +echo intl_get_error_code() . PHP_EOL; +echo intl_get_error_message() . PHP_EOL; + +try { + new IntlNumberRangeFormatter(); +} catch(Error $error) { + echo $error->getMessage() . PHP_EOL; +} + +try { + $nrf = IntlNumberRangeFormatter::createFromSkeleton( + 'invalid skeleton here', + 'ro', + 34, + IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE + ); +} catch (ValueError $exception) { + echo $exception->getMessage() . PHP_EOL; +} + +try { + $nrf = IntlNumberRangeFormatter::createFromSkeleton( + 'invalid skeleton here', + 'ro', + IntlNumberRangeFormatter::COLLAPSE_AUTO, + 343 + ); +} catch (ValueError $exception) { + echo $exception->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +Failed to create the number skeleton +65811 +Failed to create the number skeleton: U_NUMBER_SKELETON_SYNTAX_ERROR +Call to private IntlNumberRangeFormatter::__construct() from global scope +IntlNumberRangeFormatter::createFromSkeleton(): Argument #3 ($collapse) must be one of IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::COLLAPSE_NONE, IntlNumberRangeFormatter::COLLAPSE_UNIT, or IntlNumberRangeFormatter::COLLAPSE_ALL +IntlNumberRangeFormatter::createFromSkeleton(): Argument #4 ($identityFallback) must be one of IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, or IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE \ No newline at end of file diff --git a/ext/intl/tests/rangeformatter/rangeformatter_icu63_compatibility.phpt b/ext/intl/tests/rangeformatter/rangeformatter_icu63_compatibility.phpt new file mode 100644 index 0000000000000..5152a25c1706c --- /dev/null +++ b/ext/intl/tests/rangeformatter/rangeformatter_icu63_compatibility.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test IntlNumberRangeFormatter::createFromSkeleton throws error for ICU < 63 +--SKIPIF-- += 0) { + die('skip for ICU > 63.0'); +} +?> +--FILE-- +getMessage(); +} +?> +--EXPECT-- +IntlNumberRangeFormatter is not available in ICU 62 and earlier \ No newline at end of file From 6f6694e5b3311a5136c099a688cd1ef593eec5f3 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Sun, 3 Aug 2025 01:43:16 +0300 Subject: [PATCH 3/7] remove the 4th param in intl_error_set --- ext/intl/rangeformatter/rangeformatter_class.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp index eb44d1342aeb5..c5e3487c7379f 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.cpp +++ b/ext/intl/rangeformatter/rangeformatter_class.cpp @@ -120,7 +120,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton_ustr, status); if (U_FAILURE(status)) { - intl_error_set(NULL, status, "Failed to create the number skeleton", 0); + intl_error_set(NULL, status, "Failed to create the number skeleton"); zend_throw_exception(IntlException_ce_ptr, "Failed to create the number skeleton", 0); RETURN_THROWS(); } @@ -161,7 +161,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start_formattable, end_formattable, error).toString(error); if (U_FAILURE(error)) { - intl_error_set(NULL, error, "Failed to format number range", 0); + intl_error_set(NULL, error, "Failed to format number range"); zend_throw_exception(IntlException_ce_ptr, "Failed to format number range", 0); RETURN_THROWS(); } @@ -169,7 +169,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) zend_string *ret = intl_charFromString(result, &error); if (U_FAILURE(error)) { - intl_error_set(NULL, error, "Failed to convert result to UTF-8", 0); + intl_error_set(NULL, error, "Failed to convert result to UTF-8"); zend_throw_exception(IntlException_ce_ptr, "Failed to convert result to UTF-8", 0); RETURN_THROWS(); } From 47aa5fb8a01512e647c0047fd432195896a7ad2c Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Sun, 3 Aug 2025 02:05:36 +0300 Subject: [PATCH 4/7] fix failing test --- ext/intl/tests/rangeformatter/rangeformatter_errors.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt b/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt index e4bd3edbb18fd..8d2ab0b755785 100644 --- a/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt +++ b/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt @@ -57,7 +57,7 @@ try { --EXPECT-- Failed to create the number skeleton 65811 -Failed to create the number skeleton: U_NUMBER_SKELETON_SYNTAX_ERROR +IntlNumberRangeFormatter::createFromSkeleton(): Failed to create the number skeleton: U_NUMBER_SKELETON_SYNTAX_ERROR Call to private IntlNumberRangeFormatter::__construct() from global scope IntlNumberRangeFormatter::createFromSkeleton(): Argument #3 ($collapse) must be one of IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::COLLAPSE_NONE, IntlNumberRangeFormatter::COLLAPSE_UNIT, or IntlNumberRangeFormatter::COLLAPSE_ALL IntlNumberRangeFormatter::createFromSkeleton(): Argument #4 ($identityFallback) must be one of IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, or IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE \ No newline at end of file From 775e987948137ac9db52f95afacf7d6c86ad31f0 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Mon, 4 Aug 2025 15:34:05 +0300 Subject: [PATCH 5/7] change validation order and use reinterpret_cast --- ext/intl/rangeformatter/rangeformatter_class.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp index c5e3487c7379f..838f015d5c317 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.cpp +++ b/ext/intl/rangeformatter/rangeformatter_class.cpp @@ -44,7 +44,7 @@ zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce) { IntlNumberRangeFormatter_object* intern; - intern = (IntlNumberRangeFormatter_object*)zend_object_alloc(sizeof(IntlNumberRangeFormatter_object), ce); + intern = reinterpret_cast(zend_object_alloc(sizeof(IntlNumberRangeFormatter_object), ce)); zend_object_std_init(&intern->zo, ce); object_properties_init(&intern->zo, ce); @@ -84,14 +84,14 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) Z_PARAM_LONG(identityFallback) ZEND_PARSE_PARAMETERS_END(); - if (locale_len == 0) { - locale = (char *)intl_locale_get_default(); - } - if (skeleton_len == 0) { zend_argument_must_not_be_empty_error(1); RETURN_THROWS(); } + + if (locale_len == 0) { + locale = (char *)intl_locale_get_default(); + } if (locale_len > INTL_MAX_LOCALE_LEN) { zend_argument_value_error(2, "must be no longer than %d characters", INTL_MAX_LOCALE_LEN); From c0443429d5c4ca4412e1b1dbeabb3fad0eb7e95d Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Mon, 4 Aug 2025 15:39:03 +0300 Subject: [PATCH 6/7] Add more tests --- .../rangeformatter/rangeformatter_errors.phpt | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt b/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt index 8d2ab0b755785..58a62c1e26b3e 100644 --- a/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt +++ b/ext/intl/tests/rangeformatter/rangeformatter_errors.phpt @@ -53,6 +53,28 @@ try { echo $exception->getMessage() . PHP_EOL; } +try { + $nrf = IntlNumberRangeFormatter::createFromSkeleton( + 'invalid skeleton here', + 'invalid-language', + IntlNumberRangeFormatter::COLLAPSE_AUTO, + IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE + ); +} catch (ValueError $exception) { + echo $exception->getMessage() . PHP_EOL; +} + +try { + $nrf = IntlNumberRangeFormatter::createFromSkeleton( + 'invalid skeleton here', + 'ro_thisiswaytooooooooooooooooooooooooooooooooooooooooooooolongtobevaliditneedstobeatleast157characterstofailthevalidationinthelistformattercodeimplementation', + IntlNumberRangeFormatter::COLLAPSE_AUTO, + IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE + ); +} catch (ValueError $exception) { + echo $exception->getMessage() . PHP_EOL; +} + ?> --EXPECT-- Failed to create the number skeleton @@ -60,4 +82,6 @@ Failed to create the number skeleton IntlNumberRangeFormatter::createFromSkeleton(): Failed to create the number skeleton: U_NUMBER_SKELETON_SYNTAX_ERROR Call to private IntlNumberRangeFormatter::__construct() from global scope IntlNumberRangeFormatter::createFromSkeleton(): Argument #3 ($collapse) must be one of IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::COLLAPSE_NONE, IntlNumberRangeFormatter::COLLAPSE_UNIT, or IntlNumberRangeFormatter::COLLAPSE_ALL -IntlNumberRangeFormatter::createFromSkeleton(): Argument #4 ($identityFallback) must be one of IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, or IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE \ No newline at end of file +IntlNumberRangeFormatter::createFromSkeleton(): Argument #4 ($identityFallback) must be one of IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, or IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE +IntlNumberRangeFormatter::createFromSkeleton(): Argument #2 ($locale) "invalid-language" is invalid +IntlNumberRangeFormatter::createFromSkeleton(): Argument #2 ($locale) must be no longer than 156 characters \ No newline at end of file From b9e595702c97575198ff15f4a931e02ab4156807 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Tue, 5 Aug 2025 00:58:47 +0300 Subject: [PATCH 7/7] formatting fixes --- .../rangeformatter/rangeformatter_class.cpp | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp index 838f015d5c317..21a8fa71fec3b 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.cpp +++ b/ext/intl/rangeformatter/rangeformatter_class.cpp @@ -27,7 +27,7 @@ extern "C" { #include "../intl_data.h" #include "rangeformatter_arginfo.h" #include "rangeformatter_class.h" -#include "intl_convert.h" + #include "intl_convert.h" } using icu::number::NumberRangeFormatter; @@ -45,16 +45,16 @@ zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce) IntlNumberRangeFormatter_object* intern; intern = reinterpret_cast(zend_object_alloc(sizeof(IntlNumberRangeFormatter_object), ce)); - zend_object_std_init(&intern->zo, ce); - object_properties_init(&intern->zo, ce); + zend_object_std_init(&intern->zo, ce); + object_properties_init(&intern->zo, ce); - // Initialize rangeformatter_data structure - intl_error_init(&intern->nrf_data.error); - intern->nrf_data.unumrf = nullptr; + // Initialize rangeformatter_data structure + intl_error_init(&intern->nrf_data.error); + intern->nrf_data.unumrf = nullptr; intern->zo.handlers = &rangeformatter_handlers; - return &intern->zo; + return &intern->zo; } U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct) @@ -65,10 +65,10 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct) U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) { - #if U_ICU_VERSION_MAJOR_NUM < 63 - zend_throw_error(NULL, "IntlNumberRangeFormatter is not available in ICU 62 and earlier"); - RETURN_THROWS(); - #endif +#if U_ICU_VERSION_MAJOR_NUM < 63 + zend_throw_error(NULL, "IntlNumberRangeFormatter is not available in ICU 62 and earlier"); + RETURN_THROWS(); +#endif char* skeleton; char* locale; @@ -88,7 +88,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) zend_argument_must_not_be_empty_error(1); RETURN_THROWS(); } - + if (locale_len == 0) { locale = (char *)intl_locale_get_default(); } @@ -138,7 +138,6 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) RANGEFORMATTER_OBJECT(php_intl_numberrangeformatter_fetch_object(obj)) = nrf; RETURN_OBJ(obj); - } U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) @@ -166,15 +165,15 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) RETURN_THROWS(); } - zend_string *ret = intl_charFromString(result, &error); + zend_string *ret = intl_charFromString(result, &error); - if (U_FAILURE(error)) { + if (U_FAILURE(error)) { intl_error_set(NULL, error, "Failed to convert result to UTF-8"); zend_throw_exception(IntlException_ce_ptr, "Failed to convert result to UTF-8", 0); RETURN_THROWS(); - } + } - RETVAL_NEW_STR(ret); + RETVAL_NEW_STR(ret); } U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, getErrorCode) @@ -197,7 +196,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, getErrorMessage) void IntlNumberRangeFormatter_object_free(zend_object *object) { - IntlNumberRangeFormatter_object* nfo = php_intl_numberrangeformatter_fetch_object(object); + IntlNumberRangeFormatter_object* nfo = php_intl_numberrangeformatter_fetch_object(object); if (nfo->nrf_data.unumrf) { delete nfo->nrf_data.unumrf; @@ -206,14 +205,14 @@ void IntlNumberRangeFormatter_object_free(zend_object *object) intl_error_reset(&nfo->nrf_data.error); - zend_object_std_dtor(&nfo->zo); + zend_object_std_dtor(&nfo->zo); } void rangeformatter_register_class(void) { class_entry_IntlNumberRangeFormatter = register_class_IntlNumberRangeFormatter(); class_entry_IntlNumberRangeFormatter->create_object = IntlNumberRangeFormatter_object_create; - + memcpy(&rangeformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); rangeformatter_handlers.offset = XtOffsetOf(IntlNumberRangeFormatter_object, zo); rangeformatter_handlers.free_obj = IntlNumberRangeFormatter_object_free;