Skip to content

Declare DatePeriod properties #8534

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 4 commits into from
May 28, 2022
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
154 changes: 87 additions & 67 deletions ext/date/php_date.c
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_
static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n);
static HashTable *date_object_get_properties_interval(zend_object *object);
static HashTable *date_object_get_gc_period(zend_object *object, zval **table, int *n);
static HashTable *date_object_get_properties_period(zend_object *object);
static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose);
static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, int *n);
static HashTable *date_object_get_debug_info_timezone(zend_object *object, int *is_temp);
Expand Down Expand Up @@ -1396,6 +1395,64 @@ PHP_FUNCTION(getdate)
}
/* }}} */

static void create_date_period_datetime(timelib_time *datetime, zend_class_entry *ce, zval *zv)
{
if (datetime) {
php_date_obj *date_obj;

object_init_ex(zv, ce);
date_obj = Z_PHPDATE_P(zv);
date_obj->time = timelib_time_clone(datetime);
} else {
ZVAL_NULL(zv);
}
}

static void create_date_period_interval(timelib_rel_time *interval, zval *zv)
{
if (interval) {
php_interval_obj *interval_obj;

object_init_ex(zv, date_ce_interval);
interval_obj = Z_PHPINTERVAL_P(zv);
interval_obj->diff = timelib_rel_time_clone(interval);
interval_obj->initialized = 1;
} else {
ZVAL_NULL(zv);
}
}

static void initialize_date_period_properties(php_period_obj *period_obj)
{
zval start_zv, current_zv, end_zv, interval_zv;

if (UNEXPECTED(!period_obj->std.properties)) {
rebuild_object_properties(&period_obj->std);
}

create_date_period_datetime(period_obj->start, period_obj->start_ce, &start_zv);
zend_update_property(date_ce_period, &period_obj->std, "start", sizeof("start") - 1, &start_zv);
zval_ptr_dtor(&start_zv);

create_date_period_datetime(period_obj->current, period_obj->start_ce, &current_zv);
zend_string *property_name = zend_string_init("current", sizeof("current") - 1, 0);
zend_std_write_property(&period_obj->std, property_name, &current_zv, NULL);
zval_ptr_dtor(&current_zv);
zend_string_release(property_name);

create_date_period_datetime(period_obj->end, period_obj->start_ce, &end_zv);
zend_update_property(date_ce_period, &period_obj->std, "end", sizeof("end") - 1, &end_zv);
zval_ptr_dtor(&end_zv);

create_date_period_interval(period_obj->interval, &interval_zv);
zend_update_property(date_ce_period, &period_obj->std, "interval", sizeof("interval") - 1, &interval_zv);
zval_ptr_dtor(&interval_zv);

zend_update_property_long(date_ce_period, &period_obj->std, "recurrences", sizeof("recurrences") - 1, (zend_long) period_obj->recurrences);
zend_update_property_bool(date_ce_period, &period_obj->std, "include_start_date", sizeof("include_start_date") - 1, period_obj->include_start_date);
zend_update_property_bool(date_ce_period, &period_obj->std, "include_end_date", sizeof("include_end_date") - 1, period_obj->include_end_date);
}

/* define an overloaded iterator structure */
typedef struct {
zend_object_iterator intern;
Expand Down Expand Up @@ -1491,10 +1548,21 @@ static void date_period_it_move_forward(zend_object_iterator *iter)
{
date_period_it *iterator = (date_period_it *)iter;
php_period_obj *object = Z_PHPPERIOD_P(&iterator->intern.data);
timelib_time *it_time = object->current;
timelib_time *it_time = object->current;
zval current_zv;

date_period_advance(it_time, object->interval);

if (UNEXPECTED(!object->std.properties)) {
rebuild_object_properties(&object->std);
}

create_date_period_datetime(object->current, object->start_ce, &current_zv);
zend_string *property_name = zend_string_init("current", sizeof("current") - 1, 0);
zend_std_write_property(&object->std, property_name, &current_zv, NULL);
zval_ptr_dtor(&current_zv);
zend_string_release(property_name);

iterator->current_index++;
date_period_it_invalidate_current(iter);
}
Expand Down Expand Up @@ -1657,9 +1725,8 @@ static void date_register_classes(void) /* {{{ */
date_object_handlers_period.offset = XtOffsetOf(php_period_obj, std);
date_object_handlers_period.free_obj = date_object_free_storage_period;
date_object_handlers_period.clone_obj = date_object_clone_period;
date_object_handlers_period.get_properties = date_object_get_properties_period;
date_object_handlers_period.get_property_ptr_ptr = date_period_get_property_ptr_ptr;
date_object_handlers_period.get_gc = date_object_get_gc_period;
date_object_handlers_period.get_property_ptr_ptr = date_period_get_property_ptr_ptr;
date_object_handlers_period.read_property = date_period_read_property;
date_object_handlers_period.write_property = date_period_write_property;
} /* }}} */
Expand Down Expand Up @@ -4502,6 +4569,8 @@ PHP_METHOD(DatePeriod, __construct)
dpobj->recurrences = recurrences + dpobj->include_start_date + dpobj->include_end_date;

