Skip to content

Commit 181ea64

Browse files
authored
Reduce memory overhead of DatePeriod via virtual properties (#15598)
Related to #11644 and #13988
1 parent 8b0933b commit 181ea64

8 files changed

+470
-90
lines changed

ext/date/php_date.c

Lines changed: 101 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,12 @@ static int date_interval_compare_objects(zval *o1, zval *o2);
360360
static zval *date_interval_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv);
361361
static zval *date_interval_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot);
362362
static zval *date_interval_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot);
363+
static int date_period_has_property(zend_object *object, zend_string *name, int type, void **cache_slot);
363364
static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv);
364365
static zval *date_period_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot);
365366
static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot);
366-
367+
static void date_period_unset_property(zend_object *object, zend_string *name, void **cache_slot);
368+
static HashTable *date_period_get_properties_for(zend_object *object, zend_prop_purpose purpose);
367369
static int date_object_compare_timezone(zval *tz1, zval *tz2);
368370

369371
/* {{{ Module struct */
@@ -1505,45 +1507,6 @@ static void create_date_period_interval(timelib_rel_time *interval, zval *zv)
15051507
}
15061508
}
15071509

1508-
static void write_date_period_property(zend_object *obj, const char *name, const size_t name_len, zval *zv)
1509-
{
1510-
zend_string *property_name = zend_string_init(name, name_len, 0);
1511-
1512-
zend_std_write_property(obj, property_name, zv, NULL);
1513-
1514-
zval_ptr_dtor(zv);
1515-
zend_string_release(property_name);
1516-
}
1517-
1518-
static void initialize_date_period_properties(php_period_obj *period_obj)
1519-
{
1520-
zval zv;
1521-
1522-
/* rebuild properties */
1523-
zend_std_get_properties_ex(&period_obj->std);
1524-
1525-
create_date_period_datetime(period_obj->start, period_obj->start_ce, &zv);
1526-
write_date_period_property(&period_obj->std, "start", sizeof("start") - 1, &zv);
1527-
1528-
create_date_period_datetime(period_obj->current, period_obj->start_ce, &zv);
1529-
write_date_period_property(&period_obj->std, "current", sizeof("current") - 1, &zv);
1530-
1531-
create_date_period_datetime(period_obj->end, period_obj->start_ce, &zv);
1532-
write_date_period_property(&period_obj->std, "end", sizeof("end") - 1, &zv);
1533-
1534-
create_date_period_interval(period_obj->interval, &zv);
1535-
write_date_period_property(&period_obj->std, "interval", sizeof("interval") - 1, &zv);
1536-
1537-
ZVAL_LONG(&zv, (zend_long) period_obj->recurrences);
1538-
write_date_period_property(&period_obj->std, "recurrences", sizeof("recurrences") - 1, &zv);
1539-
1540-
ZVAL_BOOL(&zv, period_obj->include_start_date);
1541-
write_date_period_property(&period_obj->std, "include_start_date", sizeof("include_start_date") - 1, &zv);
1542-
1543-
ZVAL_BOOL(&zv, period_obj->include_end_date);
1544-
write_date_period_property(&period_obj->std, "include_end_date", sizeof("include_end_date") - 1, &zv);
1545-
}
1546-
15471510
/* define an overloaded iterator structure */
15481511
typedef struct {
15491512
zend_object_iterator intern;
@@ -1663,10 +1626,7 @@ static void date_period_it_move_forward(zend_object_iterator *iter)
16631626
zend_std_get_properties_ex(&object->std);
16641627

16651628
create_date_period_datetime(object->current, object->start_ce, &current_zv);
1666-
zend_string *property_name = ZSTR_INIT_LITERAL("current", 0);
1667-
zend_std_write_property(&object->std, property_name, &current_zv, NULL);
16681629
zval_ptr_dtor(&current_zv);
1669-
zend_string_release(property_name);
16701630

16711631
iterator->current_index++;
16721632
date_period_it_invalidate_current(iter);
@@ -1837,8 +1797,11 @@ static void date_register_classes(void) /* {{{ */
18371797
date_object_handlers_period.clone_obj = date_object_clone_period;
18381798
date_object_handlers_period.get_gc = date_object_get_gc_period;
18391799
date_object_handlers_period.get_property_ptr_ptr = date_period_get_property_ptr_ptr;
1800+
date_object_handlers_period.has_property = date_period_has_property;
18401801
date_object_handlers_period.read_property = date_period_read_property;
18411802
date_object_handlers_period.write_property = date_period_write_property;
1803+
date_object_handlers_period.get_properties_for = date_period_get_properties_for;
1804+
date_object_handlers_period.unset_property = date_period_unset_property;
18421805

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

51395102
dpobj->initialized = 1;
51405103

5141-
initialize_date_period_properties(dpobj);
5142-
51435104
return true;
51445105
}
51455106

