Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ PHP 8.7 UPGRADE NOTES
7. New Classes and Interfaces
========================================

- Intl:
. Added IntlNumberFormatter class to format a number with a given locale
and ICU number skeleton, which also covers measurement units, e.g.
"measure-unit/speed-kilometer-per-hour unit-width-full-name".
IntlNumberFormatter::format() accepts an int, a float or a numeric
string, the latter being formatted as a decimal of arbitrary precision.
It is supported from icu 63.

========================================
8. Removed Extensions and SAPIs
========================================
Expand Down
2 changes: 1 addition & 1 deletion ext/intl/CREDITS
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Internationalization
Ed Batutis, Vladimir Iordanov, Dmitry Lakhtyuk, Stanislav Malyshev, Vadim Savchuk, Kirti Velankar
Ed Batutis, David Carlier, Vladimir Iordanov, Dmitry Lakhtyuk, Stanislav Malyshev, Vadim Savchuk, Kirti Velankar
2 changes: 2 additions & 0 deletions ext/intl/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ if test "$PHP_INTL" != "no"; then
grapheme/grapheme_string.cpp \
grapheme/grapheme_util.cpp \
msgformat/msgformat_helpers.cpp \
numberformatter/numberformatter_class.cpp \
rangeformatter/rangeformatter_class.cpp \
timezone/timezone_class.cpp \
timezone/timezone_methods.cpp \
Expand Down Expand Up @@ -126,6 +127,7 @@ if test "$PHP_INTL" != "no"; then
$ext_builddir/listformatter
$ext_builddir/msgformat
$ext_builddir/normalizer
$ext_builddir/numberformatter
$ext_builddir/rangeformatter
$ext_builddir/resourcebundle
$ext_builddir/spoofchecker
Expand Down
3 changes: 3 additions & 0 deletions ext/intl/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ if (PHP_INTL != "no") {
normalizer_class.cpp \
normalizer_normalize.cpp \
", "intl");
ADD_SOURCES(configure_module_dirname + "/numberformatter", "\
numberformatter_class.cpp \
", "intl");
ADD_SOURCES(configure_module_dirname + "/rangeformatter", "\
rangeformatter_class.cpp \
", "intl");
Expand Down
19 changes: 19 additions & 0 deletions ext/intl/numberformatter/numberformatter.stub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/** @generate-class-entries */

/**
* @not-serializable
* @strict-properties
*/
final class IntlNumberFormatter {
private function __construct() {}

public static function createFromSkeleton(string $skeleton, string $locale): ?IntlNumberFormatter {}

public function format(float|int|string $value): string|false {}

public function getErrorCode(): int {}

public function getErrorMessage(): string {}
}
45 changes: 45 additions & 0 deletions ext/intl/numberformatter/numberformatter_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

177 changes: 177 additions & 0 deletions ext/intl/numberformatter/numberformatter_class.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
+----------------------------------------------------------------------+
| Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
| This source file is subject to the Modified BSD License that is |
| bundled with this package in the file LICENSE, and is available |
| through the World Wide Web at <https://www.php.net/license/>. |
| |
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: David Carlier <devnexen@gmail.com> |
+----------------------------------------------------------------------+
*/

extern "C" {
#include "php.h"
#include "zend_API.h"
#include "../intl_common.h"
}

#if U_ICU_VERSION_MAJOR_NUM >= 63
#include <unicode/numberformatter.h>
#include <unicode/unistr.h>
#include <unicode/locid.h>
#include "../intl_convertcpp.h"

extern "C" {
#include "../php_intl.h"
#include "numberformatter_arginfo.h"
}
#include "numberformatter_class.h"

using icu::number::NumberFormatter;
using icu::number::UnlocalizedNumberFormatter;
using icu::UnicodeString;

static zend_object_handlers numberformatter_handlers;
zend_class_entry *IntlNumberFormatter_ce_ptr;

static void numberformatter_free_obj(zend_object *object)
{
IntlNumberFormatter_object *nfo = php_intl_numberformatter_fetch_object(object);

delete NUMBERFORMATTER_OBJECT(nfo);
NUMBERFORMATTER_OBJECT(nfo) = nullptr;
intl_error_reset(INTL_DATA_ERROR_P(nfo));

zend_object_std_dtor(&nfo->zo);
}

static zend_object *numberformatter_create_object(zend_class_entry *ce)
{
IntlNumberFormatter_object *nfo = reinterpret_cast<IntlNumberFormatter_object *>(zend_object_alloc(sizeof(IntlNumberFormatter_object), ce));

intl_error_init(INTL_DATA_ERROR_P(nfo));
NUMBERFORMATTER_OBJECT(nfo) = nullptr;

zend_object_std_init(&nfo->zo, ce);
object_properties_init(&nfo->zo, ce);
nfo->zo.handlers = &numberformatter_handlers;

return &nfo->zo;
}

U_CFUNC PHP_METHOD(IntlNumberFormatter, __construct)
{
ZEND_PARSE_PARAMETERS_NONE();

zend_throw_error(nullptr, "Cannot directly construct %s, use createFromSkeleton method instead", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
}

U_CFUNC PHP_METHOD(IntlNumberFormatter, createFromSkeleton)
{
char *skeleton, *locale;
size_t skeleton_len, locale_len;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STRING(skeleton, skeleton_len)
Z_PARAM_STRING(locale, locale_len)
ZEND_PARSE_PARAMETERS_END();

intl_error_reset(nullptr);

INTL_CHECK_LOCALE_LEN(locale_len);

if (locale_len == 0) {
locale = (char *)intl_locale_get_default();
}

if (icu::Locale(locale).getISO3Language()[0] == '\0') {
zend_argument_value_error(2, "\"%s\" is invalid", locale);
RETURN_THROWS();
}

UErrorCode status = U_ZERO_ERROR;
UnicodeString uskeleton;

intl_stringFromChar(uskeleton, skeleton, skeleton_len, &status);
INTL_CHECK_STATUS_OR_NULL(status, "Error converting skeleton to UTF-16");

UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(uskeleton, status);
INTL_CHECK_STATUS_OR_NULL(status, "Number formatter creation failed");

object_init_ex(return_value, IntlNumberFormatter_ce_ptr);
NUMBERFORMATTER_OBJECT(Z_INTL_INTLNUMBERFORMATTER_P(return_value)) = new LocalizedNumberFormatter(nf.locale(icu::Locale::createCanonical(locale)));
}

U_CFUNC PHP_METHOD(IntlNumberFormatter, format)
{
zval *value;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_NUMBER_OR_STR(value)
ZEND_PARSE_PARAMETERS_END();

IntlNumberFormatter_object *nfo = Z_INTL_INTLNUMBERFORMATTER_P(ZEND_THIS);
intl_errors_reset(INTL_DATA_ERROR_P(nfo));

UErrorCode status = U_ZERO_ERROR;
UnicodeString result;

if (Z_TYPE_P(value) == IS_STRING) {
if (UNEXPECTED(zend_str_has_nul_byte(Z_STR_P(value)))) {
zend_argument_value_error(1, "must not contain any null bytes");
RETURN_THROWS();
}
if (UNEXPECTED(Z_STRLEN_P(value) > INT32_MAX)) {
zend_argument_value_error(1, "is too long");
RETURN_THROWS();
}
result = NUMBERFORMATTER_OBJECT(nfo)->formatDecimal(icu::StringPiece(Z_STRVAL_P(value), static_cast<int32_t>(Z_STRLEN_P(value))), status).toString(status);
} else if (Z_TYPE_P(value) == IS_DOUBLE) {
result = NUMBERFORMATTER_OBJECT(nfo)->formatDouble(Z_DVAL_P(value), status).toString(status);
} else {
result = NUMBERFORMATTER_OBJECT(nfo)->formatInt(static_cast<int64_t>(Z_LVAL_P(value)), status).toString(status);
}

if (U_FAILURE(status)) {
intl_errors_set(INTL_DATA_ERROR_P(nfo), status, "Number formatting failed");
RETURN_FALSE;
}

zend_string *ret = intl_charFromString(result, &status);

if (!ret) {
intl_errors_set(INTL_DATA_ERROR_P(nfo), status, "Error converting value to UTF-8");
RETURN_FALSE;
}

RETVAL_STR(ret);
}

U_CFUNC PHP_METHOD(IntlNumberFormatter, getErrorCode)
{
ZEND_PARSE_PARAMETERS_NONE();

RETURN_LONG(INTL_DATA_ERROR_CODE(Z_INTL_INTLNUMBERFORMATTER_P(ZEND_THIS)));
}

U_CFUNC PHP_METHOD(IntlNumberFormatter, getErrorMessage)
{
ZEND_PARSE_PARAMETERS_NONE();

RETURN_STR(intl_error_get_message(INTL_DATA_ERROR_P(Z_INTL_INTLNUMBERFORMATTER_P(ZEND_THIS))));
}

void numberformatter_register_class(void)
{
IntlNumberFormatter_ce_ptr = register_class_IntlNumberFormatter();
IntlNumberFormatter_ce_ptr->create_object = numberformatter_create_object;

memcpy(&numberformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
numberformatter_handlers.offset = offsetof(IntlNumberFormatter_object, zo);
numberformatter_handlers.free_obj = numberformatter_free_obj;
numberformatter_handlers.clone_obj = nullptr;
}
#endif
60 changes: 60 additions & 0 deletions ext/intl/numberformatter/numberformatter_class.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
+----------------------------------------------------------------------+
| Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
| This source file is subject to the Modified BSD License that is |
| bundled with this package in the file LICENSE, and is available |
| through the World Wide Web at <https://www.php.net/license/>. |
| |
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: David Carlier <devnexen@gmail.com> |
+----------------------------------------------------------------------+
*/

#ifndef NUMBERFORMATTER_CLASS_H
#define NUMBERFORMATTER_CLASS_H

#ifdef __cplusplus
extern "C" {
#endif
#include <php.h>
#include "intl_common.h"
#include "intl_error.h"
#include "intl_data.h"
#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
#include <unicode/numberformatter.h>
using icu::number::LocalizedNumberFormatter;
#else
typedef void LocalizedNumberFormatter;
#endif

typedef struct {
intl_error error;
LocalizedNumberFormatter *unumf;
} numberformatter_data;

typedef struct {
numberformatter_data nf_data;
zend_object zo;
} IntlNumberFormatter_object;

#define php_intl_numberformatter_fetch_object(obj) ZEND_CONTAINER_OF(obj, IntlNumberFormatter_object, zo)
#define Z_INTL_INTLNUMBERFORMATTER_P(zv) php_intl_numberformatter_fetch_object(Z_OBJ_P(zv))

#define NUMBERFORMATTER_OBJECT(nfo) (nfo)->nf_data.unumf

#ifdef __cplusplus
extern "C" {
#endif
void numberformatter_register_class(void);
extern zend_class_entry *IntlNumberFormatter_ce_ptr;
#ifdef __cplusplus
}
#endif

#endif
4 changes: 4 additions & 0 deletions ext/intl/php_intl.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "locale/locale_class.h"

#include "listformatter/listformatter_class.h"
#include "numberformatter/numberformatter_class.h"
#include "rangeformatter/rangeformatter_class.h"

#include "dateformat/dateformat.h"
Expand Down Expand Up @@ -190,6 +191,9 @@ PHP_MINIT_FUNCTION( intl )
listformatter_register_class( );

#if U_ICU_VERSION_MAJOR_NUM >= 63
/* Register 'IntlNumberFormatter' PHP class */
numberformatter_register_class( );

/* Register 'NumberRangeFormatter' PHP class */
rangeformatter_register_class( );
#endif
Expand Down
Loading
Loading