dpobj->initialized = 1;

initialize_date_period_properties(dpobj);
}
/* }}} */

Expand Down Expand Up @@ -4965,45 +5034,16 @@ static void date_period_object_to_hash(php_period_obj *period_obj, HashTable *pr
{
zval zv;

if (period_obj->start) {
php_date_obj *date_obj;
object_init_ex(&zv, period_obj->start_ce);
date_obj = Z_PHPDATE_P(&zv);
date_obj->time = timelib_time_clone(period_obj->start);
} else {
ZVAL_NULL(&zv);
}
create_date_period_datetime(period_obj->start, period_obj->start_ce, &zv);
zend_hash_str_update(props, "start", sizeof("start")-1, &zv);

if (period_obj->current) {
php_date_obj *date_obj;
object_init_ex(&zv, period_obj->start_ce);
date_obj = Z_PHPDATE_P(&zv);
date_obj->time = timelib_time_clone(period_obj->current);
} else {
ZVAL_NULL(&zv);
}
create_date_period_datetime(period_obj->current, period_obj->start_ce, &zv);
zend_hash_str_update(props, "current", sizeof("current")-1, &zv);

if (period_obj->end) {
php_date_obj *date_obj;
object_init_ex(&zv, period_obj->start_ce);
date_obj = Z_PHPDATE_P(&zv);
date_obj->time = timelib_time_clone(period_obj->end);
} else {
ZVAL_NULL(&zv);
}
create_date_period_datetime(period_obj->end, period_obj->start_ce, &zv);
zend_hash_str_update(props, "end", sizeof("end")-1, &zv);

if (period_obj->interval) {
php_interval_obj *interval_obj;
object_init_ex(&zv, date_ce_interval);
interval_obj = Z_PHPINTERVAL_P(&zv);
interval_obj->diff = timelib_rel_time_clone(period_obj->interval);
interval_obj->initialized = 1;
} else {
ZVAL_NULL(&zv);
}
create_date_period_interval(period_obj->interval, &zv);
zend_hash_str_update(props, "interval", sizeof("interval")-1, &zv);

/* converted to larger type (int->long); must check when unserializing */
Expand All @@ -5017,22 +5057,6 @@ static void date_period_object_to_hash(php_period_obj *period_obj, HashTable *pr
zend_hash_str_update(props, "include_end_date", sizeof("include_end_date")-1, &zv);
}

static HashTable *date_object_get_properties_period(zend_object *object) /* {{{ */
{
HashTable *props;
php_period_obj *period_obj;

period_obj = php_period_obj_from_obj(object);
props = zend_std_get_properties(object);
if (!period_obj->start) {
return props;
}

date_period_object_to_hash(period_obj, props);

return props;
} /* }}} */

static bool php_date_period_initialize_from_hash(php_period_obj *period_obj, HashTable *myht) /* {{{ */
{
zval *ht_entry;
Expand Down Expand Up @@ -5118,6 +5142,8 @@ static bool php_date_period_initialize_from_hash(php_period_obj *period_obj, Has

period_obj->initialized = 1;

initialize_date_period_properties(period_obj);

return 1;
} /* }}} */

Expand Down Expand Up @@ -5201,10 +5227,10 @@ PHP_METHOD(DatePeriod, __wakeup)
}
/* }}} */

/* {{{ date_period_is_magic_property
/* {{{ date_period_is_internal_property
* Common for date_period_read_property() and date_period_write_property() functions
*/
static bool date_period_is_magic_property(zend_string *name)
static bool date_period_is_internal_property(zend_string *name)
{
if (zend_string_equals_literal(name, "recurrences")
|| zend_string_equals_literal(name, "include_start_date")
Expand All @@ -5224,38 +5250,32 @@ static bool date_period_is_magic_property(zend_string *name)
static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
{
if (type != BP_VAR_IS && type != BP_VAR_R) {
if (date_period_is_magic_property(name)) {
zend_throw_error(NULL, "Retrieval of DatePeriod->%s for modification is unsupported", ZSTR_VAL(name));
if (date_period_is_internal_property(name)) {
zend_throw_error(NULL, "Cannot modify readonly property DatePeriod::$%s", ZSTR_VAL(name));
return &EG(uninitialized_zval);
}
}

object->handlers->get_properties(object); /* build properties hash table */

return zend_std_read_property(object, name, type, cache_slot, rv);
}
/* }}} */

/* {{{ date_period_write_property */
static zval *date_period_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot)
{
if (date_period_is_magic_property(name)) {
zend_throw_error(NULL, "Writing to DatePeriod->%s is unsupported", ZSTR_VAL(name));
if (zend_string_equals_literal(name, "current")) {
zend_throw_error(NULL, "Cannot modify readonly property DatePeriod::$%s", ZSTR_VAL(name));
return value;
}

return zend_std_write_property(object, name, value, cache_slot);
}
/* }}} */

/* {{{ date_period_get_property_ptr_ptr */
static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
{
if (date_period_is_magic_property(name)) {
zend_throw_error(NULL, "Retrieval of DatePeriod->%s for modification is unsupported", ZSTR_VAL(name));
if (zend_string_equals_literal(name, "current")) {
zend_throw_error(NULL, "Cannot modify readonly property DatePeriod::$%s", ZSTR_VAL(name));
return &EG(error_zval);
}

return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
}
/* }}} */
9 changes: 9 additions & 0 deletions ext/date/php_date.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,15 @@ class DatePeriod implements IteratorAggregate
*/
public const INCLUDE_END_DATE = UNKNOWN;

public readonly ?DateTimeInterface $start;
/** @readonly */
public ?DateTimeInterface $current;
public readonly ?DateTimeInterface $end;
public readonly ?DateInterval $interval;
public readonly int $recurrences;
public readonly bool $include_start_date;
public readonly bool $include_end_date;

/**
* @param DateTimeInterface|string $start
* @param DateInterval|int $interval
Expand Down
48 changes: 47 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: 2cdf90d626d020b7e746cbccdb87833720d87132 */
* Stub hash: 7ef8c504e35ca49f2c7442bd572b196587a36b7b */

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 @@ -1026,5 +1026,51 @@ static zend_class_entry *register_class_DatePeriod(zend_class_entry *class_entry
zend_declare_class_constant_ex(class_entry, const_INCLUDE_END_DATE_name, &const_INCLUDE_END_DATE_value, ZEND_ACC_PUBLIC, NULL);
zend_string_release(const_INCLUDE_END_DATE_name);

zend_string *property_start_class_DateTimeInterface = zend_string_init("DateTimeInterface", sizeof("DateTimeInterface")-1, 1);
zval property_start_default_value;
ZVAL_UNDEF(&property_start_default_value);
zend_string *property_start_name = zend_string_init("start", sizeof("start") - 1, 1);
zend_declare_typed_property(class_entry, property_start_name, &property_start_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_start_class_DateTimeInterface, 0, MAY_BE_NULL));
zend_string_release(property_start_name);

zend_string *property_current_class_DateTimeInterface = zend_string_init("DateTimeInterface", sizeof("DateTimeInterface")-1, 1);
zval property_current_default_value;
ZVAL_UNDEF(&property_current_default_value);
zend_string *property_current_name = zend_string_init("current", sizeof("current") - 1, 1);
zend_declare_typed_property(class_entry, property_current_name, &property_current_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_current_class_DateTimeInterface, 0, MAY_BE_NULL));
zend_string_release(property_current_name);

zend_string *property_end_class_DateTimeInterface = zend_string_init("DateTimeInterface", sizeof("DateTimeInterface")-1, 1);
zval property_end_default_value;
ZVAL_UNDEF(&property_end_default_value);
zend_string *property_end_name = zend_string_init("end", sizeof("end") - 1, 1);
zend_declare_typed_property(class_entry, property_end_name, &property_end_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_end_class_DateTimeInterface, 0, MAY_BE_NULL));
zend_string_release(property_end_name);

zend_string *property_interval_class_DateInterval = zend_string_init("DateInterval", sizeof("DateInterval")-1, 1);
zval property_interval_default_value;
ZVAL_UNDEF(&property_interval_default_value);
zend_string *property_interval_name = zend_string_init("interval", sizeof("interval") - 1, 1);
zend_declare_typed_property(class_entry, property_interval_name, &property_interval_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_interval_class_DateInterval, 0, MAY_BE_NULL));
zend_string_release(property_interval_name);

zval property_recurrences_default_value;
ZVAL_UNDEF(&property_recurrences_default_value);
zend_string *property_recurrences_name = zend_string_init("recurrences", sizeof("recurrences") - 1, 1);
zend_declare_typed_property(class_entry, property_recurrences_name, &property_recurrences_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(property_recurrences_name);

zval property_include_start_date_default_value;
ZVAL_UNDEF(&property_include_start_date_default_value);
zend_string *property_include_start_date_name = zend_string_init("include_start_date", sizeof("include_start_date") - 1, 1);
zend_declare_typed_property(class_entry, property_include_start_date_name, &property_include_start_date_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL));
zend_string_release(property_include_start_date_name);

zval property_include_end_date_default_value;
ZVAL_UNDEF(&property_include_end_date_default_value);
zend_string *property_include_end_date_name = zend_string_init("include_end_date", sizeof("include_end_date") - 1, 1);
zend_declare_typed_property(class_entry, property_include_end_date_name, &property_include_end_date_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL));
zend_string_release(property_include_end_date_name);

return class_entry;
}
30 changes: 18 additions & 12 deletions ext/date/tests/DatePeriod_properties2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ foreach ($properties as $property) {
}
}

