Skip to content

Fixes GH-8152: add __serialise and __unserialise methods to DateInterval #8459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
127 changes: 112 additions & 15 deletions ext/date/php_date.c
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,11 @@ static zend_object *date_object_clone_interval(zend_object *this_ptr) /* {{{ */
php_interval_obj *new_obj = php_interval_obj_from_obj(date_object_new_interval(old_obj->std.ce));

zend_objects_clone_members(&new_obj->std, &old_obj->std);
new_obj->civil_or_wall = old_obj->civil_or_wall;
new_obj->from_string = old_obj->from_string;
if (old_obj->date_string) {
new_obj->date_string = zend_string_copy(old_obj->date_string);
}
new_obj->initialized = old_obj->initialized;
if (old_obj->diff) {
new_obj->diff = timelib_rel_time_clone(old_obj->diff);
Expand All @@ -2061,16 +2066,17 @@ static HashTable *date_object_get_gc_interval(zend_object *object, zval **table,
return zend_std_get_properties(object);
} /* }}} */

static HashTable *date_object_get_properties_interval(zend_object *object) /* {{{ */
static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTable *props)
{
HashTable *props;
zval zv;
php_interval_obj *intervalobj;

intervalobj = php_interval_obj_from_obj(object);
props = zend_std_get_properties(object);
if (!intervalobj->initialized) {
return props;
/* Records whether this is a special relative interval that needs to be recreated from a string */
if (intervalobj->from_string) {
ZVAL_BOOL(&zv, (zend_bool)intervalobj->from_string);
zend_hash_str_update(props, "from_string", strlen("from_string"), &zv);
ZVAL_STR_COPY(&zv, intervalobj->date_string);
zend_hash_str_update(props, "date_string", strlen("date_string"), &zv);
return;
}

#define PHP_DATE_INTERVAL_ADD_PROPERTY(n,f) \
Expand All @@ -2085,20 +2091,31 @@ static HashTable *date_object_get_properties_interval(zend_object *object) /* {{
PHP_DATE_INTERVAL_ADD_PROPERTY("s", s);
ZVAL_DOUBLE(&zv, (double)intervalobj->diff->us / 1000000.0);
zend_hash_str_update(props, "f", sizeof("f") - 1, &zv);
PHP_DATE_INTERVAL_ADD_PROPERTY("weekday", weekday);
PHP_DATE_INTERVAL_ADD_PROPERTY("weekday_behavior", weekday_behavior);
PHP_DATE_INTERVAL_ADD_PROPERTY("first_last_day_of", first_last_day_of);
PHP_DATE_INTERVAL_ADD_PROPERTY("invert", invert);
if (intervalobj->diff->days != -99999) {
PHP_DATE_INTERVAL_ADD_PROPERTY("days", days);
} else {
ZVAL_FALSE(&zv);
zend_hash_str_update(props, "days", sizeof("days")-1, &zv);
}
PHP_DATE_INTERVAL_ADD_PROPERTY("special_type", special.type);
PHP_DATE_INTERVAL_ADD_PROPERTY("special_amount", special.amount);
PHP_DATE_INTERVAL_ADD_PROPERTY("have_weekday_relative", have_weekday_relative);
PHP_DATE_INTERVAL_ADD_PROPERTY("have_special_relative", have_special_relative);
ZVAL_BOOL(&zv, (zend_bool)intervalobj->from_string);
zend_hash_str_update(props, "from_string", strlen("from_string"), &zv);

#undef PHP_DATE_INTERVAL_ADD_PROPERTY
}

static HashTable *date_object_get_properties_interval(zend_object *object) /* {{{ */
{
HashTable *props;
php_interval_obj *intervalobj;

intervalobj = php_interval_obj_from_obj(object);
props = zend_std_get_properties(object);
if (!intervalobj->initialized) {
return props;
}

date_interval_object_to_hash(intervalobj, props);

return props;
} /* }}} */
Expand Down Expand Up @@ -2166,6 +2183,10 @@ static void date_object_free_storage_interval(zend_object *object) /* {{{ */
{
php_interval_obj *intern = php_interval_obj_from_obj(object);

if (intern->date_string) {
zend_string_release(intern->date_string);
intern->date_string = NULL;
}
timelib_rel_time_dtor(intern->diff);
zend_object_std_dtor(&intern->std);
} /* }}} */
Expand Down Expand Up @@ -3082,7 +3103,7 @@ static void php_date_sub(zval *object, zval *interval, zval *return_value) /* {{
intobj = Z_PHPINTERVAL_P(interval);
DATE_CHECK_INITIALIZED(intobj->initialized, DateInterval);

if (intobj->diff->have_special_relative) {
if (intobj->diff->have_weekday_relative || intobj->diff->have_special_relative) {
php_error_docref(NULL, E_WARNING, "Only non-special relative time specifications are supported for subtraction");
return;
}
Expand Down Expand Up @@ -4092,6 +4113,41 @@ PHP_METHOD(DateInterval, __construct)

static void php_date_interval_initialize_from_hash(zval **return_value, php_interval_obj **intobj, HashTable *myht) /* {{{ */
{
/* If ->diff is already set, then we need to free it first */
if ((*intobj)->diff) {
timelib_rel_time_dtor((*intobj)->diff);
}

/* If we have a date_string, use that instead */
zval *date_str = zend_hash_str_find(myht, "date_string", strlen("date_string"));
if (date_str && Z_TYPE_P(date_str) == IS_STRING) {
timelib_time *time;
timelib_error_container *err = NULL;

time = timelib_strtotime(Z_STRVAL_P(date_str), Z_STRLEN_P(date_str), &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);

if (err->error_count > 0) {
php_error_docref(NULL,
E_WARNING,
"Unknown or bad format (%s) at position %d (%c) while unserializing: %s",
Z_STRVAL_P(date_str),
err->error_messages[0].position,
err->error_messages[0].character ? err->error_messages[0].character : ' ', err->error_messages[0].message);
}

(*intobj)->diff = timelib_rel_time_clone(&time->relative);
(*intobj)->initialized = 1;
(*intobj)->civil_or_wall = PHP_DATE_CIVIL;
(*intobj)->from_string = true;
(*intobj)->date_string = zend_string_copy(Z_STR_P(date_str));

timelib_time_dtor(time);
timelib_error_container_dtor(err);

return;
}

/* Set new value */
(*intobj)->diff = timelib_rel_time_ctor();

#define PHP_DATE_INTERVAL_READ_PROPERTY(element, member, itype, def) \
Expand Down Expand Up @@ -4170,6 +4226,7 @@ static void php_date_interval_initialize_from_hash(zval **return_value, php_inte
(*intobj)->civil_or_wall = val;
}
}

(*intobj)->initialized = 1;
} /* }}} */

Expand All @@ -4192,6 +4249,44 @@ PHP_METHOD(DateInterval, __set_state)
}
/* }}} */

