From 1128af337b9d035dc12b196d0c0ec9cc980f516a Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 24 Aug 2026 11:44:06 -0400 Subject: [PATCH] [intl] Preserve PHP-side state when cloning formatters IntlDateFormatter_object_clone() left date_type, time_type, calendar and requested_locale at their constructor defaults and MessageFormatter_object_ clone() dropped orig_format/orig_format_len/tz_set, so a cloned formatter reported wrong types/calendar/pattern and lost the requested locale used by datefmt_set_calendar(). The clone handlers now copy these fields alongside the ICU handle; msgformat_data.arg_types is deliberately not copied since it is a lazily rebuilt cache derived from the cloned ICU formatter. Sibling audit: NumberFormatter, IntlCalendar, SpoofChecker and Transliterator carry no other PHP-side scalar state in their clone paths. --- NEWS | 4 ++ ext/intl/dateformat/dateformat_class.c | 7 ++++ ext/intl/msgformat/msgformat_class.c | 6 +++ .../tests/clone_preserves_php_fields.phpt | 37 +++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 ext/intl/tests/clone_preserves_php_fields.phpt diff --git a/NEWS b/NEWS index 3346d38ea898..8d7f6977bab9 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,10 @@ PHP NEWS registrations are freed while still reachable from the cycle collector. (Ilia Alshanetsky) +- Intl: + . Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state + such as dateType, timeType, calendar and the message pattern. (Ilia Alshanetsky) + 24 Sep 2026, PHP 8.4.26 diff --git a/ext/intl/dateformat/dateformat_class.c b/ext/intl/dateformat/dateformat_class.c index 15bf5bf23ce5..21bb16dcd46b 100644 --- a/ext/intl/dateformat/dateformat_class.c +++ b/ext/intl/dateformat/dateformat_class.c @@ -71,6 +71,13 @@ zend_object *IntlDateFormatter_object_clone(zend_object *object) /* clone standard parts */ zend_objects_clone_members(&new_dfo->zo, &dfo->zo); + new_dfo->date_type = dfo->date_type; + new_dfo->time_type = dfo->time_type; + new_dfo->calendar = dfo->calendar; + if (dfo->requested_locale != NULL) { + new_dfo->requested_locale = estrdup(dfo->requested_locale); + } + /* clone formatter object */ if (DATE_FORMAT_OBJECT(dfo) != NULL) { UErrorCode error = U_ZERO_ERROR; diff --git a/ext/intl/msgformat/msgformat_class.c b/ext/intl/msgformat/msgformat_class.c index 4e0766a911b9..0d5097e481d0 100644 --- a/ext/intl/msgformat/msgformat_class.c +++ b/ext/intl/msgformat/msgformat_class.c @@ -63,6 +63,12 @@ zend_object *MessageFormatter_object_clone(zend_object *object) /* clone standard parts */ zend_objects_clone_members(&new_mfo->zo, &mfo->zo); + if (mfo->mf_data.orig_format != NULL) { + new_mfo->mf_data.orig_format = estrndup(mfo->mf_data.orig_format, mfo->mf_data.orig_format_len); + new_mfo->mf_data.orig_format_len = mfo->mf_data.orig_format_len; + } + new_mfo->mf_data.tz_set = mfo->mf_data.tz_set; + /* clone formatter object */ if (MSG_FORMAT_OBJECT(mfo) != NULL) { UErrorCode error = U_ZERO_ERROR; diff --git a/ext/intl/tests/clone_preserves_php_fields.phpt b/ext/intl/tests/clone_preserves_php_fields.phpt new file mode 100644 index 000000000000..1c57ce539ad6 --- /dev/null +++ b/ext/intl/tests/clone_preserves_php_fields.phpt @@ -0,0 +1,37 @@ +--TEST-- +Cloning IntlDateFormatter and MessageFormatter preserves PHP-side fields +--EXTENSIONS-- +intl +--FILE-- +getDateType()); +var_dump($c->getTimeType()); +var_dump($c->getCalendar()); +var_dump($c->format(strtotime('2024-01-02 03:04:05 UTC'))); +$c->setCalendar(IntlDateFormatter::GREGORIAN); +var_dump($c->getPattern()); + +$m = new MessageFormatter('en_US', '{0, number}'); +$mc = clone $m; +var_dump($mc->getPattern()); +var_dump($mc->format([1.5])); + +date_default_timezone_set('UTC'); +$t = new MessageFormatter('en_US', '{0,time,short}'); +$dt = new DateTimeImmutable('2024-01-02 03:04:05', new DateTimeZone('UTC')); +$t->format([$dt]); +$tc = clone $t; +date_default_timezone_set('America/New_York'); +var_dump($t->format([$dt]) === $tc->format([$dt])); +?> +--EXPECT-- +int(1) +int(2) +int(1) +string(26) "2. Januar 2024 um 03:04:05" +string(23) "d. MMMM y 'um' HH:mm:ss" +string(11) "{0, number}" +string(3) "1.5" +bool(true)