@@ -5843,8 +5804,6 @@ static bool php_date_period_initialize_from_hash(php_period_obj *period_obj, Has
58435804

58445805
period_obj->initialized = 1;
58455806

5846-
initialize_date_period_properties(period_obj);
5847-
58485807
return 1;
58495808
} /* }}} */
58505809

@@ -5964,14 +5923,84 @@ PHP_METHOD(DatePeriod, __wakeup)
59645923
zend_throw_error(NULL, "Invalid serialization data for DatePeriod object");
59655924
RETURN_THROWS();
59665925
}
5926+
5927+
restore_custom_dateperiod_properties(object, myht);
59675928
}
59685929
/* }}} */
59695930

5931+
static int date_period_has_property(zend_object *object, zend_string *name, int type, void **cache_slot)
5932+
{
5933+
zval rv;
5934+
zval *prop;
5935+
5936+
if (!date_period_is_internal_property(name)) {
5937+
return zend_std_has_property(object, name, type, cache_slot);
5938+
}
5939+
5940+
php_period_obj *period_obj = php_period_obj_from_obj(object);
5941+
if (!period_obj->initialized) {
5942+
switch (type) {
5943+
case ZEND_PROPERTY_ISSET: /* Intentional fallthrough */
5944+
case ZEND_PROPERTY_NOT_EMPTY:
5945+
return 0;
5946+
case ZEND_PROPERTY_EXISTS:
5947+
return 1;
5948+
EMPTY_SWITCH_DEFAULT_CASE()
5949+
}
5950+
}
5951+
5952+
if (type == ZEND_PROPERTY_EXISTS) {
5953+
return 1;
5954+
}
5955+
5956+
prop = date_period_read_property(object, name, BP_VAR_IS, cache_slot, &rv);
5957+
ZEND_ASSERT(prop != &EG(uninitialized_zval));
5958+
5959+
bool result;
5960+
5961+
if (type == ZEND_PROPERTY_NOT_EMPTY) {
5962+
result = zend_is_true(prop);
5963+
} else if (type == ZEND_PROPERTY_ISSET) {
5964+
result = Z_TYPE_P(prop) != IS_NULL;
5965+
} else {
5966+
ZEND_UNREACHABLE();
5967+
}
5968+
5969+
zval_ptr_dtor(prop);
5970+
5971+
return result;
5972+
}
5973+
59705974
/* {{{ date_period_read_property */
59715975
static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
59725976
{
5973-
if (type != BP_VAR_IS && type != BP_VAR_R) {
5974-
if (date_period_is_internal_property(name)) {
5977+
if (date_period_is_internal_property(name)) {
5978+
if (type == BP_VAR_IS || type == BP_VAR_R) {
5979+
php_period_obj *period_obj = php_period_obj_from_obj(object);
5980+
5981+
if (zend_string_equals_literal(name, "start")) {
5982+
create_date_period_datetime(period_obj->start, period_obj->start_ce, rv);
5983+
return rv;
5984+
} else if (zend_string_equals_literal(name, "current")) {
5985+
create_date_period_datetime(period_obj->current, period_obj->start_ce, rv);
5986+
return rv;
5987+
} else if (zend_string_equals_literal(name, "end")) {
5988+
create_date_period_datetime(period_obj->end, period_obj->start_ce, rv);
5989+
return rv;
5990+
} else if (zend_string_equals_literal(name, "interval")) {
5991+
create_date_period_interval(period_obj->interval, rv);
5992+
return rv;
5993+
} else if (zend_string_equals_literal(name, "recurrences")) {
5994+
ZVAL_LONG(rv, period_obj->recurrences);
5995+
return rv;
5996+
} else if (zend_string_equals_literal(name, "include_start_date")) {
5997+
ZVAL_BOOL(rv, period_obj->include_start_date);
5998+
return rv;
5999+
} else if (zend_string_equals_literal(name, "include_end_date")) {
6000+
ZVAL_BOOL(rv, period_obj->include_end_date);
6001+
return rv;
6002+
}
6003+
} else {
59756004
zend_readonly_property_modification_error_ex("DatePeriod", ZSTR_VAL(name));
59766005
return &EG(uninitialized_zval);
59776006
}
@@ -6000,3 +6029,26 @@ static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *
60006029

60016030
return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
60026031
}
6032+
6033+
static HashTable *date_period_get_properties_for(zend_object *object, zend_prop_purpose purpose)
6034+
{
6035+
php_period_obj *period_obj = php_period_obj_from_obj(object);
6036+
HashTable *props = zend_array_dup(zend_std_get_properties(object));
6037+
if (!period_obj->initialized) {
6038+
return props;
6039+
}
6040+
6041+
date_period_object_to_hash(period_obj, props);
6042+
6043+
return props;
6044+
}
6045+
6046+
static void date_period_unset_property(zend_object *object, zend_string *name, void **cache_slot)
6047+
{
6048+
if (date_period_is_internal_property(name)) {
6049+
zend_throw_error(NULL, "Cannot unset %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(name));
6050+
return;
6051+
}
6052+
6053+
zend_std_unset_property(object, name, cache_slot);
6054+
}

ext/date/php_date.stub.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -673,19 +673,40 @@ class DatePeriod implements IteratorAggregate
673673
/** @cvalue PHP_DATE_PERIOD_INCLUDE_END_DATE */
674674
public const int INCLUDE_END_DATE = UNKNOWN;
675675

676-
/** @readonly */
676+
/**
677+
* @readonly
678+
* @virtual
679+
*/
677680
public ?DateTimeInterface $start;
678-
/** @readonly */
681+
/**
682+
* @readonly
683+
* @virtual
684+
*/
679685
public ?DateTimeInterface $current;
680-
/** @readonly */
686+
/**
687+
* @readonly
688+
* @virtual
689+
*/
681690
public ?DateTimeInterface $end;
682-
/** @readonly */
691+
/**
692+
* @readonly
693+
* @virtual
694+
*/
683695
public ?DateInterval $interval;
684-
/** @readonly */
696+
/**
697+
* @readonly
698+
* @virtual
699+
*/
685700
public int $recurrences;
686-
/** @readonly */
701+
/**
702+
* @readonly
703+
* @virtual
704+
*/
687705
public bool $include_start_date;
688-
/** @readonly */
706+
/**
707+
* @readonly
708+
* @virtual
709+
*/
689710
public bool $include_end_date;
690711

691712
public static function createFromISO8601String(string $specification, int $options = 0): static {}

ext/date/php_date_arginfo.h

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Test isset on DatePeriod instantiated without its constructor
3+
--FILE--
4+
<?php
5+
6+
class MyDatePeriod extends DatePeriod {
7+
public int $my;
8+
}
9+
10+
$rc = new ReflectionClass('MyDatePeriod');
11+
$di = $rc->newInstanceWithoutConstructor();
12+
13+
var_dump(isset($di->start));
14+
var_dump(empty($di->start));
15+
var_dump(property_exists($di, "start"));
16+
17+
var_dump(isset($di->recurrences));
18+
var_dump(empty($di->recurrences));
19+
var_dump(property_exists($di, "recurrences"));
20+
21+
var_dump(isset($di->end));
22+
var_dump(empty($di->end));
23+
var_dump(property_exists($di, "end"));
24+
25+
var_dump(isset($di->my));
26+
var_dump(empty($di->my));
27+
var_dump(property_exists($di, "my"));
28+
29+
?>
30+
--EXPECT--
31+
bool(false)
32+
bool(true)
33+
bool(true)
34+
bool(false)
35+
bool(true)
36+
bool(true)
37+
bool(false)
38+
bool(true)
39+
bool(true)
40+
bool(false)
41+
bool(true)
42+
bool(true)

0 commit comments

Comments
 (0)