/* {{{ */
PHP_METHOD(DateInterval, __serialize)
{
zval *object = ZEND_THIS;
php_interval_obj *intervalobj;
HashTable *myht;

ZEND_PARSE_PARAMETERS_NONE();

intervalobj = Z_PHPINTERVAL_P(object);
DATE_CHECK_INITIALIZED(intervalobj->initialized, DateInterval);

array_init(return_value);
myht = Z_ARRVAL_P(return_value);
date_interval_object_to_hash(intervalobj, myht);
}
/* }}} */


/* {{{ */
PHP_METHOD(DateInterval, __unserialize)
{
zval *object = ZEND_THIS;
php_interval_obj *intervalobj;
zval *array;
HashTable *myht;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(array)
ZEND_PARSE_PARAMETERS_END();

intervalobj = Z_PHPINTERVAL_P(object);
myht = Z_ARRVAL_P(array);

php_date_interval_initialize_from_hash(&object, &intervalobj, myht);
}
/* }}} */

/* {{{ */
PHP_METHOD(DateInterval, __wakeup)
{
Expand Down Expand Up @@ -4235,6 +4330,8 @@ PHP_FUNCTION(date_interval_create_from_date_string)
diobj->diff = timelib_rel_time_clone(&time->relative);
diobj->initialized = 1;
diobj->civil_or_wall = PHP_DATE_CIVIL;
diobj->from_string = true;
diobj->date_string = zend_string_copy(time_str);

cleanup:
timelib_time_dtor(time);
Expand Down
2 changes: 2 additions & 0 deletions ext/date/php_date.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ static inline php_timezone_obj *php_timezone_obj_from_obj(zend_object *obj) {
struct _php_interval_obj {
timelib_rel_time *diff;
int civil_or_wall;
bool from_string;
zend_string *date_string;
bool initialized;
zend_object std;
};
Expand Down
4 changes: 4 additions & 0 deletions ext/date/php_date.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,10 @@ public static function createFromDateString(string $datetime): DateInterval|fals
*/
public function format(string $format): string {}

public function __serialize(): array;

public function __unserialize(array $data): void;

/** @tentative-return-type */
public function __wakeup(): void {}

Expand Down
10 changes: 9 additions & 1 deletion ext/date/php_date_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: a157de6bca4bcf5a9ddace9e81ef700f132b4dda */
* Stub hash: 4845891ab3872f292438de639953e2022f849125 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_strtotime, 0, 1, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, datetime, IS_STRING, 0)
Expand Down Expand Up @@ -451,6 +451,10 @@ ZEND_END_ARG_INFO()

#define arginfo_class_DateInterval_format arginfo_class_DateTimeInterface_format

#define arginfo_class_DateInterval___serialize arginfo_timezone_abbreviations_list

#define arginfo_class_DateInterval___unserialize arginfo_class_DateTimeInterface___unserialize

#define arginfo_class_DateInterval___wakeup arginfo_class_DateTimeInterface___wakeup

ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_DateInterval___set_state, 0, 1, DateInterval, 0)
Expand Down Expand Up @@ -562,6 +566,8 @@ ZEND_METHOD(DateTimeZone, __unserialize);
ZEND_METHOD(DateTimeZone, __wakeup);
ZEND_METHOD(DateTimeZone, __set_state);
ZEND_METHOD(DateInterval, __construct);
ZEND_METHOD(DateInterval, __serialize);
ZEND_METHOD(DateInterval, __unserialize);
ZEND_METHOD(DateInterval, __wakeup);
ZEND_METHOD(DateInterval, __set_state);
ZEND_METHOD(DatePeriod, __construct);
Expand Down Expand Up @@ -714,6 +720,8 @@ static const zend_function_entry class_DateInterval_methods[] = {
ZEND_ME(DateInterval, __construct, arginfo_class_DateInterval___construct, ZEND_ACC_PUBLIC)
ZEND_ME_MAPPING(createFromDateString, date_interval_create_from_date_string, arginfo_class_DateInterval_createFromDateString, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
ZEND_ME_MAPPING(format, date_interval_format, arginfo_class_DateInterval_format, ZEND_ACC_PUBLIC)
ZEND_ME(DateInterval, __serialize, arginfo_class_DateInterval___serialize, ZEND_ACC_PUBLIC)
ZEND_ME(DateInterval, __unserialize, arginfo_class_DateInterval___unserialize, ZEND_ACC_PUBLIC)
ZEND_ME(DateInterval, __wakeup, arginfo_class_DateInterval___wakeup, ZEND_ACC_PUBLIC)
ZEND_ME(DateInterval, __set_state, arginfo_class_DateInterval___set_state, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
ZEND_FE_END
Expand Down
123 changes: 123 additions & 0 deletions ext/date/tests/DateInterval_serialize-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
--TEST--
Test DateInterval::__serialize and DateInterval::__unserialize
--FILE--
<?php
date_default_timezone_set("Europe/London");

$d = new DateInterval('P2Y4DT6H8M');
echo "Original object:\n";
var_dump($d);

echo "\n\nSerialised object:\n";
$s = serialize($d);
var_dump($s);

echo "\n\nUnserialised object:\n";
$e = unserialize($s);
var_dump($e);

echo "\n\nCalling __serialize manually:\n";
var_dump($d->__serialize());

echo "\n\nUsed serialised interval:\n";
$now = new DateTimeImmutable("2022-04-22 16:25:11 BST");
var_dump($now->add($e));
var_dump($now->sub($e));
?>
--EXPECTF--
Original object:
object(DateInterval)#1 (10) {
["y"]=>
int(2)
["m"]=>
int(0)
["d"]=>
int(4)
["h"]=>
int(6)
["i"]=>
int(8)
["s"]=>
int(0)
["f"]=>
float(0)
["invert"]=>
int(0)
["days"]=>
bool(false)
["from_string"]=>
bool(false)
}


Serialised object:
string(164) "O:12:"DateInterval":10:{s:1:"y";i:2;s:1:"m";i:0;s:1:"d";i:4;s:1:"h";i:6;s:1:"i";i:8;s:1:"s";i:0;s:1:"f";d:0;s:6:"invert";i:0;s:4:"days";b:0;s:11:"from_string";b:0;}"


Unserialised object:
object(DateInterval)#2 (10) {
["y"]=>
int(2)
["m"]=>
int(0)
["d"]=>
int(4)
["h"]=>
int(6)
["i"]=>
int(8)
["s"]=>
int(0)
["f"]=>
float(0)
["invert"]=>
int(0)
["days"]=>
bool(false)
["from_string"]=>
bool(false)
}


Calling __serialize manually:
array(%d) {
["y"]=>
int(2)
["m"]=>
int(0)
["d"]=>
int(4)
["h"]=>
int(6)
["i"]=>
int(8)
["s"]=>
int(0)
["f"]=>
float(0)
["invert"]=>
int(0)
["days"]=>
bool(false)
["from_string"]=>
bool(false)
}


Used serialised interval:
object(DateTimeImmutable)#4 (3) {
["date"]=>
string(26) "2024-04-26 22:33:11.000000"
["timezone_type"]=>
int(2)
["timezone"]=>
string(3) "BST"
}
object(DateTimeImmutable)#4 (3) {
["date"]=>
string(26) "2020-04-18 10:17:11.000000"
["timezone_type"]=>
int(2)
["timezone"]=>
string(3) "BST"
}
Loading