Skip to content

Commit

Permalink
Reduce memory overhead of DatePeriod via virtual properties (php#15598)
Browse files Browse the repository at this point in the history
Related to php#11644 and php#13988
  • Loading branch information
kocsismate authored and jorgsowa committed Oct 1, 2024
1 parent 188d75a commit ade877b
Show file tree
Hide file tree
Showing 8 changed files with 470 additions and 90 deletions.
150 changes: 101 additions & 49 deletions ext/date/php_date.c
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,12 @@ static int date_interval_compare_objects(zval *o1, zval *o2);
static zval *date_interval_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv);
static zval *date_interval_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot);
static zval *date_interval_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot);
static int date_period_has_property(zend_object *object, zend_string *name, int type, void **cache_slot);
static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv);
static zval *date_period_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot);
static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot);

static void date_period_unset_property(zend_object *object, zend_string *name, void **cache_slot);
static HashTable *date_period_get_properties_for(zend_object *object, zend_prop_purpose purpose);
static int date_object_compare_timezone(zval *tz1, zval *tz2);

/* {{{ Module struct */
Expand Down Expand Up @@ -1505,45 +1507,6 @@ static void create_date_period_interval(timelib_rel_time *interval, zval *zv)
}
}

static void write_date_period_property(zend_object *obj, const char *name, const size_t name_len, zval *zv)
{
zend_string *property_name = zend_string_init(name, name_len, 0);

zend_std_write_property(obj, property_name, zv, NULL);

zval_ptr_dtor(zv);
zend_string_release(property_name);
}

static void initialize_date_period_properties(php_period_obj *period_obj)
{
zval zv;

/* rebuild properties */
zend_std_get_properties_ex(&period_obj->std);

create_date_period_datetime(period_obj->start, period_obj->start_ce, &zv);
write_date_period_property(&period_obj->std, "start", sizeof("start") - 1, &zv);

create_date_period_datetime(period_obj->current, period_obj->start_ce, &zv);
write_date_period_property(&period_obj->std, "current", sizeof("current") - 1, &zv);

create_date_period_datetime(period_obj->end, period_obj->start_ce, &zv);
write_date_period_property(&period_obj->std, "end", sizeof("end") - 1, &zv);

create_date_period_interval(period_obj->interval, &zv);
write_date_period_property(&period_obj->std, "interval", sizeof("interval") - 1, &zv);

ZVAL_LONG(&zv, (zend_long) period_obj->recurrences);
write_date_period_property(&period_obj->std, "recurrences", sizeof("recurrences") - 1, &zv);

ZVAL_BOOL(&zv, period_obj->include_start_date);
write_date_period_property(&period_obj->std, "include_start_date", sizeof("include_start_date") - 1, &zv);

ZVAL_BOOL(&zv, period_obj->include_end_date);
write_date_period_property(&period_obj->std, "include_end_date", sizeof("include_end_date") - 1, &zv);
}

/* define an overloaded iterator structure */
typedef struct {
zend_object_iterator intern;
Expand Down Expand Up @@ -1663,10 +1626,7 @@ static void date_period_it_move_forward(zend_object_iterator *iter)
zend_std_get_properties_ex(&object->std);

create_date_period_datetime(object->current, object->start_ce, &current_zv);
zend_string *property_name = ZSTR_INIT_LITERAL("current", 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 @@ -1837,8 +1797,11 @@ static void date_register_classes(void) /* {{{ */
date_object_handlers_period.clone_obj = date_object_clone_period;
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.has_property = date_period_has_property;
date_object_handlers_period.read_property = date_period_read_property;
date_object_handlers_period.write_property = date_period_write_property;
date_object_handlers_period.get_properties_for = date_period_get_properties_for;
date_object_handlers_period.unset_property = date_period_unset_property;

date_ce_date_error = register_class_DateError(zend_ce_error);
date_ce_date_object_error = register_class_DateObjectError(date_ce_date_error);
Expand Down Expand Up @@ -5138,8 +5101,6 @@ static bool date_period_init_finish(php_period_obj *dpobj, zend_long options, ze

dpobj->initialized = 1;

initialize_date_period_properties(dpobj);

return true;
}

Expand Down Expand Up @@ -5843,8 +5804,6 @@ 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 @@ -5964,14 +5923,84 @@ PHP_METHOD(DatePeriod, __wakeup)
zend_throw_error(NULL, "Invalid serialization data for DatePeriod object");
RETURN_THROWS();
}

restore_custom_dateperiod_properties(object, myht);
}
/* }}} */

