diff --git a/php_timecop.h b/php_timecop.h index 97f00eb..7781e20 100644 --- a/php_timecop.h +++ b/php_timecop.h @@ -36,6 +36,9 @@ extern zend_module_entry timecop_module_entry; #include "TSRM.h" #endif +#include +#include "Zend/zend_interfaces.h" + PHP_MINIT_FUNCTION(timecop); PHP_MSHUTDOWN_FUNCTION(timecop); PHP_RINIT_FUNCTION(timecop); @@ -52,6 +55,8 @@ PHP_FUNCTION(timecop_strtotime); PHP_FUNCTION(timecop_strftime); PHP_FUNCTION(timecop_gmstrftime); +PHP_METHOD(TimecopDateTime, __construct); + typedef enum timecap_mode_t { TIMECAP_MODE_NORMAL, TIMECAP_MODE_FREEZE, @@ -63,12 +68,14 @@ ZEND_BEGIN_MODULE_GLOBALS(timecop) timecap_mode_t timecap_mode; long freezed_timestamp; long travel_offset; + zend_class_entry *ce_DateTime; + zend_class_entry *ce_TimecopDateTime; ZEND_END_MODULE_GLOBALS(timecop) struct timecop_overload_def { - char *orig_func; - char *ovld_func; - char *save_func; + char *orig_name; + char *ovld_name; + char *save_name; }; /* In every utility function you add that needs to use variables diff --git a/tests/overload07.phpt b/tests/overload07.phpt new file mode 100644 index 0000000..4f9dbd4 --- /dev/null +++ b/tests/overload07.phpt @@ -0,0 +1,26 @@ +--TEST-- +Class overloading test for datetime +--SKIPIF-- +format("Y-m-d H:i:s")); +--EXPECT-- +string(19) "2012-02-29 01:23:45" diff --git a/timecop.c b/timecop.c index 1427ae2..ab5cd5b 100644 --- a/timecop.c +++ b/timecop.c @@ -26,15 +26,21 @@ #include "php_ini.h" #include "ext/standard/info.h" #include "php_timecop.h" -#include ZEND_DECLARE_MODULE_GLOBALS(timecop) -/* True global resources - no need for thread safety here */ -static int le_timecop; +/* declare the class handlers */ +/* +static zend_object_handlers timecop_datetime_object_handlers; +*/ + +/* decalre the class entry */ +/* +static zend_class_entry *timecop_datetime_ce; +*/ -/* {{{ timecop_overload_def mb_ovld[] */ -static const struct timecop_overload_def timecop_ovld[] = { +/* {{{ timecop_overload_def mb_ovld_func[] */ +static const struct timecop_overload_def timecop_ovld_func[] = { {"time", "timecop_time", "timecop_orig_time"}, {"date", "timecop_date", "timecop_orig_date"}, {"gmdate", "timecop_gmdate", "timecop_orig_gmdate"}, @@ -45,6 +51,13 @@ static const struct timecop_overload_def timecop_ovld[] = { }; /* }}} */ +/* {{{ timecop_overload_def mb_ovld_class[] */ +static const struct timecop_overload_def timecop_ovld_class[] = { + {"datetime", "timecopdatetime", "timecoporigdatetime"}, + {NULL, NULL, NULL} +}; +/* }}} */ + /* {{{ timecop_functions[] */ const zend_function_entry timecop_functions[] = { PHP_FE(timecop_freeze, NULL) @@ -60,6 +73,30 @@ const zend_function_entry timecop_functions[] = { }; /* }}} */ +/* declare method parameters, */ +static ZEND_BEGIN_ARG_INFO(arginfo_timecop_datetime_create, 0) + ZEND_ARG_INFO(0, time) + ZEND_ARG_INFO(0, object) +ZEND_END_ARG_INFO(); + +/* each method can have its own parameters and visibility */ +static zend_function_entry timecop_datetime_class_functions[] = { + PHP_ME(TimecopDateTime, __construct, arginfo_timecop_datetime_create, + ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + +static int init_timecop_datetime(); +static int timecop_func_overload(); +static int timecop_class_overload(); +static int timecop_func_overload_clear(); +static int timecop_class_overload_clear(); + +static long _timecop_current_timestamp(); +static void call_callable_with_optional_timestamp(INTERNAL_FUNCTION_PARAMETERS, zval* callable, int num_required_func_args); +static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, char* orig_func_name, char* saved_func_name, int num_required_func_args); +static void _timecop_call_constructor(INTERNAL_FUNCTION_PARAMETERS, zval **object_pp, zend_class_entry *obj_ce); + /* {{{ timecop_module_entry */ zend_module_entry timecop_module_entry = { @@ -113,31 +150,18 @@ PHP_MSHUTDOWN_FUNCTION(timecop) /* {{{ PHP_RINIT_FUNCTION(timecop) */ PHP_RINIT_FUNCTION(timecop) { - zend_function *func, *orig; - const struct timecop_overload_def *p; - if (TIMECOP_G(func_overload)){ - p = &(timecop_ovld[0]); - while (p->orig_func != NULL) { - if (zend_hash_find(EG(function_table), p->save_func, - strlen(p->save_func)+1, (void **)&orig) != SUCCESS) { - zend_hash_find(EG(function_table), p->ovld_func, strlen(p->ovld_func)+1 , - (void **)&func); - if (zend_hash_find(EG(function_table), p->orig_func, strlen(p->orig_func)+ - 1, (void **)&orig) != SUCCESS) { - php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't find function %s.", p->orig_func); - return FAILURE; - } else { - zend_hash_add(EG(function_table), p->save_func, strlen(p->save_func)+1, orig, sizeof(zend_function), NULL); - if (zend_hash_update(EG(function_table), p->orig_func, strlen(p->orig_func)+1, func, sizeof(zend_function), - NULL) == FAILURE) { - php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't replace function %s.", p->orig_func); - return FAILURE; - } - } - } - p++; + int ret; + zend_class_entry *parent_ce; + + init_timecop_datetime(); + + if (TIMECOP_G(func_overload)) { + if (SUCCESS != timecop_func_overload() || + SUCCESS != timecop_class_overload()) { + return FAILURE; } } + return SUCCESS; } /* }}} */ @@ -145,21 +169,9 @@ PHP_RINIT_FUNCTION(timecop) /* {{{ PHP_RSHUTDOWN_FUNCTION(timecop) */ PHP_RSHUTDOWN_FUNCTION(timecop) { - zend_function *func, *orig; - const struct timecop_overload_def *p; - - /* clear overloaded function. */ if (TIMECOP_G(func_overload)) { - p = &(timecop_ovld[0]); - while (p->orig_func != NULL) { - if (zend_hash_find(EG(function_table), p->save_func, - strlen(p->save_func)+1, (void **)&orig) == SUCCESS) { - zend_hash_update(EG(function_table), p->orig_func, strlen(p->orig_func)+1, - orig, sizeof(zend_function), NULL); - zend_hash_del(EG(function_table), p->save_func, strlen(p->save_func)+1); - } - p++; - } + timecop_func_overload_clear(); + timecop_class_overload_clear(); } TIMECOP_G(timecap_mode) = TIMECAP_MODE_NORMAL; @@ -177,12 +189,143 @@ PHP_MINFO_FUNCTION(timecop) php_info_print_table_header(2, "timecop", "enabled"); php_info_print_table_end(); - /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); - */ } /* }}} */ +static int init_timecop_datetime() +{ + zend_class_entry **pce; + zend_class_entry ce; + zend_class_entry *self_ce, *parent_ce; + + if (zend_hash_find(CG(class_table), "datetime", sizeof("datetime"), (void **) &pce) == FAILURE) { + return SUCCESS; /* DateTime must be initialized before */ + } + parent_ce = *pce; + + INIT_CLASS_ENTRY(ce, "TimecopDateTime", timecop_datetime_class_functions); + self_ce = zend_register_internal_class_ex(&ce, parent_ce, NULL TSRMLS_CC); + self_ce->create_object = parent_ce->create_object; + + TIMECOP_G(ce_DateTime) = parent_ce; + TIMECOP_G(ce_TimecopDateTime) = self_ce; + + return SUCCESS; +} + +static int timecop_func_overload() +{ + zend_function *func, *orig; + const struct timecop_overload_def *p; + + p = &(timecop_ovld_func[0]); + while (p->orig_name != NULL) { + if (zend_hash_find(EG(function_table), p->save_name, strlen(p->save_name)+1, + (void **)&orig) == SUCCESS) { + continue; + } + if (zend_hash_find(EG(function_table), p->ovld_name, strlen(p->ovld_name)+1 , + (void **)&func) != SUCCESS) { + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't find function %s.", p->ovld_name); + } + if (zend_hash_find(EG(function_table), p->orig_name, strlen(p->orig_name)+1, + (void **)&orig) != SUCCESS) { + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't find function %s.", p->orig_name); + return FAILURE; + } + zend_hash_add(EG(function_table), p->save_name, strlen(p->save_name)+1, + orig, sizeof(zend_function), NULL); + if (zend_hash_update(EG(function_table), p->orig_name, strlen(p->orig_name)+1, + func, sizeof(zend_function), NULL) == FAILURE) { + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't replace function %s.", p->orig_name); + return FAILURE; + } + p++; + } + return SUCCESS; +} + +static int timecop_class_overload() +{ + zend_class_entry **pce_ovld, **pce_orig, *ce_ovld, *ce_orig; + const struct timecop_overload_def *p; + + p = &(timecop_ovld_class[0]); + while (p->orig_name != NULL) { + if (zend_hash_find(EG(class_table), p->save_name, strlen(p->save_name)+1, + (void **)&pce_orig) == SUCCESS) { + continue; + } + if (zend_hash_find(EG(class_table), p->ovld_name, strlen(p->ovld_name)+1 , + (void **)&pce_ovld) != SUCCESS) { + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't find function %s.", p->ovld_name); + } + if (zend_hash_find(EG(class_table), p->orig_name, strlen(p->orig_name)+1, + (void **)&pce_orig) != SUCCESS) { + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't find function %s.", p->orig_name); + return FAILURE; + } + ce_ovld = *pce_ovld; + ce_orig = *pce_orig; + + ce_orig->refcount++; + zend_hash_add(EG(class_table), p->save_name, strlen(p->save_name)+1, + &ce_orig, sizeof(zend_class_entry *), NULL); + + + ce_ovld->refcount++; + if (zend_hash_update(EG(class_table), p->orig_name, strlen(p->orig_name)+1, + &ce_ovld, sizeof(zend_class_entry *), NULL) == FAILURE) { + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, "timecop couldn't replace function %s.", p->orig_name); + + ce_ovld->refcount--; + return FAILURE; + } + p++; + } + return SUCCESS; +} + +/* clear overloaded function. */ +static int timecop_func_overload_clear() +{ + const struct timecop_overload_def *p; + zend_function *func, *orig; + + p = &(timecop_ovld_func[0]); + while (p->orig_name != NULL) { + if (zend_hash_find(EG(function_table), p->save_name, + strlen(p->save_name)+1, (void **)&orig) == SUCCESS) { + zend_hash_update(EG(function_table), p->orig_name, strlen(p->orig_name)+1, + orig, sizeof(zend_function), NULL); + zend_hash_del(EG(function_table), p->save_name, strlen(p->save_name)+1); + } + p++; + } + return SUCCESS; +} + +static int timecop_class_overload_clear() +{ + const struct timecop_overload_def *p; + zend_class_entry **pce_ovld, **pce_orig, *ce_ovld, *ce_orig; + zend_function *func, *orig; + + p = &(timecop_ovld_class[0]); + while (p->orig_name != NULL) { + if (zend_hash_find(EG(class_table), p->save_name, + strlen(p->save_name)+1, (void **)&pce_orig) == SUCCESS) { + ce_orig = *pce_orig; + zend_hash_update(EG(class_table), p->orig_name, strlen(p->orig_name)+1, + &ce_orig, sizeof(zend_class_entry *), NULL); + zend_hash_del(EG(class_table), p->save_name, strlen(p->save_name)+1); + } + p++; + } + return SUCCESS; +} + static long _timecop_current_timestamp() { zval **array, **request_time_long; @@ -255,6 +398,29 @@ static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, char* orig_func call_callable_with_optional_timestamp(INTERNAL_FUNCTION_PARAM_PASSTHRU, &callable, num_required_func_args); } +static void _timecop_call_constructor(INTERNAL_FUNCTION_PARAMETERS, zval **object_pp, zend_class_entry *obj_ce) +{ + zend_function **fn_proxy = &obj_ce->constructor; + char* method_name = "__constructor"; + zend_uint param_count; + zval ***params; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "*", ¶ms, ¶m_count) == FAILURE) { + return; + } + if (ZEND_NUM_ARGS() == 0) { + zend_call_method_with_0_params(object_pp, obj_ce, &obj_ce->constructor, method_name, NULL); + } else if (ZEND_NUM_ARGS() == 1) { + zend_call_method_with_1_params(object_pp, obj_ce, &obj_ce->constructor, method_name, NULL, *params[0]); + } else if (ZEND_NUM_ARGS() == 2) { + zend_call_method_with_2_params(object_pp, obj_ce, &obj_ce->constructor, method_name, NULL, *params[0], *params[1]); + } else { + zend_error(E_ERROR, "INTERNAL ERROR: too many parameters for method call."); + } + +} + + /* {{{ proto int timecop_freeze(long timestamp) Time travel to specified timestamp and freeze */ PHP_FUNCTION(timecop_freeze) @@ -340,6 +506,26 @@ PHP_FUNCTION(timecop_gmstrftime) } /* }}} */ +/* {{{ proto TimecopDateTime::__construct([string time[, DateTimeZone object]]) + Creates new TimecopDateTime object +*/ +PHP_METHOD(TimecopDateTime, __construct) +{ + zval *tmp; + zend_class_entry *parent_ce; + zval *obj = getThis(); + + /* call DateTime::__constuctor() */ + parent_ce = TIMECOP_G(ce_DateTime); + _timecop_call_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, &obj, parent_ce); + + /* call DateTime::setTimestamp() */ + ALLOC_INIT_ZVAL(tmp); + ZVAL_LONG(tmp, _timecop_current_timestamp()); + zend_call_method_with_1_params(&obj, parent_ce, NULL, "settimestamp", NULL, tmp); +} +/* }}} */ + /* * Local variables: * tab-width: 4