try {
$period->start->modify("+1 hour");
} catch (Error $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECT--
Writing to DatePeriod->recurrences is unsupported
Retrieval of DatePeriod->recurrences for modification is unsupported
Writing to DatePeriod->include_start_date is unsupported
Retrieval of DatePeriod->include_start_date for modification is unsupported
Writing to DatePeriod->start is unsupported
Retrieval of DatePeriod->start for modification is unsupported
Writing to DatePeriod->current is unsupported
Retrieval of DatePeriod->current for modification is unsupported
Writing to DatePeriod->end is unsupported
Retrieval of DatePeriod->end for modification is unsupported
Writing to DatePeriod->interval is unsupported
Retrieval of DatePeriod->interval for modification is unsupported
Cannot modify readonly property DatePeriod::$recurrences
Cannot modify readonly property DatePeriod::$recurrences
Cannot modify readonly property DatePeriod::$include_start_date
Cannot modify readonly property DatePeriod::$include_start_date
Cannot modify readonly property DatePeriod::$start
Cannot modify readonly property DatePeriod::$start
Cannot modify readonly property DatePeriod::$current
Cannot modify readonly property DatePeriod::$current
Cannot modify readonly property DatePeriod::$end
Cannot modify readonly property DatePeriod::$end
Cannot modify readonly property DatePeriod::$interval
Cannot modify readonly property DatePeriod::$interval
2 changes: 1 addition & 1 deletion ext/opcache/tests/jit/bug80634.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows
$v = new SomeClass(5);
?>
--EXPECTF--
Fatal error: Uncaught Error: Writing to DatePeriod->interval is unsupported in %spreload_bug80634.inc:7
Fatal error: Uncaught Error: Cannot modify readonly property DatePeriod::$current in %spreload_bug80634.inc:7
Stack trace:
#0 %sbug80634.php(2): SomeClass->__construct(5)
#1 {main}
Expand Down
8 changes: 4 additions & 4 deletions ext/opcache/tests/jit/preload_bug80634.inc
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php
class SomeClass extends \DatePeriod {
public $interval;
public ?DateTimeInterface $current;

public function __construct(int $v) {
parent::__construct(new \DateTime('2020-12-31'), new \DateInterval("P1Y"), 1);
$this->interval = $v;
var_dump($this->interval);
$this->current = $v;
var_dump($this->current);
}
}
}