static int date_period_has_property(zend_object *object, zend_string *name, int type, void **cache_slot)
{
zval rv;
zval *prop;

if (!date_period_is_internal_property(name)) {
return zend_std_has_property(object, name, type, cache_slot);
}

php_period_obj *period_obj = php_period_obj_from_obj(object);
if (!period_obj->initialized) {
switch (type) {
case ZEND_PROPERTY_ISSET: /* Intentional fallthrough */
case ZEND_PROPERTY_NOT_EMPTY:
return 0;
case ZEND_PROPERTY_EXISTS:
return 1;
EMPTY_SWITCH_DEFAULT_CASE()
}
}

if (type == ZEND_PROPERTY_EXISTS) {
return 1;
}

prop = date_period_read_property(object, name, BP_VAR_IS, cache_slot, &rv);
ZEND_ASSERT(prop != &EG(uninitialized_zval));

bool result;

if (type == ZEND_PROPERTY_NOT_EMPTY) {
result = zend_is_true(prop);
} else if (type == ZEND_PROPERTY_ISSET) {
result = Z_TYPE_P(prop) != IS_NULL;
} else {
ZEND_UNREACHABLE();
}

zval_ptr_dtor(prop);

return result;
}

/* {{{ date_period_read_property */
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_internal_property(name)) {
if (date_period_is_internal_property(name)) {
if (type == BP_VAR_IS || type == BP_VAR_R) {
php_period_obj *period_obj = php_period_obj_from_obj(object);

if (zend_string_equals_literal(name, "start")) {
create_date_period_datetime(period_obj->start, period_obj->start_ce, rv);
return rv;
} else if (zend_string_equals_literal(name, "current")) {
create_date_period_datetime(period_obj->current, period_obj->start_ce, rv);
return rv;
} else if (zend_string_equals_literal(name, "end")) {
create_date_period_datetime(period_obj->end, period_obj->start_ce, rv);
return rv;
} else if (zend_string_equals_literal(name, "interval")) {
create_date_period_interval(period_obj->interval, rv);
return rv;
} else if (zend_string_equals_literal(name, "recurrences")) {
ZVAL_LONG(rv, period_obj->recurrences);
return rv;
} else if (zend_string_equals_literal(name, "include_start_date")) {
ZVAL_BOOL(rv, period_obj->include_start_date);
return rv;
} else if (zend_string_equals_literal(name, "include_end_date")) {
ZVAL_BOOL(rv, period_obj->include_end_date);
return rv;
}
} else {
zend_readonly_property_modification_error_ex("DatePeriod", ZSTR_VAL(name));
return &EG(uninitialized_zval);
}
Expand Down Expand Up @@ -6000,3 +6029,26 @@ static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *

return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
}

static HashTable *date_period_get_properties_for(zend_object *object, zend_prop_purpose purpose)
{
php_period_obj *period_obj = php_period_obj_from_obj(object);
HashTable *props = zend_array_dup(zend_std_get_properties(object));
if (!period_obj->initialized) {
return props;
}

date_period_object_to_hash(period_obj, props);

return props;
}

static void date_period_unset_property(zend_object *object, zend_string *name, void **cache_slot)
{
if (date_period_is_internal_property(name)) {
zend_throw_error(NULL, "Cannot unset %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(name));
return;
}

zend_std_unset_property(object, name, cache_slot);
}
35 changes: 28 additions & 7 deletions ext/date/php_date.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -673,19 +673,40 @@ class DatePeriod implements IteratorAggregate
/** @cvalue PHP_DATE_PERIOD_INCLUDE_END_DATE */
public const int INCLUDE_END_DATE = UNKNOWN;

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

public static function createFromISO8601String(string $specification, int $options = 0): static {}
Expand Down
16 changes: 8 additions & 8 deletions ext/date/php_date_arginfo.h

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

42 changes: 42 additions & 0 deletions ext/date/tests/date_period_is_property.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
Test isset on DatePeriod instantiated without its constructor
--FILE--
<?php

class MyDatePeriod extends DatePeriod {
public int $my;
}

$rc = new ReflectionClass('MyDatePeriod');
$di = $rc->newInstanceWithoutConstructor();

var_dump(isset($di->start));
var_dump(empty($di->start));
var_dump(property_exists($di, "start"));

var_dump(isset($di->recurrences));
var_dump(empty($di->recurrences));
var_dump(property_exists($di, "recurrences"));

var_dump(isset($di->end));
var_dump(empty($di->end));
var_dump(property_exists($di, "end"));

var_dump(isset($di->my));
var_dump(empty($di->my));
var_dump(property_exists($di, "my"));

?>
--EXPECT--
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
Loading

0 comments on commit ade877b

Please sign in to comment.