From ec55b3741aaf11c6738fa7a79a3a1701d4bd34ba Mon Sep 17 00:00:00 2001 From: c9s Date: Fri, 16 May 2014 22:47:33 +0800 Subject: [PATCH] Add empty PHP extension skeleton from Pux for --- php/r3/ct_helper.c | 115 ++++ php/r3/ct_helper.h | 8 + php/r3/hash.c | 110 ++++ php/r3/hash.h | 59 ++ php/r3/php_expandable_mux.c | 45 ++ php/r3/php_expandable_mux.h | 21 + php/r3/php_r3.c | 136 ++++ php/r3/php_r3.h | 98 +++ php/r3/r3_controller.c | 437 +++++++++++++ php/r3/r3_controller.h | 35 + php/r3/r3_functions.c | 838 ++++++++++++++++++++++++ php/r3/r3_functions.h | 91 +++ php/r3/r3_mux.c | 1196 +++++++++++++++++++++++++++++++++++ php/r3/r3_mux.h | 64 ++ php/r3/r3_persistent.c | 54 ++ php/r3/r3_persistent.h | 10 + tests/bench_str.csv | 4 + 17 files changed, 3321 insertions(+) create mode 100644 php/r3/ct_helper.c create mode 100644 php/r3/ct_helper.h create mode 100644 php/r3/hash.c create mode 100644 php/r3/hash.h create mode 100644 php/r3/php_expandable_mux.c create mode 100644 php/r3/php_expandable_mux.h create mode 100644 php/r3/php_r3.c create mode 100644 php/r3/php_r3.h create mode 100644 php/r3/r3_controller.c create mode 100644 php/r3/r3_controller.h create mode 100644 php/r3/r3_functions.c create mode 100644 php/r3/r3_functions.h create mode 100644 php/r3/r3_mux.c create mode 100644 php/r3/r3_mux.h create mode 100644 php/r3/r3_persistent.c create mode 100644 php/r3/r3_persistent.h diff --git a/php/r3/ct_helper.c b/php/r3/ct_helper.c new file mode 100644 index 0000000..5216d1f --- /dev/null +++ b/php/r3/ct_helper.c @@ -0,0 +1,115 @@ + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "ct_helper.h" + + +#define ZEND_FIND_FUNC(ce, name, name_len, fe) \ + zend_hash_find(&ce->function_table, name, name_len, (void **) &fe) + +#define ZEND_FIND_FUNC_QUICK(ce, name, name_len, fe) \ + zend_hash_quick_find(&ce->function_table, name, name_len, zend_inline_hash_func(name, name_len) , (void **) &fe) + +/* {{{ zend_call_method + Only returns the returned zval if retval_ptr != NULL */ +ZEND_API zval* zend_call_method_with_3_params(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC) +{ + int result; + zend_fcall_info fci; + zval z_fname; + zval *retval; + HashTable *function_table; + + zval **params[3]; + + params[0] = &arg1; + params[1] = &arg2; + params[2] = &arg3; + + fci.size = sizeof(fci); + /*fci.function_table = NULL; will be read form zend_class_entry of object if needed */ + fci.object_ptr = object_pp ? *object_pp : NULL; + fci.function_name = &z_fname; + fci.retval_ptr_ptr = retval_ptr_ptr ? retval_ptr_ptr : &retval; + fci.param_count = param_count; + fci.params = params; + fci.no_separation = 1; + fci.symbol_table = NULL; + + if (!fn_proxy && !obj_ce) { + /* no interest in caching and no information already present that is + * needed later inside zend_call_function. */ + ZVAL_STRINGL(&z_fname, function_name, function_name_len, 0); + fci.function_table = !object_pp ? EG(function_table) : NULL; + result = zend_call_function(&fci, NULL TSRMLS_CC); + } else { + zend_fcall_info_cache fcic; + + fcic.initialized = 1; + if (!obj_ce) { + obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL; + } + if (obj_ce) { + function_table = &obj_ce->function_table; + } else { + function_table = EG(function_table); + } + if (!fn_proxy || !*fn_proxy) { + if (zend_hash_find(function_table, function_name, function_name_len+1, (void **) &fcic.function_handler) == FAILURE) { + /* error at c-level */ + zend_error(E_CORE_ERROR, "Couldn't find implementation for method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name); + } + if (fn_proxy) { + *fn_proxy = fcic.function_handler; + } + } else { + fcic.function_handler = *fn_proxy; + } + fcic.calling_scope = obj_ce; + if (object_pp) { + fcic.called_scope = Z_OBJCE_PP(object_pp); + } else if (obj_ce && + !(EG(called_scope) && + instanceof_function(EG(called_scope), obj_ce TSRMLS_CC))) { + fcic.called_scope = obj_ce; + } else { + fcic.called_scope = EG(called_scope); + } + fcic.object_ptr = object_pp ? *object_pp : NULL; + result = zend_call_function(&fci, &fcic TSRMLS_CC); + } + if (result == FAILURE) { + /* error at c-level */ + if (!obj_ce) { + obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL; + } + if (!EG(exception)) { + zend_error(E_CORE_ERROR, "Couldn't execute method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name); + } + } + if (!retval_ptr_ptr) { + if (retval) { + zval_ptr_dtor(&retval); + } + return NULL; + } + return *retval_ptr_ptr; +} +/* }}} */ + + +char * find_place_holder(char *pattern, int pattern_len) { + char needle_char[2] = { ':', 0 }; + return php_memnstr(pattern, + needle_char, + 1, + pattern + pattern_len); +} + diff --git a/php/r3/ct_helper.h b/php/r3/ct_helper.h new file mode 100644 index 0000000..198a3fe --- /dev/null +++ b/php/r3/ct_helper.h @@ -0,0 +1,8 @@ +#ifndef PHP_CT_HELPER_H +#define PHP_CT_HELPER_H 1 + +ZEND_API zval* zend_call_method_with_3_params(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC); + +char * find_place_holder(char *pattern, int pattern_len); + +#endif diff --git a/php/r3/hash.c b/php/r3/hash.c new file mode 100644 index 0000000..41f8dbc --- /dev/null +++ b/php/r3/hash.c @@ -0,0 +1,110 @@ + +#include "hash.h" + +HashTable * zend_hash_clone_persistent(HashTable* src TSRMLS_DC) +{ + zval **tmp; + return my_copy_hashtable(NULL, src, (ht_copy_fun_t) my_copy_zval_ptr, (void*) &tmp, sizeof(zval *), 1 TSRMLS_CC); +} + +HashTable * zend_hash_clone(HashTable* src TSRMLS_DC) +{ + zval **tmp; + return my_copy_hashtable(NULL, src, (ht_copy_fun_t) my_copy_zval_ptr, (void*) &tmp, sizeof(zval *), 0 TSRMLS_CC); +} + + +/** + * Recursively copy hash and all its value. + * + * This replaces zend_hash_copy + */ +HashTable * my_copy_hashtable(HashTable *target, HashTable *source, ht_copy_fun_t copy_fn, void *tmp, uint size, int persistent TSRMLS_DC) +{ + Bucket *curr = NULL, *prev = NULL , *newp = NULL; + void *new_entry; + int first = 1; + + assert(source != NULL); + + // allocate persistent memory for target and initialize it. + if (!target) { + target = pemalloc(sizeof(source[0]), persistent); + } + memcpy(target, source, sizeof(source[0])); + target->arBuckets = pemalloc(target->nTableSize * sizeof(Bucket*), persistent); + + memset(target->arBuckets, 0, target->nTableSize * sizeof(Bucket*)); + target->pInternalPointer = NULL; + target->pListHead = NULL; + + // since it's persistent, destructor should be NULL + target->persistent = persistent; + + if (! target->persistent) { + target->pDestructor = ZVAL_PTR_DTOR; + } + + curr = source->pListHead; + while (curr) { + // hash index + int n = curr->h % target->nTableSize; + + // allocate new bucket +// from apc +#ifdef ZEND_ENGINE_2_4 + if (!curr->nKeyLength) { + newp = (Bucket*) pemalloc(sizeof(Bucket), persistent); + memcpy(newp, curr, sizeof(Bucket)); + } else if (IS_INTERNED(curr->arKey)) { + newp = (Bucket*) pemalloc(sizeof(Bucket), persistent); + memcpy(newp, curr, sizeof(Bucket)); + } else { + // ugly but we need to copy + newp = (Bucket*) pemalloc(sizeof(Bucket) + curr->nKeyLength, persistent); + memcpy(newp, curr, sizeof(Bucket) + curr->nKeyLength ); + newp->arKey = (const char*)(newp+1); + } +#else + newp = (Bucket*) pecalloc(1, (sizeof(Bucket) + curr->nKeyLength - 1), persistent); + memcpy(newp, curr, sizeof(Bucket) + curr->nKeyLength - 1); +#endif + + + /* insert 'newp' into the linked list at its hashed index */ + if (target->arBuckets[n]) { + newp->pNext = target->arBuckets[n]; + newp->pLast = NULL; + newp->pNext->pLast = newp; + } else { + newp->pNext = newp->pLast = NULL; + } + target->arBuckets[n] = newp; + + // now we copy the bucket data using our 'copy_fn' + newp->pData = copy_fn(NULL, curr->pData, persistent TSRMLS_CC); + memcpy(&newp->pDataPtr, newp->pData, sizeof(void*)); + + /* insert 'newp' into the table-thread linked list */ + newp->pListLast = prev; + newp->pListNext = NULL; + + if (prev) { + prev->pListNext = newp; + } + if (first) { + target->pListHead = newp; + first = 0; + } + prev = newp; + + curr = curr->pListNext; + } + + target->pListTail = newp; + zend_hash_internal_pointer_reset(target); + + // return the newly allocated memory + return target; +} + diff --git a/php/r3/hash.h b/php/r3/hash.h new file mode 100644 index 0000000..d0c0662 --- /dev/null +++ b/php/r3/hash.h @@ -0,0 +1,59 @@ +#ifndef HASH_H +#define HASH_H 1 + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "php_r3.h" +#include "r3_mux.h" +#include "r3_functions.h" + + +#if R3_DEBUG +#define HT_OK 0 +#define HT_IS_DESTROYING 1 +#define HT_DESTROYED 2 +#define HT_CLEANING 3 + +static void _zend_is_inconsistent(const HashTable *ht, const char *file, int line) +{ + if (ht->inconsistent==HT_OK) { + return; + } + switch (ht->inconsistent) { + case HT_IS_DESTROYING: + zend_output_debug_string(1, "%s(%d) : ht=%p is being destroyed", file, line, ht); + break; + case HT_DESTROYED: + zend_output_debug_string(1, "%s(%d) : ht=%p is already destroyed", file, line, ht); + break; + case HT_CLEANING: + zend_output_debug_string(1, "%s(%d) : ht=%p is being cleaned", file, line, ht); + break; + default: + zend_output_debug_string(1, "%s(%d) : ht=%p is inconsistent", file, line, ht); + break; + } + zend_bailout(); +} +#define IS_CONSISTENT(a) _zend_is_inconsistent(a, __FILE__, __LINE__); +#define SET_INCONSISTENT(n) ht->inconsistent = n; +#else +#define IS_CONSISTENT(a) +#define SET_INCONSISTENT(n) +#endif + +HashTable * zend_hash_clone_persistent(HashTable* src TSRMLS_DC); + +HashTable * zend_hash_clone(HashTable* src TSRMLS_DC); + +typedef void* (*ht_copy_fun_t)(void*, void*, int TSRMLS_DC); +HashTable * my_copy_hashtable(HashTable *target, HashTable *source, ht_copy_fun_t copy_fn, void *tmp, uint size, int persistent TSRMLS_DC); + +#endif diff --git a/php/r3/php_expandable_mux.c b/php/r3/php_expandable_mux.c new file mode 100644 index 0000000..484393d --- /dev/null +++ b/php/r3/php_expandable_mux.c @@ -0,0 +1,45 @@ +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "r3_functions.h" +#include "php_expandable_mux.h" + +zend_class_entry *ce_r3_expandable_mux; + +const zend_function_entry expandable_mux_methods[] = { + PHP_ABSTRACT_ME(ExpandableMux, expand, NULL) + PHP_FE_END +}; + +/** + * TODO: Use zend_class_implements to register Controller class. + * + * zend_class_implements(mysqli_result_class_entry TSRMLS_CC, 1, zend_ce_traversable); + */ +static int implement_expandable_mux_interface_handler(zend_class_entry *interface, zend_class_entry *implementor TSRMLS_DC) +{ + if (implementor->type == ZEND_USER_CLASS && + !instanceof_function(implementor, ce_r3_expandable_mux TSRMLS_CC) + ) { + zend_error(E_ERROR, "R3\\ExpandableMux can't be implemented by user classes"); + } + return SUCCESS; +} + + +void r3_init_expandable_mux(TSRMLS_D) +{ + zend_class_entry ce_interface; + INIT_CLASS_ENTRY(ce_interface, "R3\\ExpandableMux", expandable_mux_methods); + + // if(Z_TYPE_PP(current) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(current), curl_CURLFile_class TSRMLS_CC)) + ce_r3_expandable_mux = zend_register_internal_interface(&ce_interface TSRMLS_CC); + ce_r3_expandable_mux->interface_gets_implemented = implement_expandable_mux_interface_handler; +} + diff --git a/php/r3/php_expandable_mux.h b/php/r3/php_expandable_mux.h new file mode 100644 index 0000000..9321b5b --- /dev/null +++ b/php/r3/php_expandable_mux.h @@ -0,0 +1,21 @@ +#ifndef PHP_EXPANDABLE_MUX_H +#define PHP_EXPANDABLE_MUX_H 1 + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "php_r3.h" +#include "r3_functions.h" + +extern zend_class_entry *ce_r3_expandable_mux; + +void r3_init_expandable_mux(TSRMLS_D); + +#endif diff --git a/php/r3/php_r3.c b/php/r3/php_r3.c new file mode 100644 index 0000000..28a0de4 --- /dev/null +++ b/php/r3/php_r3.c @@ -0,0 +1,136 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/standard/php_string.h" + +#include "php_r3.h" +// #include "ct_helper.h" +// #include "r3_functions.h" +// #include "r3_mux.h" +// #include "php_expandable_mux.h" +// #include "r3_controller.h" + +ZEND_DECLARE_MODULE_GLOBALS(r3); + + +// persistent list entry type for HashTable +int le_mux_hash_table; + +// persistent list entry type for boolean +int le_mux_bool; + +// persistent list entry type for int +int le_mux_int; + +// persistent list entry type for string +int le_mux_string; + +zend_class_entry *ce_r3_exception; + + +// #define DEBUG 1 +static const zend_function_entry r3_functions[] = { + PHP_FE(r3_match, NULL) + PHP_FE_END +}; + +void r3_init_exception(TSRMLS_D) { + zend_class_entry e; + INIT_CLASS_ENTRY(e, "R3Exception", NULL); + ce_r3_exception = zend_register_internal_class_ex(&e, (zend_class_entry*)zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); +} + +void r3_mux_le_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) +{ + HashTable *h = (HashTable*) rsrc->ptr; + if (h) { + // zend_hash_destroy(h); + // pefree(h, 1); + } +} + +zend_module_entry r3_module_entry = { + STANDARD_MODULE_HEADER, + PHP_R3_EXTNAME, + r3_functions, + PHP_MINIT(r3), + PHP_MSHUTDOWN(r3), + PHP_RINIT(r3), + NULL, + NULL, + PHP_R3_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +PHP_INI_BEGIN() + PHP_INI_ENTRY("r3.fstat", "0", PHP_INI_ALL, NULL) + // STD_PHP_INI_ENTRY("r3.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals) +PHP_INI_END() + +#ifdef COMPILE_DL_R3 +ZEND_GET_MODULE(r3) +#endif + +static void php_r3_init_globals(zend_r3_globals *r3_globals) +{ + // r3_globals->persistent_list = (HashTable*) + // array_init(r3_globals->persistent_list); +} + +PHP_MINIT_FUNCTION(r3) { + ZEND_INIT_MODULE_GLOBALS(r3, php_r3_init_globals, NULL); + REGISTER_INI_ENTRIES(); + // r3_init_mux(TSRMLS_C); + // r3_init_expandable_mux(TSRMLS_C); + // r3_init_controller(TSRMLS_C); + le_mux_hash_table = zend_register_list_destructors_ex(NULL, r3_mux_le_hash_dtor, "hash table", module_number); + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(r3) { + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} + +PHP_RINIT_FUNCTION(r3) { + return SUCCESS; +} + + +/* + * r3_compile(array $routes, string $path); + */ +PHP_FUNCTION(r3_match) +{ + zval *z_routes; + char *path; + int path_len; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "as", + &z_routes, + &path, &path_len ) == FAILURE) { + RETURN_FALSE; + } + + /* + zval *z_route; + z_route = php_r3_match(z_routes, path, path_len TSRMLS_CC); + if ( z_route != NULL ) { + *return_value = *z_route; + zval_copy_ctor(return_value); + return; + } + */ + RETURN_NULL(); +} + diff --git a/php/r3/php_r3.h b/php/r3/php_r3.h new file mode 100644 index 0000000..03a4351 --- /dev/null +++ b/php/r3/php_r3.h @@ -0,0 +1,98 @@ +#ifndef PHP_R3_H +#define PHP_R3_H 1 + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/standard/php_string.h" + +#define PHP_R3_VERSION "1.3.1" +#define PHP_R3_EXTNAME "r3" + + +#ifdef ZTS +#include "TSRM.h" +#endif + +extern int r3_globals_id; + +extern int le_mux_hash_table; + +// global variable structure +ZEND_BEGIN_MODULE_GLOBALS(r3) + // zval *mux_array; + HashTable * persistent_list; + // zend_bool direction; +ZEND_END_MODULE_GLOBALS(r3) + +#ifdef ZTS +#define R3_G(v) TSRMG(r3_globals_id, zend_r3_globals *, v) +#else +#define R3_G(v) (r3_globals.v) +#endif + + + +#define ZEND_HASH_FETCH(hash,key,ret) \ + zend_hash_find(hash, key, sizeof(key), (void**)&ret) == SUCCESS + +#define PUSH_PARAM(arg) zend_vm_stack_push(arg TSRMLS_CC) +#define POP_PARAM() (void)zend_vm_stack_pop(TSRMLS_C) +#define PUSH_EO_PARAM() +#define POP_EO_PARAM() + +#define CALL_METHOD_BASE(classname, name) zim_##classname##_##name + +#define CALL_METHOD_HELPER(classname, name, retval, thisptr, num, param) \ + PUSH_PARAM(param); PUSH_PARAM((void*)num); \ + PUSH_EO_PARAM(); \ + CALL_METHOD_BASE(classname, name)(num, retval, NULL, thisptr, 0 TSRMLS_CC); \ + POP_EO_PARAM(); \ + POP_PARAM(); POP_PARAM(); + +#define CALL_METHOD(classname, name, retval, thisptr) \ + CALL_METHOD_BASE(classname, name)(0, retval, NULL, thisptr, 0 TSRMLS_CC); + +#define CALL_METHOD1(classname, name, retval, thisptr, param1) \ + CALL_METHOD_HELPER(classname, name, retval, thisptr, 1, param1); + +#define CALL_METHOD2(classname, name, retval, thisptr, param1, param2) \ + PUSH_PARAM(param1); \ + CALL_METHOD_HELPER(classname, name, retval, thisptr, 2, param2); \ + POP_PARAM(); + +#define CALL_METHOD3(classname, name, retval, thisptr, param1, param2, param3) \ + PUSH_PARAM(param1); PUSH_PARAM(param2); \ + CALL_METHOD_HELPER(classname, name, retval, thisptr, 3, param3); \ + POP_PARAM(); POP_PARAM(); + +PHP_MINIT_FUNCTION(r3); +PHP_MSHUTDOWN_FUNCTION(r3); +PHP_RINIT_FUNCTION(r3); + +/* +zval * php_r3_match(zval *z_routes, char *path, int path_len TSRMLS_DC); + +zval * call_mux_method(zval * object , char * method_name , int method_name_len, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC); + +zend_class_entry ** get_pattern_compiler_ce(TSRMLS_D); + +extern zend_class_entry *ce_r3_exception; +*/ + +extern zend_module_entry r3_module_entry; + +void r3_init_exception(TSRMLS_D); + +void r3_mux_le_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC); + +PHP_FUNCTION(r3_match); + +#define phpext_r3_ptr &r3_module_entry + +#endif diff --git a/php/r3/r3_controller.c b/php/r3/r3_controller.c new file mode 100644 index 0000000..c290a48 --- /dev/null +++ b/php/r3/r3_controller.c @@ -0,0 +1,437 @@ +#include "string.h" +#include "ctype.h" +#include "php.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "Zend/zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "ext/standard/php_var.h" + +#include "ct_helper.h" +#include "php_r3.h" +#include "r3_mux.h" +#include "r3_functions.h" +#include "php_expandable_mux.h" +#include "r3_controller.h" + +#include "annotation/scanner.h" +#include "annotation/annot.h" + +zend_class_entry *ce_r3_controller; + +const zend_function_entry controller_methods[] = { + PHP_ME(Controller, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(Controller, expand, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Controller, getActionMethods, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Controller, getActionRoutes, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(Controller, before, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Controller, after, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Controller, toJson, NULL, ZEND_ACC_PUBLIC) + + // PHP_ME(Controller, __destruct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) + PHP_FE_END +}; + +zend_bool phannot_fetch_argument_value(zval **arg, zval** value TSRMLS_DC) { + zval **expr; + if (zend_hash_find(Z_ARRVAL_PP(arg), "expr", sizeof("expr"), (void**)&expr) == FAILURE ) { + return FAILURE; + } + return zend_hash_find(Z_ARRVAL_PP(expr), "value", sizeof("value"), (void**) value); +} + +zend_bool phannot_fetch_argument_type(zval **arg, zval **type TSRMLS_DC) { + zval **expr; + if (zend_hash_find(Z_ARRVAL_PP(arg), "expr", sizeof("expr"), (void**)&expr) == FAILURE ) { + return FAILURE; + } + return zend_hash_find(Z_ARRVAL_PP(expr), "type", sizeof("type"), (void**)type); +} + +int strpos(const char *haystack, char *needle) +{ + char *p = strstr(haystack, needle); + if (p) + return p - haystack; + return -1; +} + + + + +void r3_init_controller(TSRMLS_D) { + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "R3\\Controller", controller_methods); + ce_r3_controller = zend_register_internal_class(&ce TSRMLS_CC); + zend_class_implements(ce_r3_controller TSRMLS_CC, 1, ce_r3_expandable_mux); +} + +PHP_METHOD(Controller, __construct) { +} + + +/** + * Returns [ method, annotations ] + */ +PHP_METHOD(Controller, getActionMethods) +{ + // get function table hash + HashTable *function_table = &Z_OBJCE_P(this_ptr)->function_table; + HashPosition pos; + + array_init(return_value); + + zend_function *mptr; + zend_hash_internal_pointer_reset_ex(function_table, &pos); + + const char *fn; + size_t fn_len; + int p; + + while (zend_hash_get_current_data_ex(function_table, (void **) &mptr, &pos) == SUCCESS) { + fn = mptr->common.function_name; + fn_len = strlen(mptr->common.function_name); + p = strpos(fn, "Action"); + + if ( p == -1 || (size_t)p != (fn_len - strlen("Action")) ) { + zend_hash_move_forward_ex(function_table, &pos); + continue; + } + + // append the structure [method name, annotations] + zval *new_item; + zval *z_method_annotations; + zval *z_indexed_annotations; + + zval *z_comment; + zval *z_file; + zval *z_line_start; + + MAKE_STD_ZVAL(new_item); + array_init(new_item); + add_next_index_stringl(new_item, fn, fn_len, 1); + + /* + * @var + * + * if there no annotation, we pass an empty array. + * + * The method annotation structure + * + */ + MAKE_STD_ZVAL(z_method_annotations); + array_init(z_method_annotations); + + // simplified annotation information is saved to this variable. + MAKE_STD_ZVAL(z_indexed_annotations); + array_init(z_indexed_annotations); + + if ( mptr->type == ZEND_USER_FUNCTION && mptr->op_array.doc_comment ) { + ALLOC_ZVAL(z_comment); + ZVAL_STRING(z_comment, mptr->op_array.doc_comment, 1); + + ALLOC_ZVAL(z_file); + ZVAL_STRING(z_file, mptr->op_array.filename, 1); + + ALLOC_ZVAL(z_line_start); + ZVAL_LONG(z_line_start, mptr->op_array.line_start); + + /* + zval *z_line_end; + ALLOC_ZVAL(z_line_end); + ZVAL_LONG(z_line_start, mptr->op_array.line_end); + */ + zval **z_ann = NULL, **z_ann_name = NULL, **z_ann_arguments = NULL, **z_ann_argument = NULL, **z_ann_argument_value = NULL; + + // TODO: make phannot_parse_annotations reads comment variable in char* type, so we don't need to create extra zval(s) + if (phannot_parse_annotations(z_method_annotations, z_comment, z_file, z_line_start TSRMLS_CC) == SUCCESS) { + + /* + * z_method_annotations is an indexed array, which contains a structure like this: + * + * [ + * { name => ... , type => ... , arguments => ... , file => , line => }, + * { name => ... , type => ... , arguments => ... , file => , line => }, + * { name => ... , type => ... , arguments => ... , file => , line => }, + * ] + * + * the actuall structure: + * + * array(2) { + * [0]=> + * array(5) { + * ["type"]=> + * int(300) + * ["name"]=> + * string(5) "Route" + * ["arguments"]=> + * array(1) { + * [0]=> + * array(1) { + * ["expr"]=> + * array(2) { + * ["type"]=> + * int(303) + * ["value"]=> + * string(7) "/delete" + * } + * } + * } + * ["file"]=> string(48) "...." + * ["line"]=> int(54) + * }, + * ..... + * } + */ + HashPosition annp; + for ( + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z_method_annotations), &annp); + zend_hash_get_current_data_ex(Z_ARRVAL_P(z_method_annotations), (void**)&z_ann, &annp) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(z_method_annotations), &annp) + ) { + if (zend_hash_find(Z_ARRVAL_P(*z_ann), "name", sizeof("name"), (void**)&z_ann_name) == FAILURE ) { + continue; + } + if (zend_hash_find(Z_ARRVAL_P(*z_ann), "arguments", sizeof("arguments"), (void**)&z_ann_arguments) == FAILURE ) { + continue; + } + + // We currenly only support "@Method()" and "@Route" + if ( strncmp( Z_STRVAL_PP(z_ann_name), "Method", sizeof("Method") - 1 ) != 0 + && strncmp( Z_STRVAL_PP(z_ann_name), "Route", sizeof("Route") - 1 ) != 0 ) + { + continue; + } + + // read the first argument (we only support for one argument currently, and should support complex syntax later.) + if ( zend_hash_index_find(Z_ARRVAL_PP(z_ann_arguments), 0, (void**) &z_ann_argument) == SUCCESS ) { + if ( phannot_fetch_argument_value(z_ann_argument, (zval**) &z_ann_argument_value TSRMLS_CC) == SUCCESS ) { + Z_ADDREF_PP(z_ann_argument_value); + add_assoc_zval(z_indexed_annotations, Z_STRVAL_PP(z_ann_name), *z_ann_argument_value); + } + } + } + } + } + + Z_ADDREF_P(z_indexed_annotations); + add_next_index_zval(new_item, z_indexed_annotations); + add_next_index_zval(return_value, new_item); + + zend_hash_move_forward_ex(function_table, &pos); + } + return; +} + + +char * translate_method_name_to_path(const char *method_name) +{ + char *p = strstr(method_name, "Action"); + if ( p == NULL ) { + return NULL; + } + + if ( strncmp(method_name, "indexAction", strlen("indexAction") ) == 0 ) { + // returns empty string as its path + return strndup("",sizeof("")-1); + } + char * new_path; + + // XXX: this might overflow.. + new_path = ecalloc( 128 , sizeof(char) ); + + int len = p - method_name; + char * c = (char*) method_name; + int x = 0; + new_path[x++] = '/'; + int new_path_len = 1; + while( len-- ) { + if ( isupper(*c) ) { + new_path[x++] = '/'; + new_path[x++] = tolower(*c); + new_path_len += 2; + } else { + new_path[x++] = *c; + new_path_len ++; + } + c++; + } + return new_path; +} + +// return path => path pairs +// structure: [[ path, method name ], [ ... ], [ ... ], ... ] +PHP_METHOD(Controller, getActionRoutes) +{ + zend_function *fe; + if ( zend_hash_quick_find( &ce_r3_controller->function_table, "getactionmethods", sizeof("getactionmethods"), zend_inline_hash_func(ZEND_STRS("getactionmethods")), (void **) &fe) == FAILURE ) { + php_error(E_ERROR, "getActionMethods method not found"); + } + // call export method + zval *rv = NULL; + zend_call_method_with_0_params( &this_ptr, ce_r3_controller, &fe, "getactionmethods", &rv ); + + HashTable *func_list = Z_ARRVAL_P(rv); + HashPosition pointer; + zval **z_method_name; + zval **z_annotations; + zval **z_doc_method; + zval **z_doc_uri; + zval **item; + + array_init(return_value); + + for(zend_hash_internal_pointer_reset_ex(func_list, &pointer); + zend_hash_get_current_data_ex(func_list, (void**) &item, &pointer) == SUCCESS; + zend_hash_move_forward_ex(func_list, &pointer)) + { + + zend_hash_index_find(Z_ARRVAL_PP(item), 0, (void**)&z_method_name); + + const char *method_name = Z_STRVAL_PP(z_method_name); + int method_name_len = Z_STRLEN_PP(z_method_name); + char *path = NULL; + + zval *z_route_options; + MAKE_STD_ZVAL(z_route_options); + array_init(z_route_options); + + if ( zend_hash_index_find(Z_ARRVAL_PP(item), 1, (void**)&z_annotations) == SUCCESS ) { + if (zend_hash_find(Z_ARRVAL_PP(z_annotations), "Route", sizeof("Route"), (void**)&z_doc_uri) == SUCCESS) { + path = estrndup(Z_STRVAL_PP(z_doc_uri), Z_STRLEN_PP(z_doc_uri)); + } + if (zend_hash_find(Z_ARRVAL_PP(z_annotations), "Method", sizeof("Method"), (void**)&z_doc_method) == SUCCESS) { + Z_ADDREF_PP(z_doc_method); + add_assoc_zval(z_route_options, "method", *z_doc_method); + } + } + + if (!path) { + path = translate_method_name_to_path(method_name); + } + + // return structure [ path, method name, http method ] + zval * new_item; + MAKE_STD_ZVAL(new_item); + array_init_size(new_item, 3); + add_next_index_string(new_item, path, 1); + add_next_index_stringl(new_item, method_name, method_name_len, 0); + + Z_ADDREF_P(z_route_options); + add_next_index_zval(new_item, z_route_options); + + // append to the result array + add_next_index_zval(return_value, new_item); + } + + return; +} + +PHP_METHOD(Controller, expand) +{ + zval *new_mux; + MAKE_STD_ZVAL(new_mux); + object_init_ex(new_mux, ce_r3_mux); + CALL_METHOD(Mux, __construct, new_mux, new_mux); + + Z_ADDREF_P(new_mux); // add reference + + + zval *path_array = NULL; + zend_call_method_with_0_params( &this_ptr, ce_r3_controller, NULL, "getactionroutes", &path_array ); + + if ( Z_TYPE_P(path_array) != IS_ARRAY ) { + php_error(E_ERROR, "getActionRoutes does not return an array."); + RETURN_FALSE; + } + + const char *class_name = NULL; + zend_uint class_name_len; + int dup = zend_get_object_classname(this_ptr, &class_name, &class_name_len TSRMLS_CC); + + HashPosition pointer; + zval **path_entry = NULL; + for ( + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(path_array), &pointer); + zend_hash_get_current_data_ex(Z_ARRVAL_P(path_array), (void**) &path_entry, &pointer) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(path_array), &pointer) + ) { + zval **z_path; + zval **z_method; + zval **z_options = NULL; + + + if ( zend_hash_index_find(Z_ARRVAL_PP(path_entry), 0, (void**) &z_path) == FAILURE ) { + continue; + } + if ( zend_hash_index_find(Z_ARRVAL_PP(path_entry), 1, (void**) &z_method) == FAILURE ) { + continue; + } + + zend_hash_index_find(Z_ARRVAL_PP(path_entry), 2, (void**) &z_options); + + zval *z_callback; + MAKE_STD_ZVAL(z_callback); + array_init_size(z_callback, 2); + + Z_ADDREF_P(z_callback); + Z_ADDREF_PP(z_method); + + add_next_index_stringl(z_callback, class_name, class_name_len, 1); + add_next_index_zval(z_callback, *z_method); + + zval *rv = NULL; + zend_call_method_with_3_params(&new_mux, ce_r3_mux, NULL, "add", strlen("add"), &rv, 3, *z_path, z_callback, *z_options TSRMLS_CC); + } + + zval *rv = NULL; + zend_call_method_with_0_params(&new_mux, ce_r3_mux, NULL, "sort", &rv ); + + *return_value = *new_mux; + zval_copy_ctor(return_value); +} + +PHP_METHOD(Controller, before) { + +} + +PHP_METHOD(Controller, after) { + +} + +PHP_METHOD(Controller, toJson) +{ + zval *z_data; + long options = 0; + long depth = 0; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|ll", &z_data, &options, &depth) == FAILURE) { + RETURN_FALSE; + } + + zval *z_options; + zval *z_depth; + + MAKE_STD_ZVAL(z_options); + MAKE_STD_ZVAL(z_depth); + + ZVAL_LONG(z_depth, 0); + ZVAL_LONG(z_options, 0); + + + + zval *rv = NULL; + // zend_call_method_with_3_params(NULL, NULL, NULL, "json_encode", sizeof("json_encode"), &rv, 3, z_data, z_options, z_depth TSRMLS_CC ); + zend_call_method_with_2_params(NULL, NULL, NULL, "json_encode", &rv, z_data, z_options); + + *return_value = *rv; + zval_copy_ctor(return_value); +} + + + diff --git a/php/r3/r3_controller.h b/php/r3/r3_controller.h new file mode 100644 index 0000000..b0194b8 --- /dev/null +++ b/php/r3/r3_controller.h @@ -0,0 +1,35 @@ +#ifndef PHP_CONTROLLER_H +#define PHP_CONTROLLER_H 1 + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "php_r3.h" +#include "r3_functions.h" + +extern zend_class_entry *ce_r3_controller; + +void r3_init_controller(TSRMLS_D); + +char * translate_method_name_to_path(const char *method_name); +zend_bool phannot_fetch_argument_value(zval **arg, zval** value TSRMLS_DC); +zend_bool phannot_fetch_argument_type(zval **arg, zval **type TSRMLS_DC); +int strpos(const char *haystack, char *needle); + +PHP_METHOD(Controller, __construct); +PHP_METHOD(Controller, expand); +PHP_METHOD(Controller, getActionMethods); +PHP_METHOD(Controller, getActionRoutes); +PHP_METHOD(Controller, before); +PHP_METHOD(Controller, after); +PHP_METHOD(Controller, toJson); + + +#endif diff --git a/php/r3/r3_functions.c b/php/r3/r3_functions.c new file mode 100644 index 0000000..33544b9 --- /dev/null +++ b/php/r3/r3_functions.c @@ -0,0 +1,838 @@ +/* +vim:fdm=marker:et:sw=4:ts=4:sts=4: +*/ + +#include "php.h" +#include "string.h" +#include "pcre.h" +#include "main/php_main.h" +#include "Zend/zend_compile.h" +#include "Zend/zend_alloc.h" +#include "Zend/zend_operators.h" +#include "Zend/zend_API.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "php_r3.h" +#include "r3_functions.h" + +// #include "r3_persistent.h" +// #include "php_expandable_mux.h" +// #include "hash.h" + +/** + * new_dst = ht_copy_fun_t(NULL, src); + * + * Can be our zval copy function + */ +/* {{{ my_copy_zval_ptr */ +zval** my_copy_zval_ptr(zval** dst, const zval** src, int persistent TSRMLS_DC) +{ + zval* dst_new; + assert(src != NULL); + if (!dst) { + dst = (zval**) pemalloc(sizeof(zval*), persistent); + } + CHECK(dst[0] = (zval*) pemalloc(sizeof(zval), persistent)); + CHECK(dst_new = my_copy_zval(*dst, *src, persistent TSRMLS_CC)); + if (dst_new != *dst) { + *dst = dst_new; + } + return dst; +} +/* }}} */ + + + + +/* {{{ my_copy_zval */ +zval* my_copy_zval(zval* dst, const zval* src, int persistent TSRMLS_DC) +{ + zval **tmp; + assert(dst != NULL); + assert(src != NULL); + memcpy(dst, src, sizeof(zval)); + + /* deep copies are refcount(1), but moved up for recursive + * arrays, which end up being add_ref'd during its copy. */ + Z_SET_REFCOUNT_P(dst, 1); + Z_UNSET_ISREF_P(dst); + + switch (src->type & IS_CONSTANT_TYPE_MASK) { + case IS_RESOURCE: + php_error(E_ERROR, "Cannot copy resource"); + break; + case IS_BOOL: + case IS_LONG: + case IS_DOUBLE: + case IS_NULL: + break; + + case IS_CONSTANT: + case IS_STRING: + if (src->value.str.val) { + dst->value.str.val = pestrndup(src->value.str.val, src->value.str.len, persistent); + } + break; + + case IS_ARRAY: + case IS_CONSTANT_ARRAY: + dst->value.ht = my_copy_hashtable(NULL, src->value.ht, (ht_copy_fun_t) my_copy_zval_ptr, (void*) &tmp, sizeof(zval *), persistent TSRMLS_CC); + break; + + // XXX: we don't serialize object. + case IS_OBJECT: + php_error(E_ERROR, "Cannot copy Object."); + break; +#ifdef ZEND_ENGINE_2_4 + case IS_CALLABLE: + php_error(E_ERROR, "Cannot copy Callable."); + // XXX: we don't serialize callbable object. + break; +#endif + default: + assert(0); + } + return dst; +} +/* }}} */ + +/* my_zval_copy_ctor_func {{{*/ +void my_zval_copy_ctor_func(zval *zvalue ZEND_FILE_LINE_DC) +{ + switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) { + case IS_RESOURCE: { + TSRMLS_FETCH(); + + zend_list_addref(zvalue->value.lval); + } + break; + case IS_BOOL: + case IS_LONG: + case IS_NULL: + break; + case IS_CONSTANT: + case IS_STRING: + CHECK_ZVAL_STRING_REL(zvalue); + if (!IS_INTERNED(zvalue->value.str.val)) { + zvalue->value.str.val = (char *) estrndup_rel(zvalue->value.str.val, zvalue->value.str.len); + } + break; + case IS_ARRAY: + case IS_CONSTANT_ARRAY: { + zval *tmp; + HashTable *original_ht = zvalue->value.ht; + HashTable *tmp_ht = NULL; + TSRMLS_FETCH(); + + if (zvalue->value.ht == &EG(symbol_table)) { + return; /* do nothing */ + } + ALLOC_HASHTABLE_REL(tmp_ht); + zend_hash_init(tmp_ht, zend_hash_num_elements(original_ht), NULL, ZVAL_PTR_DTOR, 0); + zend_hash_copy(tmp_ht, original_ht, (copy_ctor_func_t) my_zval_copy_ctor_func, (void *) &tmp, sizeof(zval *)); + zvalue->value.ht = tmp_ht; + } + break; + case IS_OBJECT: + { + TSRMLS_FETCH(); + Z_OBJ_HT_P(zvalue)->add_ref(zvalue TSRMLS_CC); + } + break; + } + Z_ADDREF_P(zvalue); +} +/*}}}*/ + +// my_zval_copy_ctor_persistent_func {{{ +void my_zval_copy_ctor_persistent_func(zval *zvalue ZEND_FILE_LINE_DC) +{ + /* + zval *orig_zvalue; + orig_zvalue = zvalue; + zvalue = pemalloc(sizeof(zval), 1); + *zvalue = *zvalue; + zval_copy_ctor(zvalue); + Z_SET_REFCOUNT_P(zvalue, 1); + Z_UNSET_ISREF_P(zvalue); + // MAKE_COPY_ZVAL(&new_zvalue, zvalue); + SEPARATE_ZVAL(&zvalue); + */ + + switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) { + case IS_RESOURCE: + case IS_BOOL: + case IS_LONG: + case IS_DOUBLE: + case IS_NULL: + break; + + case IS_CONSTANT: + case IS_STRING: + CHECK_ZVAL_STRING_REL(zvalue); + zvalue->value.str.val = (char *) pestrndup(zvalue->value.str.val, zvalue->value.str.len, 1); + break; + + case IS_ARRAY: + case IS_CONSTANT_ARRAY: { + zval *tmp; + HashTable *original_ht = zvalue->value.ht; + HashTable *tmp_ht = NULL; + TSRMLS_FETCH(); + if (zvalue->value.ht == &EG(symbol_table)) { + return; /* do nothing */ + } + tmp_ht = pemalloc(sizeof(HashTable), 1); + zend_hash_init(tmp_ht, zend_hash_num_elements(original_ht), NULL, ZVAL_PTR_DTOR, 1); + zend_hash_copy(tmp_ht, original_ht, (copy_ctor_func_t) my_zval_copy_ctor_persistent_func, (void *) &tmp, sizeof(zval *)); + zvalue->value.ht = tmp_ht; + } + break; + case IS_OBJECT: + { + TSRMLS_FETCH(); + Z_OBJ_HT_P(zvalue)->add_ref(zvalue TSRMLS_CC); + } + break; + } + Z_SET_REFCOUNT_P(zvalue, 1); +} +// }}} + +int _r3_store_mux(char *name, zval * mux TSRMLS_DC) +{ + zend_rsrc_list_entry new_le, *le; + + // variables for copying mux object properties + char *id_key, *expand_key; + int status, id_key_len, expand_key_len; + + id_key_len = spprintf(&id_key, 0, "mux_id_%s", name); + expand_key_len = spprintf(&expand_key, 0, "mux_expand_%s", name); + + + // Z_ADDREF_P(mux); + + // make the hash table persistent + zval *prop, *tmp; + HashTable *routes, *static_routes; + + prop = zend_read_property(ce_r3_mux, mux, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + routes = zend_hash_clone_persistent( Z_ARRVAL_P(prop) TSRMLS_CC); + if ( ! routes ) { + php_error(E_ERROR, "Can not clone HashTable"); + return FAILURE; + } + r3_persistent_store( name, "routes", le_mux_hash_table, (void*) routes TSRMLS_CC); + return SUCCESS; + + + prop = zend_read_property(ce_r3_mux, mux, "staticRoutes", sizeof("staticRoutes")-1, 1 TSRMLS_CC); + static_routes = zend_hash_clone_persistent( Z_ARRVAL_P(prop) TSRMLS_CC); + if ( ! static_routes ) { + php_error(E_ERROR, "Can not clone HashTable"); + return FAILURE; + } + r3_persistent_store(name, "static_routes", le_mux_hash_table, (void *) static_routes TSRMLS_CC) ; + + + // copy ID + /* + prop = zend_read_property(ce_r3_mux, mux, "id", sizeof("id")-1, 1 TSRMLS_CC); + tmp = pemalloc(sizeof(zval), 1); + INIT_ZVAL(tmp); + Z_TYPE_P(tmp) = IS_LONG; + Z_LVAL_P(tmp) = Z_LVAL_P(prop); + Z_SET_REFCOUNT_P(tmp, 1); + r3_persistent_store( name, "id", (void*) tmp TSRMLS_CC); + */ + + // We cannot copy un-expandable mux object because we don't support recursively copy for Mux object. + prop = zend_read_property(ce_r3_mux, mux, "expand", sizeof("expand")-1, 1 TSRMLS_CC); + if ( ! Z_BVAL_P(prop) ) { + php_error(E_ERROR, "We cannot copy un-expandable mux object because we don't support recursively copy for Mux object."); + } + efree(id_key); + efree(expand_key); + return SUCCESS; +} + + +/** + * Fetch mux related properties (hash tables) from EG(persistent_list) hash table and + * rebless a new Mux object with these properties. + * + * @return Mux object + */ +zval * _r3_fetch_mux(char *name TSRMLS_DC) +{ + zval *z_id, *z_routes, *z_static_routes, *z_submux, *z_routes_by_id, *tmp; + HashTable *routes_hash; + HashTable *static_routes_hash; + + // fetch related hash to this mux object. + routes_hash = (HashTable*) r3_persistent_fetch(name, "routes" TSRMLS_CC); + if ( ! routes_hash ) { + return NULL; + } + + static_routes_hash = (HashTable*) r3_persistent_fetch(name, "static_routes" TSRMLS_CC); + if ( ! static_routes_hash ) { + return NULL; + } + + z_id = (zval*) r3_persistent_fetch(name, "id" TSRMLS_CC); + MAKE_STD_ZVAL(z_routes); + MAKE_STD_ZVAL(z_static_routes); + MAKE_STD_ZVAL(z_routes_by_id); + MAKE_STD_ZVAL(z_submux); + + Z_TYPE_P(z_routes) = IS_ARRAY; + Z_TYPE_P(z_static_routes) = IS_ARRAY; + array_init(z_routes_by_id); + array_init(z_submux); + + // we need to clone hash table deeply because when Mux object returned to userspace, it will be freed. + Z_ARRVAL_P(z_routes) = zend_hash_clone(routes_hash TSRMLS_CC); + Z_ARRVAL_P(z_static_routes) = zend_hash_clone(static_routes_hash TSRMLS_CC); + + // create new object and return to userspace. + zval *new_mux = NULL; + ALLOC_INIT_ZVAL(new_mux); + object_init_ex(new_mux, ce_r3_mux); + + // We don't need __construct because we assign the property by ourself. + // CALL_METHOD(Mux, __construct, new_mux, new_mux); + Z_SET_REFCOUNT_P(new_mux, 1); + + + + if ( z_id ) { + Z_ADDREF_P(z_id); + zend_update_property_long(ce_r3_mux, new_mux, "id" , sizeof("id")-1, Z_LVAL_P(z_id) TSRMLS_CC); + } + // persistent mux should always be expanded. (no recursive structure) + zend_update_property_bool(ce_r3_mux , new_mux , "expand" , sizeof("expand")-1 , 1 TSRMLS_CC); + zend_update_property(ce_r3_mux , new_mux , "routes" , sizeof("routes")-1 , z_routes TSRMLS_CC); + zend_update_property(ce_r3_mux , new_mux , "staticRoutes" , sizeof("staticRoutes")-1 , z_static_routes TSRMLS_CC); + + zend_update_property(ce_r3_mux, new_mux, "routesById", sizeof("routesById")-1, z_routes_by_id TSRMLS_CC); + zend_update_property(ce_r3_mux, new_mux, "submux", sizeof("submux")-1, z_submux TSRMLS_CC); + return new_mux; +} + + +int mux_loader(char *path, zval *result TSRMLS_DC) +{ + zend_file_handle file_handle; + zend_op_array *op_array; + char realpath[MAXPATHLEN]; + + if (!VCWD_REALPATH(path, realpath)) { + return FAILURE; + } + + file_handle.filename = path; + file_handle.free_filename = 0; + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.opened_path = NULL; + file_handle.handle.fp = NULL; + + op_array = zend_compile_file(&file_handle, ZEND_INCLUDE TSRMLS_CC); + + + if (op_array && file_handle.handle.stream.handle) { + int dummy = 1; + + if (!file_handle.opened_path) { + file_handle.opened_path = path; + } + + zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy, sizeof(int), NULL); + } + zend_destroy_file_handle(&file_handle TSRMLS_CC); + + if (op_array) { + zval *local_retval_ptr = NULL; + + R3_STORE_EG_ENVIRON(); + EG(return_value_ptr_ptr) = &local_retval_ptr; + EG(active_op_array) = op_array; + +#if ((PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION > 2)) || (PHP_MAJOR_VERSION > 5) + if (!EG(active_symbol_table)) { + zend_rebuild_symbol_table(TSRMLS_C); + } +#endif + zend_execute(op_array TSRMLS_CC); + + destroy_op_array(op_array TSRMLS_CC); + efree(op_array); + + if (!EG(exception)) { + if (local_retval_ptr) { + if ( result ) { + COPY_PZVAL_TO_ZVAL(*result, local_retval_ptr); + } else { + zval_ptr_dtor(EG(return_value_ptr_ptr)); + } + } + } + R3_RESTORE_EG_ENVIRON(); + return SUCCESS; + } + + return FAILURE; +} + + +/* + * r3_compile(array $routes, string $path); + */ +PHP_FUNCTION(r3_match) +{ + zval *z_routes; + char *path; + int path_len; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "as", + &z_routes, + &path, &path_len ) == FAILURE) { + RETURN_FALSE; + } + + zval *z_route; + z_route = php_r3_match(z_routes, path, path_len TSRMLS_CC); + if ( z_route != NULL ) { + *return_value = *z_route; + zval_copy_ctor(return_value); + return; + } + RETURN_NULL(); +} + +PHP_FUNCTION(r3_store_mux) +{ + zval *mux; + char *name; + int name_len; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &name_len, &mux ) == FAILURE) { + RETURN_FALSE; + } + if ( _r3_store_mux(name, mux TSRMLS_CC) == SUCCESS ) { + RETURN_TRUE; + } + RETURN_FALSE; +} + + +PHP_FUNCTION(r3_persistent_dispatch) +{ + char *ns, *filename, *path; + int ns_len, filename_len, path_len; + zval *mux = NULL; + zval *route = NULL; + zval *z_path = NULL; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &ns, &ns_len, &filename, &filename_len, &path, &path_len) == FAILURE) { + RETURN_FALSE; + } + + mux = _r3_fetch_mux(ns TSRMLS_CC); + if ( mux == NULL ) { + ALLOC_INIT_ZVAL(mux); + if ( mux_loader(filename, mux TSRMLS_CC) == FAILURE ) { + php_error(E_ERROR, "Can not load Mux object from %s", filename); + } + + // TODO: compile mux and sort routes + if ( _r3_store_mux(ns, mux TSRMLS_CC) == FAILURE ) { + php_error(E_ERROR, "Can not store Mux object from %s", filename); + } + } + + ALLOC_INIT_ZVAL(z_path); + ZVAL_STRINGL(z_path, path ,path_len, 1); // no copy + + // XXX: pass return_value to the method call, so we don't need to copy + route = call_mux_method(mux, "dispatch" , sizeof("dispatch"), 1 , z_path, NULL, NULL TSRMLS_CC); + zval_ptr_dtor(&z_path); + + if ( route ) { + *return_value = *route; + zval_copy_ctor(return_value); + return; + } + // route not found + RETURN_FALSE; +} + + + +PHP_FUNCTION(r3_delete_mux) +{ + char *name, *persistent_key; + int name_len, persistent_key_len; + zend_rsrc_list_entry *le; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", + &name, &name_len) == FAILURE) { + RETURN_FALSE; + } + + persistent_key_len = spprintf(&persistent_key, 0, "mux_%s", name); + + if ( zend_hash_find(&EG(persistent_list), persistent_key, persistent_key_len + 1, (void**) &le) == SUCCESS ) { + zval_ptr_dtor((zval**) &le->ptr); + zend_hash_del(&EG(persistent_list), persistent_key, persistent_key_len + 1); + efree(persistent_key); + RETURN_TRUE; + } + efree(persistent_key); + RETURN_FALSE; +} + +PHP_FUNCTION(r3_fetch_mux) +{ + char *name; + int name_len; + zval * mux; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", + &name, &name_len) == FAILURE) { + RETURN_FALSE; + } + + mux = _r3_fetch_mux(name TSRMLS_CC); + if ( mux ) { + *return_value = *mux; + zval_copy_ctor(return_value); + return; + } + RETURN_FALSE; +} + +PHP_FUNCTION(r3_sort_routes) +{ + zval *a; + zval *b; + + /* parse parameters */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", + &a, + &b ) == FAILURE) { + RETURN_FALSE; + } + + zval **a_pcre; + zval **a_pattern; + zval **a_compiled_pattern; + zval **a_options; + + zval **b_pcre; + zval **b_pattern; + zval **b_compiled_pattern; + zval **b_options; + + + zend_hash_index_find( Z_ARRVAL_P(a) , 0, (void**)&a_pcre); + zend_hash_index_find( Z_ARRVAL_P(b) , 0, (void**)&b_pcre); + + zend_hash_index_find( Z_ARRVAL_P(a) , 1, (void**)&a_pattern); + zend_hash_index_find( Z_ARRVAL_P(b) , 1, (void**)&b_pattern); + + zend_hash_index_find( Z_ARRVAL_P(a) , 3, (void**)&a_options); + zend_hash_index_find( Z_ARRVAL_P(b) , 3, (void**)&b_options); + + + // return strlen($a[3]['compiled']) > strlen($b[3]['compiled']); + if ( Z_BVAL_PP(a_pcre) && Z_BVAL_PP(b_pcre) ) { + zend_hash_quick_find( Z_ARRVAL_PP(a_options) , "compiled", strlen("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&a_compiled_pattern); + zend_hash_quick_find( Z_ARRVAL_PP(b_options) , "compiled", strlen("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&b_compiled_pattern); + + int a_len = Z_STRLEN_PP(a_compiled_pattern); + int b_len = Z_STRLEN_PP(b_compiled_pattern); + if ( a_len == b_len ) { + RETURN_LONG(0); + } else if ( a_len > b_len ) { + RETURN_LONG(-1); + } else { + RETURN_LONG(1); + } + } + else if ( Z_BVAL_PP(a_pcre) ) { + RETURN_LONG(-1); + } + else if ( Z_BVAL_PP(b_pcre) ) { + RETURN_LONG(1); + } + + int a_len = Z_STRLEN_PP(a_pattern); + int b_len = Z_STRLEN_PP(b_pattern); + + if ( a_len == b_len ) { + RETURN_LONG(0); + } + else if ( a_len > b_len ) { + RETURN_LONG(-1); + } + else { + RETURN_LONG(1); + } +} + +// +// int zend_hash_has_key( ) +// +inline zval * php_r3_match(zval *z_routes, char *path, int path_len TSRMLS_DC) { + int current_request_method = 0; + int current_https = 0; + zval * current_http_host = NULL; + + HashTable *server_vars_hash = fetch_server_vars_hash(TSRMLS_C); + if ( server_vars_hash ) { + current_request_method = get_current_request_method_const(server_vars_hash TSRMLS_CC); + current_https = get_current_https(server_vars_hash TSRMLS_CC); + current_http_host = get_current_http_host(server_vars_hash TSRMLS_CC); + } + + HashPosition z_routes_pointer; + + // for iterating routes + zval **z_route_pp; + + zval **z_is_pcre_pp; // route[0] + zval **z_pattern_pp; // route[1] + zval **z_callback_pp; // callback @ route[2] + zval **z_route_options_pp; // route[3] + + pcre_cache_entry *pce; /* Compiled regular expression */ + + zval *pcre_ret = NULL; + zval *pcre_subpats = NULL; /* Array for subpatterns */ + + HashTable * z_routes_hash = Z_ARRVAL_P(z_routes); + + ALLOC_INIT_ZVAL(pcre_ret); // this is required. + ALLOC_INIT_ZVAL(pcre_subpats); // also required + + for(zend_hash_internal_pointer_reset_ex(z_routes_hash, &z_routes_pointer); + zend_hash_get_current_data_ex(z_routes_hash, (void**) &z_route_pp, &z_routes_pointer) == SUCCESS; + zend_hash_move_forward_ex(z_routes_hash, &z_routes_pointer)) + { + zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 0, (void**) &z_is_pcre_pp); + zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 1, (void**) &z_pattern_pp); + + if ( Z_BVAL_PP(z_is_pcre_pp) ) { + /* Compile regex or get it from cache. */ + if ((pce = pcre_get_compiled_regex_cache(Z_STRVAL_PP(z_pattern_pp), Z_STRLEN_PP(z_pattern_pp) TSRMLS_CC)) == NULL) { + zend_throw_exception(zend_exception_get_default(TSRMLS_C), "PCRE pattern compile failed.", 0 TSRMLS_CC); + return NULL; + } + + php_pcre_match_impl(pce, path, path_len, pcre_ret, pcre_subpats, 0, 0, 0, 0 TSRMLS_CC); + + // not matched ? + if ( ! Z_BVAL_P(pcre_ret) ) { + continue; + } + + // tell garbage collector to collect it, we need to use pcre_subpats later. + + // check conditions only when route option is provided + if ( zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 3, (void**) &z_route_options_pp) == SUCCESS ) { + if ( zend_hash_num_elements(Z_ARRVAL_PP(z_route_options_pp)) ) { + if ( 0 == validate_request_method( z_route_options_pp, current_request_method TSRMLS_CC) ) { + continue; + } + if ( 0 == validate_https( z_route_options_pp, current_https TSRMLS_CC) ) { + continue; + } + if ( 0 == validate_domain( z_route_options_pp, current_http_host TSRMLS_CC) ) { + continue; + } + } + } + + if ( Z_TYPE_P(pcre_subpats) == IS_NULL ) { + array_init(pcre_subpats); + } + + Z_ADDREF_P(pcre_subpats); + add_assoc_zval(*z_route_options_pp , "vars" , pcre_subpats); + return *z_route_pp; + // Apply "default" value to "vars" + /* + foreach( $route['variables'] as $k ) { + if( isset($regs[$k]) ) { + $route['vars'][ $k ] = $regs[$k]; + } else { + $route['vars'][ $k ] = $route['default'][ $k ]; + } + } + */ + /* + zval **z_route_default; + zval **z_route_subpat_val; + if ( zend_hash_find(z_route_options_pp, "default", sizeof("default"), (void**) &z_route_default ) == FAILURE ) { + HashPosition default_pointer; + HashTable *default_hash; + + variables_hash = Z_ARRVAL_PP(z_variables); + + // foreach variables as var, check if url contains variable or we should apply default value + for(zend_hash_internal_pointer_reset_ex(variables_hash, &variables_pointer); + zend_hash_get_current_data_ex(variables_hash, (void**) &z_var_name, &variables_pointer) == SUCCESS; + zend_hash_move_forward_ex(variables_hash, &variables_pointer)) + { + } + // if ( zend_hash_find(z_route_default, "default", sizeof("default"), (void**) &z_route_default ) == FAILURE ) { + } + */ + } else { + // normal string comparison + // pattern-prefix match + zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 2, (void**) &z_callback_pp); + if ( ( Z_TYPE_PP(z_callback_pp) == IS_LONG && strncmp(Z_STRVAL_PP( z_pattern_pp ), path, Z_STRLEN_PP(z_pattern_pp) ) == 0 ) + || strcmp(Z_STRVAL_PP( z_pattern_pp ), path ) == 0 ) + { + + // check conditions + if ( zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 3, (void**) &z_route_options_pp) == SUCCESS ) { + if ( zend_hash_num_elements(Z_ARRVAL_PP(z_route_options_pp)) ) { + if ( 0 == validate_request_method( z_route_options_pp, current_request_method TSRMLS_CC) ) { + continue; + } + if ( 0 == validate_https( z_route_options_pp, current_https TSRMLS_CC) ) { + continue; + } + if ( 0 == validate_domain( z_route_options_pp, current_http_host TSRMLS_CC) ) { + continue; + } + } + } + // we didn't use the pcre variables + zval_ptr_dtor(&pcre_subpats); + zval_ptr_dtor(&pcre_ret); + return *z_route_pp; + } + } + } + zval_ptr_dtor(&pcre_subpats); + zval_ptr_dtor(&pcre_ret); + return NULL; +} + +inline HashTable * fetch_server_vars_hash(TSRMLS_D) { + zval **z_server_hash = NULL; + if ( zend_hash_quick_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), zend_inline_hash_func(ZEND_STRS("_SERVER")), (void **) &z_server_hash) == SUCCESS ) { + return Z_ARRVAL_PP(z_server_hash); + } + return NULL; +} + +inline zval * fetch_server_var(HashTable * server_hash, char *key , int key_len TSRMLS_DC) { + zval **rv; + if ( zend_hash_find(server_hash, key, key_len, (void **) &rv) == SUCCESS ) { + return *rv; + } + return NULL; +} + +inline zval * get_current_remote_addr(HashTable * server_vars_hash TSRMLS_DC) { + // REMOTE_ADDR + return fetch_server_var(server_vars_hash, "REMOTE_ADDR", sizeof("REMOTE_ADDR") TSRMLS_CC); +} + +inline zval * get_current_http_host(HashTable * server_vars_hash TSRMLS_DC) { + return fetch_server_var(server_vars_hash, "HTTP_HOST", sizeof("HTTP_HOST") TSRMLS_CC); +} + +inline zval * get_current_request_uri(HashTable * server_vars_hash TSRMLS_DC) { + return fetch_server_var(server_vars_hash, "REQUEST_URI", sizeof("REQUEST_URI") TSRMLS_CC); +} + +inline int get_current_https(HashTable * server_vars_hash TSRMLS_DC) { + zval *https = fetch_server_var(server_vars_hash, "HTTPS", sizeof("HTTPS") TSRMLS_CC); + if ( https && Z_BVAL_P(https) ) { + return 1; + } + return 0; +} + +inline zval * get_current_request_method(HashTable * server_vars_hash TSRMLS_DC) { + return fetch_server_var(server_vars_hash, "REQUEST_METHOD", sizeof("REQUEST_METHOD") TSRMLS_CC); +} + +/* get request method type in constant value. {{{ + */ +inline int get_current_request_method_const(HashTable * server_vars_hash TSRMLS_DC) { + char *c_request_method; + zval *z_request_method = get_current_request_method(server_vars_hash TSRMLS_CC); + if ( z_request_method ) { + c_request_method = Z_STRVAL_P(z_request_method); + if ( strncmp("GET", c_request_method , sizeof("GET") ) == 0 ) { + return REQ_METHOD_GET; + } else if ( strncmp("POST", c_request_method , sizeof("POST") ) == 0 ) { + return REQ_METHOD_POST; + } else if ( strncmp("PUT" , c_request_method , sizeof("PUT") ) == 0 ) { + return REQ_METHOD_PUT; + } else if ( strncmp("DELETE", c_request_method, sizeof("DELETE") ) == 0 ) { + return REQ_METHOD_DELETE; + } else if ( strncmp("HEAD", c_request_method, sizeof("HEAD") ) == 0 ) { + return REQ_METHOD_HEAD; + } else if ( strncmp("PATCH", c_request_method, sizeof("PATCH") ) == 0 ) { + return REQ_METHOD_HEAD; + } else if ( strncmp("OPTIONS", c_request_method, sizeof("OPTIONS") ) == 0 ) { + return REQ_METHOD_OPTIONS; + } + } + return 0; +} +// }}} + +inline int validate_https(zval **z_route_options_pp, int https TSRMLS_DC) +{ + zval **z_route_secure = NULL; + if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options_pp) , "secure", sizeof("secure"), zend_inline_hash_func(ZEND_STRS("secure")), (void**) &z_route_secure ) == SUCCESS ) { + // check HTTPS flag + if ( https && ! Z_BVAL_PP(z_route_secure) ) { + return 0; + } + } + return 1; +} + +inline int validate_domain(zval **z_route_options_pp, zval * http_host TSRMLS_DC) +{ + zval **z_route_domain = NULL; + if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options_pp) , "domain", sizeof("domain"), zend_inline_hash_func(ZEND_STRS("domain")), (void**) &z_route_domain ) == SUCCESS ) { + // check HTTP_HOST from $_SERVER + if ( strncmp(Z_STRVAL_PP(z_route_domain), Z_STRVAL_P(http_host), Z_STRLEN_PP(z_route_domain) ) != 0 ) { + return 0; + } + } + return 1; +} + +inline int validate_request_method(zval **z_route_options_pp, int current_request_method TSRMLS_DC) +{ + zval **z_route_method = NULL; + if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options_pp) , "method", sizeof("method"), zend_inline_hash_func(ZEND_STRS("method")), (void**) &z_route_method ) == SUCCESS ) { + if ( Z_TYPE_PP(z_route_method) == IS_LONG && Z_LVAL_PP(z_route_method) != current_request_method ) { + return 0; + } + } + return 1; +} + diff --git a/php/r3/r3_functions.h b/php/r3/r3_functions.h new file mode 100644 index 0000000..d44f18f --- /dev/null +++ b/php/r3/r3_functions.h @@ -0,0 +1,91 @@ +#ifndef PHP_R3_FUNCTIONS_H +#define PHP_R3_FUNCTIONS_H 1 + +#define REQ_METHOD_GET 1 +#define REQ_METHOD_POST 2 +#define REQ_METHOD_PUT 3 +#define REQ_METHOD_DELETE 4 +#define REQ_METHOD_PATCH 5 +#define REQ_METHOD_HEAD 6 +#define REQ_METHOD_OPTIONS 7 + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "php_r3.h" +#include "r3_mux.h" +#include "r3_functions.h" + +extern inline zval * php_r3_match(zval *z_routes, char *path, int path_len TSRMLS_DC); +extern inline int get_current_request_method_const(HashTable * server_vars_hash TSRMLS_DC); +extern inline int get_current_https(HashTable * server_vars_hash TSRMLS_DC); +extern inline HashTable * fetch_server_vars_hash(TSRMLS_D); +extern inline zval * fetch_server_var(HashTable *server_vars_hash, char *key , int key_len TSRMLS_DC); +extern inline zval * get_current_http_host(HashTable * server_vars_hash TSRMLS_DC); +extern inline zval * get_current_request_uri(HashTable * server_vars_hash TSRMLS_DC); +extern inline zval * get_current_request_method(HashTable * server_vars_hash TSRMLS_DC); + +extern inline int validate_request_method(zval **z_route_options_pp, int current_request_method TSRMLS_DC); +extern inline int validate_domain(zval **z_route_options_pp, zval * http_host TSRMLS_DC); +extern inline int validate_https(zval **z_route_options_pp, int https TSRMLS_DC); + +#if ((PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION > 2)) || (PHP_MAJOR_VERSION > 5) +#define R3_STORE_EG_ENVIRON() \ + { \ + zval ** __old_return_value_pp = EG(return_value_ptr_ptr); \ + zend_op ** __old_opline_ptr = EG(opline_ptr); \ + zend_op_array * __old_op_array = EG(active_op_array); + +#define R3_RESTORE_EG_ENVIRON() \ + EG(return_value_ptr_ptr) = __old_return_value_pp;\ + EG(opline_ptr) = __old_opline_ptr; \ + EG(active_op_array) = __old_op_array; \ + } + +#else + +#define R3_STORE_EG_ENVIRON() \ + { \ + zval ** __old_return_value_pp = EG(return_value_ptr_ptr); \ + zend_op ** __old_opline_ptr = EG(opline_ptr); \ + zend_op_array * __old_op_array = EG(active_op_array); \ + zend_function_state * __old_func_state = EG(function_state_ptr); + +#define R3_RESTORE_EG_ENVIRON() \ + EG(return_value_ptr_ptr) = __old_return_value_pp;\ + EG(opline_ptr) = __old_opline_ptr; \ + EG(active_op_array) = __old_op_array; \ + EG(function_state_ptr) = __old_func_state; \ + } + +#endif + +#define CHECK(p) { if ((p) == NULL) return NULL; } + + +zval* my_copy_zval(zval* dst, const zval* src, int persistent TSRMLS_DC); + +zval** my_copy_zval_ptr(zval** dst, const zval** src, int persistent TSRMLS_DC); + + +zval * _r3_fetch_mux(char *name TSRMLS_DC); +int mux_loader(char *path, zval *result TSRMLS_DC); +int _r3_store_mux(char *name, zval * mux TSRMLS_DC) ; + +void my_zval_copy_ctor_persistent_func(zval *zvalue ZEND_FILE_LINE_DC); + +PHP_FUNCTION(r3_match); +PHP_FUNCTION(r3_sort_routes); +PHP_FUNCTION(r3_store_mux); +PHP_FUNCTION(r3_fetch_mux); +PHP_FUNCTION(r3_delete_mux); +PHP_FUNCTION(r3_persistent_dispatch); + + +#endif diff --git a/php/r3/r3_mux.c b/php/r3/r3_mux.c new file mode 100644 index 0000000..3fe6abb --- /dev/null +++ b/php/r3/r3_mux.c @@ -0,0 +1,1196 @@ + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "ext/standard/php_var.h" +#include "ext/standard/php_smart_str.h" +#include "ext/standard/php_array.h" + +#include "php_r3.h" +#include "ct_helper.h" +#include "r3_functions.h" +#include "r3_controller.h" +#include "r3_mux.h" + +zend_class_entry *ce_r3_mux; + +const zend_function_entry mux_methods[] = { + PHP_ME(Mux, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(Mux, __destruct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) + PHP_ME(Mux, getId, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, add, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, any, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, compile, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, sort, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, dispatch, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, length, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, mount, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, appendRoute, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, appendPCRERoute, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, match, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, getRoutes, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, setRoutes, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, getRoute, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, getSubMux, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, getRequestMethodConstant, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, export, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(Mux, get, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, post, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, put, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, delete, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, head, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, patch, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Mux, options, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(Mux, __set_state, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(Mux, generate_id, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_FE_END +}; + +void r3_init_mux(TSRMLS_D) { + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "R3\\Mux", mux_methods); + ce_r3_mux = zend_register_internal_class(&ce TSRMLS_CC); + zend_declare_property_null(ce_r3_mux, "id", strlen("id"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(ce_r3_mux, "routes", strlen("routes"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(ce_r3_mux, "routesById", strlen("routesById"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(ce_r3_mux, "staticRoutes", strlen("staticRoutes"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(ce_r3_mux, "submux", strlen("submux"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_bool(ce_r3_mux, "expand", strlen("expand"), 1, ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_long(ce_r3_mux, "id_counter", strlen("id_counter"), 0, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC TSRMLS_CC); +} + +zend_class_entry ** get_pattern_compiler_ce(TSRMLS_D) { + zend_class_entry **ce_pattern_compiler = NULL; + if ( zend_lookup_class( "R3\\PatternCompiler", strlen("R3\\PatternCompiler") , &ce_pattern_compiler TSRMLS_CC) == FAILURE ) { + php_error(E_ERROR, "Class R3\\PatternCompiler not found."); + return NULL; + } + if ( ce_pattern_compiler == NULL || *ce_pattern_compiler == NULL ) { + php_error(E_ERROR, "Class R3\\PatternCompiler not found."); + return NULL; + } + return ce_pattern_compiler; +} + +// Returns compiled route zval +zval * compile_route_pattern(zval *z_pattern, zval *z_options, zend_class_entry **ce_pattern_compiler TSRMLS_DC) +{ + // zend_class_entry **ce_pattern_compiler; + if ( ce_pattern_compiler == NULL ) { + ce_pattern_compiler = get_pattern_compiler_ce(TSRMLS_C); + if ( ce_pattern_compiler == NULL ) { + return NULL; + } + } + + zval *z_compiled_route = NULL; // will be an array + zend_call_method( NULL, *ce_pattern_compiler, NULL, "compile", strlen("compile"), &z_compiled_route, 2, z_pattern, z_options TSRMLS_CC ); + + if ( z_compiled_route == NULL ) { + return NULL; + } else if ( Z_TYPE_P(z_compiled_route) == IS_NULL ) { + zval_ptr_dtor(&z_compiled_route); + return NULL; + } + if ( Z_TYPE_P(z_compiled_route) != IS_ARRAY ) { + zval_ptr_dtor(&z_compiled_route); + return NULL; + } + return z_compiled_route; +} + +static void var_export(zval *return_value, zval *what TSRMLS_DC) +{ + smart_str buf = {0}; + php_var_export_ex(&what, 0, &buf TSRMLS_CC); + smart_str_0 (&buf); + ZVAL_STRINGL(return_value, buf.c, buf.len, 0); +} + +PHP_METHOD(Mux, __construct) { + zval *z_routes = NULL, *z_routes_by_id , *z_submux = NULL, *z_static_routes = NULL; + + MAKE_STD_ZVAL(z_routes); + MAKE_STD_ZVAL(z_routes_by_id); + MAKE_STD_ZVAL(z_static_routes); + MAKE_STD_ZVAL(z_submux); + + array_init(z_routes); + array_init(z_routes_by_id); + array_init(z_static_routes); + array_init(z_submux); + + zend_update_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, z_routes TSRMLS_CC); + zend_update_property(ce_r3_mux, this_ptr, "routesById", sizeof("routesById")-1, z_routes_by_id TSRMLS_CC); + zend_update_property(ce_r3_mux, this_ptr, "staticRoutes", sizeof("staticRoutes")-1, z_static_routes TSRMLS_CC); + zend_update_property(ce_r3_mux, this_ptr, "submux", sizeof("submux")-1, z_submux TSRMLS_CC); +} + +PHP_METHOD(Mux, __destruct) { + zval * val; + val = zend_read_property(ce_r3_mux, getThis(), "routes", sizeof("routes")-1, 1 TSRMLS_CC); + zval_ptr_dtor(&val); + + val = zend_read_property(ce_r3_mux, getThis(), "routesById", sizeof("routesById")-1, 1 TSRMLS_CC); + zval_ptr_dtor(&val); + + val = zend_read_property(ce_r3_mux, getThis(), "staticRoutes", sizeof("staticRoutes")-1, 1 TSRMLS_CC); + zval_ptr_dtor(&val); + + val = zend_read_property(ce_r3_mux, getThis(), "submux", sizeof("submux")-1, 1 TSRMLS_CC); + zval_ptr_dtor(&val); +} + +PHP_METHOD(Mux, generate_id) { + zval * z_counter = NULL; + long counter = 0; + z_counter = zend_read_static_property(ce_r3_mux, "id_counter", strlen("id_counter") , 0 TSRMLS_CC); + if ( z_counter != NULL ) { + counter = Z_LVAL_P(z_counter); + } + counter++; + Z_LVAL_P(z_counter) = counter; + RETURN_LONG(counter); +} + +PHP_METHOD(Mux, __set_state) { + zval *z_array; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &z_array) == FAILURE) { + RETURN_FALSE; + } + + object_init_ex(return_value, ce_r3_mux); + // XXX: put this back if we have problem + // CALL_METHOD(Mux, __construct, return_value, return_value); + + zval **z_id = NULL; + zval **z_routes = NULL; + zval **z_static_routes = NULL; + zval **z_routes_by_id = NULL; + zval **z_submux = NULL; + zval **z_expand = NULL; + + if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "id", sizeof("id"), zend_inline_hash_func(ZEND_STRS("id")), (void**)&z_id) == SUCCESS ) { + zend_update_property(ce_r3_mux, return_value, "id", sizeof("id")-1, *z_id TSRMLS_CC); + } + + + if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "routes", sizeof("routes"), zend_inline_hash_func(ZEND_STRS("routes")), (void**)&z_routes) == SUCCESS ) { + Z_ADDREF_PP(z_routes); + zend_update_property(ce_r3_mux, return_value, "routes", sizeof("routes")-1, *z_routes TSRMLS_CC); + } + + if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "staticRoutes", sizeof("staticRoutes"), zend_inline_hash_func(ZEND_STRS("staticRoutes")), (void**)&z_static_routes) == SUCCESS ) { + Z_ADDREF_PP(z_static_routes); + zend_update_property(ce_r3_mux, return_value, "staticRoutes", sizeof("staticRoutes")-1, *z_static_routes TSRMLS_CC); + } + + if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "routesById", sizeof("routesById"), zend_inline_hash_func(ZEND_STRS("routesById")), (void**)&z_routes_by_id) == SUCCESS ) { + Z_ADDREF_PP(z_routes_by_id); + zend_update_property(ce_r3_mux, return_value, "routesById", sizeof("routesById")-1, *z_routes_by_id TSRMLS_CC); + } + + if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "submux", sizeof("submux"), zend_inline_hash_func(ZEND_STRS("submux")), (void**)&z_submux) == SUCCESS ) { + Z_ADDREF_PP(z_submux); + zend_update_property(ce_r3_mux, return_value, "submux", sizeof("submux")-1, *z_submux TSRMLS_CC); + } + + if ( zend_hash_quick_find(Z_ARRVAL_P(z_array), "expand", sizeof("expand"), zend_inline_hash_func(ZEND_STRS("expand")), (void**)&z_expand) == SUCCESS ) { + zend_update_property(ce_r3_mux, return_value, "expand", sizeof("expand")-1, *z_expand TSRMLS_CC); + } +} + + +/** + * get_mux_function_entry("method", sizeof("method"), zend_inline_hash_func(ZEND_STRS("pattern"))); + */ +inline zend_function * get_mux_function_entry(char * method_name, int method_name_len, ulong h) { + zend_function *fe; + if ( zend_hash_quick_find( &ce_r3_mux->function_table, method_name, method_name_len, h, (void **) &fe) == SUCCESS ) { + return fe; + } + php_error(E_ERROR, "%s method not found", method_name); + return NULL; +} + +inline zval * call_mux_method(zval * object , char * method_name , int method_name_len, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC) +{ + zend_function *fe; + if ( zend_hash_find( &ce_r3_mux->function_table, method_name, method_name_len, (void **) &fe) == FAILURE ) { + php_error(E_ERROR, "%s method not found", method_name); + } + // call export method + zval *z_retval = NULL; + zend_call_method_with_3_params( &object, ce_r3_mux, &fe, method_name, method_name_len, &z_retval, param_count, arg1, arg2, arg3 TSRMLS_CC ); + return z_retval; +} + + +PHP_METHOD(Mux, get) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + // $options['method'] = REQ_METHOD_GET; + add_assoc_long(z_options, "method", REQ_METHOD_GET); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( this_ptr, "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } + // zval_ptr_dtor(&z_retval); +} + + + + + +PHP_METHOD(Mux, put) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + add_assoc_long(z_options, "method", REQ_METHOD_PUT); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } + // zval_ptr_dtor(&z_retval); +} + +PHP_METHOD(Mux, delete) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + add_assoc_long(z_options, "method", REQ_METHOD_DELETE); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } + // zval_ptr_dtor(&z_retval); +} + + +PHP_METHOD(Mux, post) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + add_assoc_long(z_options, "method", REQ_METHOD_POST); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } +} + + +PHP_METHOD(Mux, patch) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + add_assoc_long(z_options, "method", REQ_METHOD_PATCH); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } +} + +PHP_METHOD(Mux, head) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + add_assoc_long(z_options, "method", REQ_METHOD_HEAD); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } +} + +PHP_METHOD(Mux, options) { + zval *z_pattern = NULL, *z_callback = NULL, *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|a", &z_pattern, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init_size(z_options, 1); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init_size(z_options, 1); + } + + add_assoc_long(z_options, "method", REQ_METHOD_OPTIONS); + + // $this->add($pattern, $callback, $options); + zval * z_retval = call_mux_method( getThis(), "add" , sizeof("add"), 3 , z_pattern, z_callback, z_options TSRMLS_CC); + if ( z_retval ) { + *return_value = *z_retval; + zval_copy_ctor(return_value); + } +} + + +PHP_METHOD(Mux, mount) { + char *pattern; + int pattern_len; + + zval *z_mux = NULL; + zval *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|a", &pattern, &pattern_len, &z_mux, &z_options) == FAILURE) { + RETURN_FALSE; + } + if ( Z_TYPE_P(z_mux) == IS_NULL ) { + RETURN_FALSE; + } + + if ( Z_TYPE_P(z_mux) == IS_OBJECT ) { + if ( instanceof_function( Z_OBJCE_P(z_mux), ce_r3_controller TSRMLS_CC) ) { + zval *rv; + zend_call_method(&z_mux, ce_r3_controller, NULL, "expand", strlen("expand"), &rv, 0, NULL, NULL TSRMLS_CC ); + z_mux = rv; + } + } + + + Z_ADDREF_P(z_mux); + + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init(z_options); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init(z_options); + } + + + zend_class_entry **ce_pattern_compiler = get_pattern_compiler_ce(TSRMLS_C); + if ( ce_pattern_compiler == NULL ) { + RETURN_FALSE; + } + + zval *z_routes; + zval *z_expand; + zval *z_mux_routes; + + z_routes = zend_read_property( ce_r3_mux, getThis(), "routes", sizeof("routes")-1, 1 TSRMLS_CC); + z_expand = zend_read_property( ce_r3_mux, getThis(), "expand", sizeof("expand")-1, 1 TSRMLS_CC); + + + // TODO: merge routesById and staticRoutes properties + + + if ( Z_BVAL_P(z_expand) ) { + // fetch routes from $mux + // + z_mux_routes = zend_read_property( ce_r3_mux, z_mux, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + HashPosition route_pointer; + HashTable *mux_routes_hash; + mux_routes_hash = Z_ARRVAL_P(z_mux_routes); + zval **z_mux_route; + + + // iterate mux + for(zend_hash_internal_pointer_reset_ex(mux_routes_hash, &route_pointer); + zend_hash_get_current_data_ex(mux_routes_hash, (void**) &z_mux_route, &route_pointer) == SUCCESS; + zend_hash_move_forward_ex(mux_routes_hash, &route_pointer)) + { + // zval for new route + zval *z_new_routes; + + // zval for route item + zval **z_is_pcre; // route[0] + zval **z_route_pattern; + zval **z_route_callback; + zval **z_route_options; + zval **z_route_original_pattern; // for PCRE pattern + + if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 0, (void**) &z_is_pcre) == FAILURE ) { + continue; + } + if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 1, (void**) &z_route_pattern) == FAILURE ) { + continue; + } + if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 2, (void**) &z_route_callback) == FAILURE ) { + continue; + } + if ( zend_hash_index_find( Z_ARRVAL_PP(z_mux_route), 3, (void**) &z_route_options) == FAILURE ) { + continue; + } + + // Z_ADDREF_P(z_route_callback); + // Z_ADDREF_P(z_route_options); // reference it so it will not be recycled. + MAKE_STD_ZVAL(z_new_routes); + array_init(z_new_routes); + + if ( Z_BVAL_PP(z_is_pcre) ) { + // $newPattern = $pattern . $route[3]['pattern']; + + if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options), "pattern", sizeof("pattern"), zend_inline_hash_func(ZEND_STRS("pattern")), (void**) &z_route_original_pattern) == FAILURE ) { + php_error( E_ERROR, "Can not compile pattern, original pattern not found"); + } + + char new_pattern[120] = { 0 }; + int new_pattern_len; + strncat( new_pattern, pattern , pattern_len ); + strncat( new_pattern, Z_STRVAL_PP(z_route_original_pattern) , Z_STRLEN_PP(z_route_original_pattern) ); + + new_pattern_len = pattern_len + Z_STRLEN_PP(z_route_original_pattern); + + zval *z_new_pattern = NULL; + MAKE_STD_ZVAL(z_new_pattern); + ZVAL_STRINGL(z_new_pattern, new_pattern, new_pattern_len, 1); + + // TODO: merge options + + // $routeArgs = PatternCompiler::compile($newPattern, + // array_merge_recursive($route[3], $options) ); + zval *z_compiled_route = compile_route_pattern(z_new_pattern, *z_route_options, ce_pattern_compiler TSRMLS_CC); + + + if ( z_compiled_route == NULL || Z_TYPE_P(z_compiled_route) == IS_NULL ) { + php_error( E_ERROR, "Cannot compile pattern: %s", new_pattern); + } + + + zval **z_compiled_route_pattern; + if ( zend_hash_quick_find( Z_ARRVAL_P(z_compiled_route) , "compiled", sizeof("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&z_compiled_route_pattern) == FAILURE ) { + php_error( E_ERROR, "compiled pattern not found: %s", new_pattern); + } + + zend_hash_quick_update( Z_ARRVAL_P(z_compiled_route), "pattern", sizeof("pattern"), zend_inline_hash_func(ZEND_STRS("pattern")), &z_new_pattern, sizeof(zval *), NULL); + + + Z_ADDREF_PP(z_compiled_route_pattern); + Z_ADDREF_PP(z_route_callback); + Z_ADDREF_P(z_compiled_route); + Z_ADDREF_P(z_new_routes); + + // create new route and append to mux->routes + add_index_bool(z_new_routes, 0 , 1); // pcre flag == false + add_index_zval(z_new_routes, 1, *z_compiled_route_pattern); + add_index_zval(z_new_routes, 2 , *z_route_callback); + add_index_zval(z_new_routes, 3, z_compiled_route); + add_next_index_zval(z_routes, z_new_routes); + + } else { + + + // $this->routes[] = array( + // false, + // $pattern . $route[1], + // $route[2], + // $options, + // ); + char new_pattern[120] = { 0 }; + strncat(new_pattern, pattern, pattern_len); + + strncat(new_pattern, Z_STRVAL_PP(z_route_pattern), Z_STRLEN_PP(z_route_pattern) ); + + int new_pattern_len = pattern_len + Z_STRLEN_PP(z_route_pattern); + + // Merge the mount options with the route options + zval *z_new_route_options; + MAKE_STD_ZVAL(z_new_route_options); + array_init(z_new_route_options); + php_array_merge(Z_ARRVAL_P(z_new_route_options), Z_ARRVAL_P(z_options), 0 TSRMLS_CC); + php_array_merge(Z_ARRVAL_P(z_new_route_options), Z_ARRVAL_P(*z_route_options), 0 TSRMLS_CC); + + Z_ADDREF_PP(z_route_callback); + Z_ADDREF_P(z_new_route_options); + Z_ADDREF_P(z_new_routes); + + /* make the array: [ pcreFlag, pattern, callback, options ] */ + add_index_bool(z_new_routes, 0 , 0); // pcre flag == false + add_index_stringl(z_new_routes, 1 , new_pattern , new_pattern_len, 1); + add_index_zval( z_new_routes, 2 , *z_route_callback); + add_index_zval( z_new_routes, 3 , z_new_route_options); + add_next_index_zval(z_routes, z_new_routes); + } + } + + } else { + zend_function *fe_getid = NULL; // method entry + zend_function *fe_add = NULL; // method entry + + if ( zend_hash_quick_find(&ce_r3_mux->function_table, "getid", sizeof("getid"), zend_inline_hash_func(ZEND_STRS("getid")), (void **) &fe_getid) == FAILURE ) { + php_error(E_ERROR, "Cannot call method Mux::getid()"); + RETURN_FALSE; + } + if ( zend_hash_quick_find(&ce_r3_mux->function_table, "add", sizeof("add"), zend_inline_hash_func(ZEND_STRS("add")), (void **) &fe_add) == FAILURE ) { + php_error(E_ERROR, "Cannot call method Mux::add()"); + RETURN_FALSE; + } + + // $muxId = $mux->getId(); + // $this->add($pattern, $muxId, $options); + // $this->submux[ $muxId ] = $mux; + // zval *z_mux_id = call_mux_method(z_mux, "getid", sizeof("getid") , ); + + zval *z_mux_id = NULL; + zend_call_method( &z_mux, ce_r3_mux, &fe_getid, "getid", strlen("getid"), &z_mux_id, 0, NULL, NULL TSRMLS_CC ); + + if ( z_mux_id == NULL || Z_TYPE_P(z_mux_id) == IS_NULL ) { + php_error(E_ERROR, "Mux id is required. got NULL."); + } + + // create pattern + zval *z_pattern = NULL; + MAKE_STD_ZVAL(z_pattern); + ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1); // duplicate + + zval *z_retval = NULL; + zend_call_method_with_3_params( &this_ptr, ce_r3_mux, &fe_add, "add", + strlen("add"), &z_retval, 3, z_pattern, z_mux_id, z_options TSRMLS_CC); + + + zval *z_submux_array = zend_read_property( ce_r3_mux, this_ptr , "submux", sizeof("submux") - 1, 1 TSRMLS_CC); + add_index_zval(z_submux_array, Z_LVAL_P(z_mux_id) , z_mux); + + // release zvals + zval_ptr_dtor(&z_mux_id); + // zval_ptr_dtor(&z_pattern); + } +} + +PHP_METHOD(Mux, getSubMux) { + long submux_id = 0; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &submux_id ) == FAILURE) { + RETURN_FALSE; + } + + zval *z_submux_array; + zval **z_submux; + z_submux_array = zend_read_property( Z_OBJCE_P(this_ptr), this_ptr, "submux", sizeof("submux")-1, 1 TSRMLS_CC); + + if ( zend_hash_index_find( Z_ARRVAL_P(z_submux_array), submux_id , (void**) &z_submux) == SUCCESS ) { + *return_value = **z_submux; + zval_copy_ctor(return_value); + return; + } + RETURN_FALSE; +} + + +PHP_METHOD(Mux, getRequestMethodConstant) { + char *req_method = NULL, *mthit, *mthp; + long req_method_const = 0; + long req_method_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &req_method, &req_method_len) != FAILURE) { + mthit = mthp = estrndup(req_method, strlen(req_method)); + while(( *mthit = toupper(*mthit))) mthit++; + + if (strcmp(mthp, "GET") == 0) { + req_method_const = REQ_METHOD_GET; + } else if (strcmp(mthp, "POST") == 0) { + req_method_const = REQ_METHOD_POST; + } else if (strcmp(mthp, "PUT") == 0) { + req_method_const = REQ_METHOD_PUT; + } else if (strcmp(mthp, "DELETE") == 0) { + req_method_const = REQ_METHOD_DELETE; + } else if (strcmp(mthp, "HEAD") == 0) { + req_method_const = REQ_METHOD_HEAD; + } else if (strcmp(mthp, "OPTIONS") == 0) { + req_method_const = REQ_METHOD_OPTIONS; + } else if (strcmp(mthp, "PATCH") == 0) { + req_method_const = REQ_METHOD_PATCH; + } + + efree(req_method); + } + + RETURN_LONG(req_method_const); +} + +PHP_METHOD(Mux, getRoute) { + char * route_id = NULL; + int route_id_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &route_id, &route_id_len ) == FAILURE) { + RETURN_FALSE; + } + + zval *z_routes_by_id = NULL; + zval **z_route = NULL; + z_routes_by_id = zend_read_property( ce_r3_mux , this_ptr, "routesById", sizeof("routesById")-1, 1 TSRMLS_CC); + + // php_var_dump(&z_routes_by_id, 1 TSRMLS_CC); + if ( zend_hash_find( Z_ARRVAL_P(z_routes_by_id) , route_id, route_id_len + 1, (void**) &z_route ) == SUCCESS ) { + *return_value = **z_route; + zval_copy_ctor(return_value); + return; + } + RETURN_NULL(); +} + +PHP_METHOD(Mux, setRoutes) { + zval * routes; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &routes ) == FAILURE) { + RETURN_FALSE; + } + zend_update_property(ce_r3_mux , this_ptr, "routes", sizeof("routes")-1, routes TSRMLS_CC); + RETURN_TRUE; +} + +PHP_METHOD(Mux, getRoutes) { + zval *z_routes; + z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + *return_value = *z_routes; + zval_copy_ctor(return_value); +} + +PHP_METHOD(Mux, export) { + smart_str buf = {0}; + php_var_export_ex(&this_ptr, 0, &buf TSRMLS_CC); + smart_str_0 (&buf); + RETVAL_STRINGL(buf.c, buf.len, 0); +} + +PHP_METHOD(Mux, getId) { + zval *z_id; + long counter = 0; + + z_id = zend_read_property(ce_r3_mux, getThis(), "id", sizeof("id")-1, 1 TSRMLS_CC); + + if ( z_id != NULL && Z_TYPE_P(z_id) != IS_NULL ) { + RETURN_LONG( Z_LVAL_P(z_id) ); + } + + zval *rv = NULL; + zend_call_method( NULL, ce_r3_mux, NULL, "generate_id", strlen("generate_id"), &rv, 0, NULL, NULL TSRMLS_CC ); + + if ( rv ) { + counter = Z_LVAL_P(rv); + zend_update_property_long(ce_r3_mux, this_ptr, "id" , sizeof("id") - 1, counter TSRMLS_CC); + zval_ptr_dtor(&rv); + } + RETURN_LONG(counter); +} + +PHP_METHOD(Mux, length) { + zval *z_routes; + z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + long length = zend_hash_num_elements( Z_ARRVAL_P(z_routes) ); + + RETURN_LONG(length); +} + +PHP_METHOD(Mux, sort) { + zval *z_routes; + z_routes = zend_read_property(Z_OBJCE_P(this_ptr) , this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + zval *retval_ptr = NULL; + + zval *z_sort_callback = NULL; + MAKE_STD_ZVAL(z_sort_callback); + ZVAL_STRING( z_sort_callback, "r3_sort_routes" , 1 ); + + Z_SET_ISREF_P(z_routes); + zend_call_method( NULL, NULL, NULL, "usort", strlen("usort"), &retval_ptr, 2, + z_routes, z_sort_callback TSRMLS_CC ); + zval_ptr_dtor(&z_sort_callback); + if (retval_ptr) { + zval_ptr_dtor(&retval_ptr); + } +} + +PHP_METHOD(Mux, compile) { + char *filename; + int filename_len; + zend_bool sort_before_compile = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &filename, &filename_len, &sort_before_compile) == FAILURE) { + RETURN_FALSE; + } + + if (sort_before_compile) { + zval *z_routes; + z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + Z_SET_ISREF_P(z_routes); + // duplicated code to sort method + zval *rv = NULL; + zval *z_sort_callback = NULL; + MAKE_STD_ZVAL(z_sort_callback); + ZVAL_STRING( z_sort_callback, "r3_sort_routes" , 1 ); + + zend_call_method( NULL, NULL, NULL, "usort", strlen("usort"), &rv, 2, + z_routes, z_sort_callback TSRMLS_CC ); + zval_ptr_dtor(&z_sort_callback); // recycle sort callback zval + // php_error(E_ERROR,"route sort failed."); + // zend_update_property(ce_r3_mux, getThis(), "routes", sizeof("routes")-1, z_routes TSRMLS_CC); + } + + // $code = 'export() . ';'; + + // get export method function entry + zend_function *fe_export; + if ( zend_hash_quick_find( &Z_OBJCE_P(this_ptr)->function_table, "export", sizeof("export"), zend_inline_hash_func(ZEND_STRS("export")), (void **) &fe_export) == FAILURE ) { + php_error(E_ERROR, "export method not found"); + } + + // call export method + zval *compiled_code = NULL; + zend_call_method( &this_ptr, Z_OBJCE_P(this_ptr) , &fe_export, "export", strlen("export"), &compiled_code, 0, NULL, NULL TSRMLS_CC ); + + if ( compiled_code == NULL || Z_TYPE_P(compiled_code) == IS_NULL ) { + php_error(E_ERROR, "Cannot compile routes."); + } + + + int buf_len = Z_STRLEN_P(compiled_code) + strlen("function_table, "match", sizeof("match"), zend_inline_hash_func(ZEND_STRS("match")), (void **) &fe); + zend_call_method( &this_ptr, ce_r3_mux, &fe, "match", strlen("match"), &z_return_route, 1, z_path, NULL TSRMLS_CC ); + + if ( ! z_return_route || Z_TYPE_P(z_return_route) == IS_NULL ) { + zval_ptr_dtor(&z_path); + RETURN_NULL(); + } + + + // read data from matched route + zval **z_pcre; + zval **z_pattern; + zval **z_callback; + zval **z_options; + + zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 0 , (void**) &z_pcre ); + zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 1 , (void**) &z_pattern ); + zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 2 , (void**) &z_callback ); + zend_hash_index_find( Z_ARRVAL_P(z_return_route) , 3 , (void**) &z_options ); + + zval *z_submux_array = NULL; + zval **z_submux = NULL; + + zval *z_retval = NULL; + + // dispatch to submux if the callback is an ID. + if ( Z_TYPE_PP(z_callback) == IS_LONG ) { + z_submux_array = zend_read_property( ce_r3_mux, this_ptr, "submux", sizeof("submux")-1, 1 TSRMLS_CC); + + if ( z_submux_array == NULL ) { + zend_throw_exception(ce_r3_exception, "submux property is null", 0 TSRMLS_CC); + return; + } + + if ( zend_hash_index_find( Z_ARRVAL_P(z_submux_array), Z_LVAL_PP(z_callback) , (void**) &z_submux) == FAILURE ) { + zend_throw_exception(ce_r3_exception, "submux not found", 0 TSRMLS_CC); + return; + } + + + if ( z_submux == NULL || *z_submux == NULL ) { + zend_throw_exception(ce_r3_exception, "submux not found", 0 TSRMLS_CC); + return; + } + + // php_var_dump(z_submux, 1 TSRMLS_CC); + + // $matchedString = $route[3]['vars'][0]; + // return $submux->dispatch(substr($path, strlen($matchedString)) + if ( Z_BVAL_PP(z_pcre) ) { + zval **z_route_vars = NULL; + zval **z_route_vars_0 = NULL; + zval *z_substr; + + if ( zend_hash_quick_find( Z_ARRVAL_PP(z_options) , "vars", sizeof("vars"), zend_inline_hash_func(ZEND_STRS("vars")), (void**) &z_route_vars ) == FAILURE ) { + php_error(E_ERROR, "require route vars"); + RETURN_FALSE; + } + if ( zend_hash_index_find( Z_ARRVAL_PP(z_options) , 0 , (void**) &z_route_vars_0 ) == FAILURE ) { + php_error(E_ERROR, "require route vars[0]"); + RETURN_FALSE; + } + + MAKE_STD_ZVAL(z_substr); + ZVAL_STRING(z_substr, path + Z_STRLEN_PP(z_route_vars_0), 1); + + z_retval = call_mux_method( *z_submux, "dispatch" , sizeof("dispatch"), 1 , z_substr, NULL, NULL TSRMLS_CC); + zval_ptr_dtor(&z_substr); + + if (z_retval) { + Z_ADDREF_P(z_retval); + *return_value = *z_retval; + zval_copy_ctor(return_value); + } + // zval_ptr_dtor(&z_return_route); + RETURN_FALSE; + return; + + } else { + zval *z_substr; + + MAKE_STD_ZVAL(z_substr); + ZVAL_STRING(z_substr, path + Z_STRLEN_PP(z_pattern), 1); + + // return $submux->dispatch( + // substr($path, strlen($route[1])) + // ); + + z_retval = call_mux_method( *z_submux, "dispatch" , sizeof("dispatch"), 1 , z_substr, NULL, NULL TSRMLS_CC); + zval_ptr_dtor(&z_substr); + + if ( z_retval ) { + Z_ADDREF_P(z_retval); + *return_value = *z_retval; + zval_copy_ctor(return_value); + } + // zval_ptr_dtor(&z_return_route); + return; + + } + } + + if ( z_return_route ) { + *return_value = *z_return_route; + zval_copy_ctor(return_value); + } + zval_ptr_dtor(&z_path); + zval_ptr_dtor(&z_return_route); + return; +} + +PHP_METHOD(Mux, match) { + char *path; + int path_len; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &path, &path_len) == FAILURE) { + RETURN_FALSE; + } + + zval **z_route_pp = NULL; + zval *z_route = NULL; + if ( zend_hash_find( Z_ARRVAL_P( zend_read_property(ce_r3_mux, this_ptr, "staticRoutes", sizeof("staticRoutes") - 1, 1 TSRMLS_CC) ), path, path_len, (void**)&z_route_pp) == SUCCESS ) { + if ( Z_TYPE_PP(z_route_pp) != IS_NULL ) { + *return_value = **z_route_pp; + Z_ADDREF_PP(z_route_pp); + zval_copy_ctor(return_value); + return; + } + } + z_route = php_r3_match(zend_read_property(ce_r3_mux , this_ptr , "routes", sizeof("routes")-1, 1 TSRMLS_CC), path, path_len TSRMLS_CC); + if ( z_route != NULL ) { + *return_value = *z_route; + zval_copy_ctor(return_value); + Z_ADDREF_P(z_route); + return; + } + RETURN_NULL(); +} + +PHP_METHOD(Mux, appendRoute) { + char *pattern; + int pattern_len; + zval *z_callback; + zval *z_options; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a", &pattern, &pattern_len, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + + zval *z_routes; + zval *z_new_routes; + + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init(z_options); + } + if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init(z_options); + } + + z_routes = zend_read_property(Z_OBJCE_P(this_ptr), this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + MAKE_STD_ZVAL(z_new_routes); + array_init_size(z_new_routes, 4); + + add_index_bool(z_new_routes, 0 , 0); // pcre flag == false + add_index_stringl( z_new_routes, 1 , pattern , pattern_len, 1); + add_index_zval( z_new_routes, 2 , z_callback); + add_index_zval( z_new_routes, 3 , z_options); + + add_next_index_zval(z_routes, z_new_routes); +} + + +PHP_METHOD(Mux, appendPCRERoute) { + char *pattern; + int pattern_len; + zval *z_callback; + zval *z_options; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a", &pattern, &pattern_len, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init(z_options); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + array_init(z_options); + } + + zval *z_pattern = NULL; + zval *z_routes; + + MAKE_STD_ZVAL(z_pattern); + ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1); + + z_routes = zend_read_property(Z_OBJCE_P(this_ptr), getThis(), "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + zend_class_entry **ce_pattern_compiler = get_pattern_compiler_ce(TSRMLS_C); + if ( ce_pattern_compiler == NULL ) { + RETURN_FALSE; + } + + zval *rv = NULL; + zend_call_method( NULL, *ce_pattern_compiler, NULL, "compile", strlen("compile"), &rv, 1, z_pattern, NULL TSRMLS_CC ); + + if ( rv == NULL || Z_TYPE_P(rv) == IS_NULL ) { + zend_throw_exception(ce_r3_exception, "Can not compile route pattern", 0 TSRMLS_CC); + RETURN_FALSE; + } + add_next_index_zval(z_routes, rv); +} + + +inline void mux_add_route(INTERNAL_FUNCTION_PARAMETERS) +{ + char *pattern; + int pattern_len; + + zval *z_callback = NULL; + zval *z_options = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|a", &pattern, &pattern_len, &z_callback, &z_options) == FAILURE) { + RETURN_FALSE; + } + + // $pcre = strpos($pattern,':') !== false; + char *found = find_place_holder(pattern, pattern_len); + + + if ( z_options == NULL ) { + MAKE_STD_ZVAL(z_options); + array_init(z_options); + } else if ( Z_TYPE_P(z_options) == IS_NULL ) { + // make it as an array + array_init(z_options); + } + + // Generalize callback variable + if ( Z_TYPE_P(z_callback) == IS_STRING ) { + if ( strpos( Z_STRVAL_P(z_callback), ":" ) != -1 ) { + zval *delim; + MAKE_STD_ZVAL(delim); + ZVAL_STRINGL(delim, ":" , 1 , 1); + + zval *rv; + MAKE_STD_ZVAL(rv); + array_init(rv); + php_explode(delim, z_callback, rv, 2); + + z_callback = rv; + // zval_copy_ctor(z_callback); + zval_ptr_dtor(&delim); + } + } + + + zval * z_routes = zend_read_property(ce_r3_mux, this_ptr, "routes", sizeof("routes")-1, 1 TSRMLS_CC); + + // PCRE pattern here + if ( found ) { + zval *z_pattern = NULL; + MAKE_STD_ZVAL(z_pattern); + ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1); + + zval *z_compiled_route = compile_route_pattern(z_pattern, z_options, NULL TSRMLS_CC); + if ( z_compiled_route == NULL ) { + zend_throw_exception(ce_r3_exception, "Unable to compile route pattern.", 0 TSRMLS_CC); + RETURN_FALSE; + } + zval_ptr_dtor(&z_pattern); + + zval **z_compiled_route_pattern; + if ( zend_hash_quick_find( Z_ARRVAL_P(z_compiled_route) , "compiled", sizeof("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&z_compiled_route_pattern) == FAILURE ) { + zend_throw_exception(ce_r3_exception, "Unable to find compiled pattern.", 0 TSRMLS_CC); + RETURN_FALSE; + } + Z_ADDREF_P(z_callback); + + zval *z_new_routes; + MAKE_STD_ZVAL(z_new_routes); + array_init_size(z_new_routes, 4); + + add_index_bool(z_new_routes, 0 , 1); // pcre flag == false + add_index_zval(z_new_routes, 1, *z_compiled_route_pattern); + add_index_zval(z_new_routes, 2 , z_callback); + add_index_zval(z_new_routes, 3, z_compiled_route); + add_next_index_zval(z_routes, z_new_routes); + + + zval **z_route_id; + if ( zend_hash_quick_find( Z_ARRVAL_P(z_options) , "id", sizeof("id"), zend_inline_hash_func(ZEND_STRS("id")), (void**)&z_route_id) == SUCCESS ) { + zval * z_routes_by_id = zend_read_property(ce_r3_mux, this_ptr, "routesById", sizeof("routesById")-1, 1 TSRMLS_CC); + add_assoc_zval(z_routes_by_id, Z_STRVAL_PP(z_route_id), z_new_routes); + } + } else { + Z_ADDREF_P(z_options); // reference it so it will not be recycled. + Z_ADDREF_P(z_callback); + + zval *z_new_route; + MAKE_STD_ZVAL(z_new_route); + array_init_size(z_new_route, 4); + + /* make the array: [ pcreFlag, pattern, callback, options ] */ + add_index_bool(z_new_route, 0 , 0); // pcre flag == false + add_index_stringl( z_new_route, 1 , pattern, pattern_len , 1 ); + add_index_zval( z_new_route, 2 , z_callback); + add_index_zval( z_new_route, 3 , z_options); + add_next_index_zval(z_routes, z_new_route); + + // if there is no option specified in z_options, we can add the route to our static route hash + if ( zend_hash_num_elements(Z_ARRVAL_P(z_options)) ) { + zval * z_static_routes = zend_read_property(ce_r3_mux, this_ptr, "staticRoutes", sizeof("staticRoutes")-1, 1 TSRMLS_CC); + if ( z_static_routes ) { + add_assoc_zval(z_static_routes, pattern, z_new_route); + } + } + + zval **z_route_id; + if ( zend_hash_quick_find( Z_ARRVAL_P(z_options) , "id", sizeof("id"), zend_inline_hash_func(ZEND_STRS("id")), (void**)&z_route_id) == SUCCESS ) { + zval * z_routes_by_id = zend_read_property(ce_r3_mux, this_ptr, "routesById", sizeof("routesById")-1, 1 TSRMLS_CC); + + /* + zval *id_route = NULL; + ALLOC_ZVAL(id_route); + ZVAL_COPY_VALUE(id_route, z_new_route); + zval_copy_ctor(id_route); + add_assoc_zval(z_routes_by_id, Z_STRVAL_PP(z_route_id), id_route); + */ + + add_assoc_zval(z_routes_by_id, Z_STRVAL_PP(z_route_id), z_new_route); + } + } +} + + +PHP_METHOD(Mux, add) { + mux_add_route(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} + +PHP_METHOD(Mux, any) { + mux_add_route(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} + diff --git a/php/r3/r3_mux.h b/php/r3/r3_mux.h new file mode 100644 index 0000000..5f96911 --- /dev/null +++ b/php/r3/r3_mux.h @@ -0,0 +1,64 @@ +#ifndef PHP_MUX_H +#define PHP_MUX_H 1 + +#include "php.h" +#include "string.h" +#include "main/php_main.h" +#include "Zend/zend_API.h" +#include "Zend/zend_variables.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_object_handlers.h" +#include "ext/pcre/php_pcre.h" +#include "ext/standard/php_string.h" +#include "php_r3.h" +#include "r3_functions.h" + +extern zend_class_entry *ce_r3_mux; + +void r3_init_mux(TSRMLS_D); + +zend_class_entry ** get_pattern_compiler_ce(TSRMLS_D); +zval * compile_route_pattern(zval *z_pattern, zval *z_options, zend_class_entry **ce_pattern_compiler TSRMLS_DC); + +extern inline zval * call_mux_method(zval * object , char * method_name , int method_name_len, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC); + +extern inline void mux_add_route(INTERNAL_FUNCTION_PARAMETERS); + + +PHP_METHOD(Mux, __construct); +PHP_METHOD(Mux, __destruct); +PHP_METHOD(Mux, getId); +PHP_METHOD(Mux, add); +PHP_METHOD(Mux, any); +PHP_METHOD(Mux, length); +PHP_METHOD(Mux, compile); +PHP_METHOD(Mux, sort); +PHP_METHOD(Mux, appendRoute); +PHP_METHOD(Mux, appendPCRERoute); +PHP_METHOD(Mux, setRoutes); +PHP_METHOD(Mux, getRoutes); +PHP_METHOD(Mux, getRoute); +PHP_METHOD(Mux, match); +PHP_METHOD(Mux, dispatch); +PHP_METHOD(Mux, getSubMux); +PHP_METHOD(Mux, getRequestMethodConstant); +PHP_METHOD(Mux, export); +PHP_METHOD(Mux, mount); + +PHP_METHOD(Mux, get); +PHP_METHOD(Mux, post); +PHP_METHOD(Mux, put); +PHP_METHOD(Mux, delete); +PHP_METHOD(Mux, head); +PHP_METHOD(Mux, patch); +PHP_METHOD(Mux, options); + +PHP_METHOD(Mux, __set_state); + +// static method +PHP_METHOD(Mux, generate_id); + + + +#endif diff --git a/php/r3/r3_persistent.c b/php/r3/r3_persistent.c new file mode 100644 index 0000000..51994b9 --- /dev/null +++ b/php/r3/r3_persistent.c @@ -0,0 +1,54 @@ +#include "php.h" +#include "php_r3.h" +#include "r3_persistent.h" +#include "php_expandable_mux.h" + +inline int persistent_store(char *key, int key_len, int list_type, void * val TSRMLS_DC) +{ + zend_rsrc_list_entry new_le; + zend_rsrc_list_entry *le; + + // store it if it's not in persistent_list + if ( zend_hash_find(&EG(persistent_list), key, key_len + 1, (void**) &le) == SUCCESS ) { + zend_hash_del(&EG(persistent_list), key, key_len + 1); + } + new_le.type = list_type; + new_le.ptr = val; + return zend_hash_update(&EG(persistent_list), key, key_len + 1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL); +} + +inline void * persistent_fetch(char *key, int key_len TSRMLS_DC) +{ + zend_rsrc_list_entry *le; + if ( zend_hash_find(&EG(persistent_list), key, key_len + 1, (void**) &le) == SUCCESS ) { + return le->ptr; + } + return NULL; +} + + +inline void * r3_persistent_fetch(char *ns, char *key TSRMLS_DC) +{ + char *newkey; + int newkey_len; + void *ptr; + newkey_len = spprintf(&newkey, 0, "r3_%s_%s", ns, key); + ptr = persistent_fetch(newkey, newkey_len TSRMLS_CC); + efree(newkey); + return ptr; +} + +/* + * Store persistent value with r3 namespace. + */ +inline int r3_persistent_store(char *ns, char *key, int list_type, void * val TSRMLS_DC) +{ + char *newkey; + int newkey_len; + int status; + newkey_len = spprintf(&newkey, 0, "r3_%s_%s", ns, key); + status = persistent_store(newkey, newkey_len, list_type, val TSRMLS_CC); + efree(newkey); + return status; +} + diff --git a/php/r3/r3_persistent.h b/php/r3/r3_persistent.h new file mode 100644 index 0000000..4a827a5 --- /dev/null +++ b/php/r3/r3_persistent.h @@ -0,0 +1,10 @@ +#ifndef R3_PERSISTENT_H +#define R3_PERSISTENT_H 1 + +extern inline int persistent_store(char *key, int key_len, int list_type, void * val TSRMLS_DC); +extern inline int r3_persistent_store(char *ns, char *key, int list_type, void * val TSRMLS_DC) ; + +extern inline void * persistent_fetch(char *key, int key_len TSRMLS_DC); +extern inline void * r3_persistent_fetch(char *ns, char *key TSRMLS_DC); + +#endif diff --git a/tests/bench_str.csv b/tests/bench_str.csv index 1452016..402f900 100644 --- a/tests/bench_str.csv +++ b/tests/bench_str.csv @@ -42,3 +42,7 @@ 1400245281,6205337.09 1400245289,6217947.43 1400245311,6423151.81 +1400251162,5826610.20 +1400251172,6039034.62 +1400251256,6219533.32 +1400251264,6015717.76