#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); }