437 lines
14 KiB
C
437 lines
14 KiB
C
#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);
|
|
}
|
|
|
|
|
|
|