diff --git a/README.md b/README.md index 6b1d60a..17e629b 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,3 @@ For performace details, please refer to the wiki. ### Support opensearch@baidu.com -### User List -If sofa-pbrpc is used in your product, please record your user information in the belowing table with a new line, -so that communication between users can be more convenient, and new features will be better notified. - -| company | product line | cluster scale | owner | recommendation | -| -------:| ------------:| -------------:| -----:| --------------:| diff --git a/php/README b/php/README new file mode 100644 index 0000000..778c2ea --- /dev/null +++ b/php/README @@ -0,0 +1,36 @@ +This directory contains the php extension for sofa-pbrpc client. + +Installation +============ + +1) Make sure your PHP version is at least 5.4 and you have installed sofa-pbrpc library in sofa-pbrpc output + +2) Because this lib depends on the "allegro/php-protobuf" lib to serialize and unserialize data, + + You should install the allegro/php-protobuf and use this lib in your PHP codes and you can get + + "allegro/php-protobuf" on this page: + + https://github.com/allegro/php-protobuf + +3) Change the dependences path: + + Change the dependences path "RPCLIB_DIR" and "PROTOBUFLIB_DIR" in comfig.m4 + +4) Build the extension + + Run "/usr/local/php/bin/phpize ./config.m4" + + "./configure --with-php-config=/usr/local/php/bin/php-config" to generate Makefile + + Run "make clean && make -j10 && make install" to build the extension + + You can get extension in ./modules + +5) Generate PHP interface + + You can generate PHP interface like this: + + "php -c /usr/etc/php.ini protoc-php.php test.proto" + + You can get a simple usage in ./sample diff --git a/php/common.h b/php/common.h new file mode 100644 index 0000000..9c4fbf2 --- /dev/null +++ b/php/common.h @@ -0,0 +1,33 @@ +// Copyright (c) 2016 Baidu.com, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Author: zhangdi05@baidu.com (Zhangdi Di) + +#ifndef COMMON_H +#define COMMON_H + +#include + +namespace sofa_php_ext +{ + +#define EXT_ZEND_FOREACH(iter, hash) \ + for (zend_hash_internal_pointer_reset_ex((hash), (iter)); \ + zend_hash_has_more_elements_ex((hash), (iter)) == SUCCESS; \ + zend_hash_move_forward_ex((hash), (iter))) + +const static std::string PB_VALUES_PROPERTY = "values"; +const static std::string PB_FIELDS_METHOD = "fields"; +const static std::string PB_FIELD_TYPE = "type"; +const static std::string PB_FIELD_REQUIRED = "required"; +const static std::string PB_FIELD_REPEATED = "repeated"; +const static std::string PB_FIELD_NAME = "name"; +const static std::string CLASS_TYPE = "class_type"; +const static std::string PARSE_FUNCTION = "parseFromString"; +static const char* IMPL = "impl"; + +} +#endif // COMMON_H + +/* vim: set ts=4 sw=4 sts=4 tw=100 */ diff --git a/php/config.m4 b/php/config.m4 new file mode 100644 index 0000000..ac08239 --- /dev/null +++ b/php/config.m4 @@ -0,0 +1,24 @@ +dnl $Id$ +dnl config.m4 for extension sofa-pbrpc +PHP_ARG_ENABLE(sofa_pbrpc, whether to enable sofa_pbrpc support, +[ --enable-sofa_pbprc enable sofa_pbrpc support]) + +if test "$PHP_SOFAPBRPC" != "no"; then + RPC_LIBNAME=sofa-pbrpc + RPCLIB_DIR=/usr/local/sofa-pbrpc + PROTOBUF_LIBNAME=protobuf + PROTOBUFLIB_DIR=/usr/local/protobuf + PHP_REQUIRE_CXX() + PHP_SUBST(SOFA_PBRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(stdc++, 1, SOFA_PBRPC_SHARED_LIBADD) + + PHP_ADD_LIBRARY(sofa-pbrpc, 1, SOFA_PBRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY_WITH_PATH($RPC_LIBNAME, $RPCLIB_DIR/output/lib) + PHP_ADD_INCLUDE($RPCLIB_DIR/output/include) + PHP_ADD_LIBRARY(protobuf, 1, SOFA_PBRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY_WITH_PATH($PROTOBUF_LIBNAME, $PROTOBUFLIB_DIR/lib) + PHP_ADD_INCLUDE($PROTOBUFLIB_DIR/include) + PHP_NEW_EXTENSION(sofa_pbrpc, ext_rpc_service_stub.cc ext_rpc_service_stub_impl.cc, $ext_shared) +fi + +PHP_C_BIGENDIAN() diff --git a/php/ext_rpc_service_stub.cc b/php/ext_rpc_service_stub.cc new file mode 100644 index 0000000..ddaa081 --- /dev/null +++ b/php/ext_rpc_service_stub.cc @@ -0,0 +1,342 @@ +// Copyright (c) 2016 Baidu.com, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Author: zhangdi05@baidu.com (Zhangdi Di) + +extern "C" { +#include +#include +#include +#include +#include +} + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef add_method +#undef add_method +#include +#endif + +#include "ext_rpc_service_stub.h" +#include "ext_rpc_service_stub_impl.h" + +namespace sofa_php_ext +{ + +#define PB_CONSTANT(name) \ + zend_declare_class_constant_long(rpc_service_stub_ce, #name, sizeof(#name) - 1, name TSRMLS_CC) + +zend_object_handlers rpc_service_stub_object_handlers; +struct rpc_service_stub_object +{ + zend_object std; + PhpRpcServiceStubImpl* stub_impl; +}; + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_initservice, 0, 0, 3) + ZEND_ARG_INFO(0, address_str) + ZEND_ARG_INFO(0, package_name) + ZEND_ARG_INFO(0, service_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_settimeout, 0, 0, 1) + ZEND_ARG_INFO(0, timeout) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_getfailed, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_geterrortext, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_registermethod, 0, 0, 3) + ZEND_ARG_INFO(0, method_name) + ZEND_ARG_INFO(0, request) + ZEND_ARG_INFO(0, response) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_initmethod, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(rpc_service_stub_arginfo_callmethod, 0, 0, 4) + ZEND_ARG_INFO(0, method_name) + ZEND_ARG_INFO(0, request) + ZEND_ARG_INFO(0, response) + ZEND_ARG_INFO(0, closure) +ZEND_END_ARG_INFO() + +void rpc_service_stub_free_storage(void *object TSRMLS_DC) +{ + rpc_service_stub_object *obj = (rpc_service_stub_object*)object; + delete obj->stub_impl; + zend_hash_destroy(obj->std.properties); + FREE_HASHTABLE(obj->std.properties); + efree(obj); +} + +zend_object_value rpc_service_stub_create_handler(zend_class_entry* type TSRMLS_DC) +{ + zval* tmp; + zend_object_value retval; + rpc_service_stub_object* obj + = (rpc_service_stub_object*)emalloc(sizeof(rpc_service_stub_object)); + memset(obj, 0, sizeof(rpc_service_stub_object)); + zend_object_std_init(&obj->std, type TSRMLS_CC); +#if PHP_VERSION_ID < 50399 + zend_hash_copy(obj->std.properties, + &(type->default_properties_table), + (copy_ctor_func_t)zval_add_ref, + (void*)&tmp, + sizeof(zval*)); +#else + object_properties_init(&obj->std, type); +#endif + retval.handle = zend_objects_store_put(obj, + (zend_objects_store_dtor_t) zend_objects_destroy_object, + (zend_objects_free_object_storage_t) rpc_service_stub_free_storage, + NULL TSRMLS_CC); + retval.handlers = &rpc_service_stub_object_handlers; + return retval; +} + +PHP_METHOD(PhpRpcServiceStub, InitService) +{ + srand((unsigned)time(NULL)); + zval* stub_zval = getThis(); + char* address_str; + int address_str_len; + char* package_name; + int package_len; + char* service_name; + int service_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "sss", + &address_str, + &address_str_len, + &package_name, + &package_len, + &service_name, + &service_len) == FAILURE) + { + SLOG(ERROR, "create stub failed for bad parameters"); + return; + } + std::string server_addr_str(address_str, address_str_len); + std::string package_name_str(package_name, package_len); + std::string service_name_str(service_name, service_len); + + PhpRpcServiceStubImpl* _impl + = new PhpRpcServiceStubImpl(server_addr_str, package_name_str, service_name_str); + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (!stub_obj) + { + RETURN_FALSE; + } + stub_obj->stub_impl = _impl; + RETURN_TRUE; +} + +PHP_METHOD(PhpRpcServiceStub, SetTimeout) +{ + long timeout; + zval* stub_zval = getThis(); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &timeout) == FAILURE) + { + SLOG(ERROR, "set timeout failed for bad parameters"); + return; + } + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (stub_obj && stub_obj->stub_impl) + { + PhpRpcServiceStubImpl* _impl = stub_obj->stub_impl; + _impl->SetTimeout(timeout); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(PhpRpcServiceStub, GetFailed) +{ + zval* stub_zval = getThis(); + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (stub_obj && stub_obj->stub_impl) + { + PhpRpcServiceStubImpl* _impl = stub_obj->stub_impl; + RETURN_BOOL(_impl->Failed()); + } + RETURN_FALSE; +} + +PHP_METHOD(PhpRpcServiceStub, GetErrorText) +{ + zval* stub_zval = getThis(); + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (stub_obj && stub_obj->stub_impl) + { + PhpRpcServiceStubImpl* _impl = stub_obj->stub_impl; + std::string err_text = _impl->ErrorText(); + RETURN_STRINGL(err_text.c_str(), err_text.length(), 1); + } + RETURN_EMPTY_STRING(); +} + +PHP_METHOD(PhpRpcServiceStub, RegisterMethod) +{ + char* method_name; + int method_length; + zval* request; + zval* response; + zval* stub_zval = getThis(); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "szz", + &method_name, + &method_length, + &request, + &response) == FAILURE) + { + SLOG(ERROR, "register method failed for bad parameters"); + return; + } + std::string method_name_str(method_name, method_length); + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (stub_obj && stub_obj->stub_impl) + { + PhpRpcServiceStubImpl* _impl = stub_obj->stub_impl; + RETURN_BOOL(_impl->RegisterMethod(method_name_str, request, response)); + } + RETURN_FALSE; +} + +PHP_METHOD(PhpRpcServiceStub, InitMethods) +{ + zval* stub_zval = getThis(); + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (stub_obj && stub_obj->stub_impl) + { + PhpRpcServiceStubImpl* _impl = stub_obj->stub_impl; + RETURN_BOOL(_impl->InitMethods()); + } + RETURN_FALSE; +} + +PHP_METHOD(PhpRpcServiceStub, CallMethod) +{ + char* method_name; + int method_length; + zval* request_zval; + zval* response_zval; + zval* controller_zval; + zval* closure_zval; + zval* stub_zval = getThis(); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "szzz", + &method_name, + &method_length, + &request_zval, + &response_zval, + &closure_zval) == FAILURE) + { + SLOG(ERROR, "call method failed for bad parameters"); + return; + } + + rpc_service_stub_object* stub_obj + = (rpc_service_stub_object*)zend_object_store_get_object(stub_zval TSRMLS_CC); + if (stub_obj && stub_obj->stub_impl) + { + PhpRpcServiceStubImpl* _impl = stub_obj->stub_impl; + std::string method_name_str(method_name, method_length); + _impl->CallMethod(method_name_str, request_zval, response_zval, closure_zval); + } +} + +const zend_function_entry sofa_pbrpc_functions[] = { + + PHP_ME(PhpRpcServiceStub, InitService, rpc_service_stub_arginfo_initservice, ZEND_ACC_PUBLIC) + PHP_ME(PhpRpcServiceStub, SetTimeout, rpc_service_stub_arginfo_settimeout, ZEND_ACC_PUBLIC) + PHP_ME(PhpRpcServiceStub, GetFailed, rpc_service_stub_arginfo_getfailed, ZEND_ACC_PUBLIC) + PHP_ME(PhpRpcServiceStub, GetErrorText, rpc_service_stub_arginfo_geterrortext, ZEND_ACC_PUBLIC) + PHP_ME(PhpRpcServiceStub, RegisterMethod, rpc_service_stub_arginfo_registermethod, ZEND_ACC_PUBLIC) + PHP_ME(PhpRpcServiceStub, InitMethods, rpc_service_stub_arginfo_initmethod, ZEND_ACC_PUBLIC) + PHP_ME(PhpRpcServiceStub, CallMethod, rpc_service_stub_arginfo_callmethod, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL, 0, 0} +}; + +PHP_MINIT_FUNCTION(sofa_pbrpc) +{ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "PhpRpcServiceStub", sofa_pbrpc_functions); + rpc_service_stub_ce = zend_register_internal_class(&ce TSRMLS_CC); + rpc_service_stub_ce->create_object = rpc_service_stub_create_handler; + memcpy(&rpc_service_stub_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + rpc_service_stub_object_handlers.clone_obj = NULL; + + PB_CONSTANT(PB_TYPE_DOUBLE); + PB_CONSTANT(PB_TYPE_FIXED32); + PB_CONSTANT(PB_TYPE_FIXED64); + PB_CONSTANT(PB_TYPE_FLOAT); + PB_CONSTANT(PB_TYPE_INT); + PB_CONSTANT(PB_TYPE_SIGNED_INT); + PB_CONSTANT(PB_TYPE_STRING); + PB_CONSTANT(PB_TYPE_BOOL); + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(sofa_pbrpc) +{ + return SUCCESS; +} + +PHP_RINIT_FUNCTION(sofa_pbrpc) +{ + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(sofa_pbrpc) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(sofa_pbrpc) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "sofa_pbrpc support", "enabled"); + php_info_print_table_end(); +} + +zend_module_entry sofa_pbrpc_module_entry = { +#if ZEND_MODULE_API_NO >= 20010901 + STANDARD_MODULE_HEADER, +#endif + "sofa_pbrpc", + sofa_pbrpc_functions, + PHP_MINIT(sofa_pbrpc), + PHP_MSHUTDOWN(sofa_pbrpc), + PHP_RINIT(sofa_pbrpc), + PHP_RSHUTDOWN(sofa_pbrpc), + PHP_MINFO(sofa_pbrpc), +#if ZEND_MODULE_API_NO >= 20010901 + "0.1", +#endif + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_SOFA_PBRPC +ZEND_GET_MODULE(sofa_pbrpc) +#endif + +} + +/* vim: set ts=4 sw=4 sts=4 tw=100 */ diff --git a/php/ext_rpc_service_stub.h b/php/ext_rpc_service_stub.h new file mode 100644 index 0000000..9ccdf98 --- /dev/null +++ b/php/ext_rpc_service_stub.h @@ -0,0 +1,38 @@ +// Copyright (c) 2016 Baidu.com, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Author: zhangdi05@baidu.com (Zhangdi Di) + +#ifndef EXT_RPC_SERVICE_STUB_H +#define EXT_RPC_SERVICE_STUB_H + +extern "C" { +#include +#include +#include +} + +namespace sofa_php_ext +{ + +zend_class_entry* rpc_service_stub_ce; + +PHP_METHOD(PhpRpcServiceStub, InitService); + +PHP_METHOD(PhpRpcServiceStub, SetTimeout); + +PHP_METHOD(PhpRpcServiceStub, GetFailed); + +PHP_METHOD(PhpRpcServiceStub, GetErrorText); + +PHP_METHOD(PhpRpcServiceStub, RegisterMethod); + +PHP_METHOD(PhpRpcServiceStub, InitMethods); + +PHP_METHOD(PhpRpcServiceStub, CallMethod); + +} +#endif // EXT_RPC_SERVICE_STUB_H + +/* vim: set ts=4 sw=4 sts=4 tw=100 */ diff --git a/php/ext_rpc_service_stub_impl.cc b/php/ext_rpc_service_stub_impl.cc new file mode 100644 index 0000000..0388c4f --- /dev/null +++ b/php/ext_rpc_service_stub_impl.cc @@ -0,0 +1,1064 @@ +// Copyright (c) 2016 Baidu.com, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Author: zhangdi05@baidu.com (Zhangdi Di) + +#include "common.h" +#include "ext_rpc_service_stub_impl.h" +#include +namespace sofa_php_ext +{ + +sofa::pbrpc::RpcClientOptions g_client_options; +sofa::pbrpc::RpcClient g_client(g_client_options); + +PhpRpcServiceStubImpl::PhpRpcServiceStubImpl(const std::string& server_addr, + const std::string& package_name, + const std::string& service_name) : + _timeout(100), + _server_addr(server_addr), + _package_name(package_name), + _service_name(service_name), + _method_board(NULL), + _pool(NULL), + _file_proto(NULL), + _service_proto(NULL), + _file_descriptor(NULL) +{ + sofa::pbrpc::RpcChannelOptions channel_options; + _channel = new sofa::pbrpc::RpcChannel(&g_client, _server_addr, channel_options); + _cntl = new sofa::pbrpc::RpcController(); + _method_board = new MethodBoard(); + _message_set = new MessageSet(); + _pool = new google::protobuf::DescriptorPool(); + _file_proto = new google::protobuf::FileDescriptorProto(); + _file_proto->set_name(FILE_PROTO); + _file_proto->set_package(_package_name); + _service_proto = _file_proto->add_service(); + _service_proto->set_name(_service_name); +} + +PhpRpcServiceStubImpl::~PhpRpcServiceStubImpl() +{ + delete _channel; + _channel = NULL; + delete _cntl; + _cntl = NULL; + MethodBoard::iterator it = _method_board->begin(); + MethodBoard::iterator end = _method_board->end(); + for (; it != end; ++it) + { + delete it->second; + it->second = NULL; + } + delete _method_board; + _method_board = NULL; + delete _message_set; + _message_set = NULL; + delete _pool; + _pool = NULL; + delete _file_proto; + _file_proto = NULL; +} + +zval* PhpRpcServiceStubImpl::GetFieldDescriptors(zval* obj) +{ + zval method; + zval* descriptors + TSRMLS_FETCH(); + INIT_ZVAL(method); + ZVAL_STRINGL(&method, PB_FIELDS_METHOD.c_str(), PB_FIELDS_METHOD.length(), 0); + if (call_user_function_ex(NULL, &obj, &method, &descriptors, 0, NULL, 0, NULL TSRMLS_CC) != SUCCESS) + { + return NULL; + } + Z_DELREF_P(descriptors); + return descriptors; +} + +zval** PhpRpcServiceStubImpl::GetFieldType(zval** field_descriptor, uint32_t field_number) +{ + zval** field_type; + TSRMLS_FETCH(); + if (zend_hash_find(Z_ARRVAL_PP((field_descriptor)), + PB_FIELD_TYPE.c_str(), PB_FIELD_TYPE.length() + 1, + (void**) &(field_type)) == FAILURE) + { + return NULL; + } + return field_type; +} + +zval** PhpRpcServiceStubImpl::GetRequiredLabel(zval** field_descriptor, uint32_t field_number) +{ + zval** field_required; + TSRMLS_FETCH(); + if (zend_hash_find(Z_ARRVAL_PP((field_descriptor)), + PB_FIELD_REQUIRED.c_str(), + PB_FIELD_REQUIRED.length() + 1, + (void**) &(field_required)) == FAILURE) + { + return NULL; + } + return field_required; +} + +zval** PhpRpcServiceStubImpl::GetRepeatedLabel(zval** field_descriptor, uint32_t field_number) +{ + zval** field_repeated; + TSRMLS_FETCH(); + if (zend_hash_find(Z_ARRVAL_PP((field_descriptor)), + PB_FIELD_REPEATED.c_str(), + PB_FIELD_REPEATED.length() + 1, + (void**) &(field_repeated)) == FAILURE) + { + return NULL; + } + return field_repeated; +} + +const char* PhpRpcServiceStubImpl::GetFieldName(zval** field_descriptor) +{ + zval** field_name; + TSRMLS_FETCH(); + if (zend_hash_find(Z_ARRVAL_PP((field_descriptor)), + PB_FIELD_NAME.c_str(), + PB_FIELD_NAME.length() + 1, + (void**) &(field_name))) + { + return NULL; + } + return (const char*) Z_STRVAL_PP(field_name); +} + +zval** PhpRpcServiceStubImpl::GetUserValues(zval* obj) +{ + zval** values; + TSRMLS_FETCH(); + zend_hash_find((Z_OBJPROP_P(obj)), + PB_VALUES_PROPERTY.c_str(), + PB_VALUES_PROPERTY.length() + 1, + (void**) &(values)); + return values; +} + +void PhpRpcServiceStubImpl::GetMessageType(zval* obj, + std::string& message_type) +{ + zval method; + zval* msg_type; + TSRMLS_FETCH(); + INIT_ZVAL(method); + ZVAL_STRINGL(&method, CLASS_TYPE.c_str(), CLASS_TYPE.length(), 0); + call_user_function_ex(NULL, &obj, &method, &msg_type, 0, NULL, 0, NULL TSRMLS_CC); + convert_to_string(msg_type); + message_type = Z_STRVAL_P(msg_type); +} + +zval* PhpRpcServiceStubImpl::GetSubMessage(zval* obj, + ulong field_number) +{ + zval** tmp_val; + zval** sub_msg; + zval** values; + if ((values = GetUserValues(obj)) == NULL) + { + return NULL; + } + if (zend_hash_index_find((Z_ARRVAL_PP(values)), field_number, (void**) &(sub_msg)) == FAILURE) + { + return NULL; + } + if (Z_TYPE_PP(sub_msg) == IS_ARRAY) + { + if (zend_hash_index_find((Z_ARRVAL_PP(sub_msg)), 0, (void**) &(tmp_val)) == FAILURE) + { + return NULL; + } + sub_msg = tmp_val; + } + return *sub_msg; +} + +int PhpRpcServiceStubImpl::GetSofaFieldType(zval** type, + google::protobuf::FieldDescriptorProto::Type& pb_type) +{ + if (Z_TYPE_PP(type) == IS_STRING) + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_MESSAGE; + } + else if (Z_TYPE_PP(type) == IS_LONG) + { + switch (Z_LVAL_PP(type)) + { + case PB_TYPE_DOUBLE: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_DOUBLE; + break; + } + case PB_TYPE_FIXED32: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_FIXED32; + break; + } + case PB_TYPE_FIXED64: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_FIXED64; + break; + } + case PB_TYPE_FLOAT: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_FLOAT; + break; + } + case PB_TYPE_INT: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_INT32; + break; + } + case PB_TYPE_SIGNED_INT: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_SINT32; + break; + } + case PB_TYPE_STRING: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_STRING; + break; + } + case PB_TYPE_BOOL: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_BOOL; + break; + } + case PB_TYPE_UINT64: + { + pb_type = google::protobuf::FieldDescriptorProto::TYPE_UINT64; + break; + } + default: + SLOG(ERROR, "transform pb type to sofa failed for bad type"); + return -1; + } + } + else + { + SLOG(ERROR, "transform pb type to sofa failed for bad type"); + return -1; + } + return 0; +} + +int PhpRpcServiceStubImpl::ToSofaField(zval** type, + const bool repeated, + zval** value, + google::protobuf::Message* sofa_msg, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection) +{ + if (!reflection || !field) + { + SLOG(ERROR, "transform to sofa field failed for sofa message not initialized"); + return -1; + } + int array_size; + if (repeated) + { + array_size = reflection->FieldSize(*sofa_msg, field); + } + switch (Z_LVAL_PP(type)) + { + case PB_TYPE_DOUBLE: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **) &item, &i); + reflection->AddDouble(sofa_msg, field, Z_DVAL_PP(item)); + } + } + else + { + reflection->SetDouble(sofa_msg, field, Z_DVAL_PP(value)); + } + break; + } + case PB_TYPE_FIXED32: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddUInt32(sofa_msg, field, Z_LVAL_PP(item)); + } + } + else + { + reflection->SetUInt32(sofa_msg, field, Z_LVAL_PP(value)); + } + break; + } + case PB_TYPE_INT: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddInt32(sofa_msg, field, Z_LVAL_PP(item)); + } + } + else + { + reflection->SetInt32(sofa_msg, field, Z_LVAL_PP(value)); + } + break; + } + case PB_TYPE_BOOL: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddBool(sofa_msg, field, Z_LVAL_PP(item)); + } + } + else + { + reflection->SetBool(sofa_msg, field, Z_LVAL_PP(value)); + } + break; + } + case PB_TYPE_FIXED64: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddUInt64(sofa_msg, field, Z_LVAL_PP(item)); + } + } + else + { + reflection->SetUInt64(sofa_msg, field, Z_LVAL_PP(value)); + } + break; + } + case PB_TYPE_FLOAT: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddFloat(sofa_msg, field, Z_DVAL_PP(item)); + } + } + else + { + reflection->SetFloat(sofa_msg, field, Z_DVAL_PP(value)); + } + break; + } + case PB_TYPE_SIGNED_INT: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddInt32(sofa_msg, field, Z_LVAL_PP(item)); + } + } + else + { + reflection->SetInt32(sofa_msg, field, Z_LVAL_PP(value)); + } + break; + } + case PB_TYPE_STRING: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddString(sofa_msg, field, Z_STRVAL_PP(item)); + } + } + else + { + reflection->SetString(sofa_msg, field, Z_STRVAL_PP(value)); + } + break; + } + case PB_TYPE_UINT64: + { + if (repeated) + { + zval** array = value; + zval** item; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_PP(value)) + { + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **)&item, &i); + reflection->AddUInt64(sofa_msg, field, Z_LVAL_PP(item)); + } + } + else + { + reflection->SetUInt64(sofa_msg, field, Z_LVAL_PP(value)); + } + break; + } + default: + SLOG(ERROR, "transform to sofa field failed for bad field"); + return -1; + } + return 0; +} + +int PhpRpcServiceStubImpl::PhpTransformToSofa(zval* user_msg, + google::protobuf::Message* sofa_msg) +{ + HashPosition i; + const google::protobuf::Reflection* reflection = sofa_msg->GetReflection(); + const google::protobuf::Descriptor* message_descriptor = sofa_msg->GetDescriptor(); + + if (!message_descriptor || !reflection) + { + SLOG(ERROR, "transform to sofa message failed for sofa message is not initialized"); + return -1; + } + zval* field_descriptors = GetFieldDescriptors(user_msg); + if (!field_descriptors) + { + SLOG(ERROR, "transform to sofa message failed for field_descriptor is null"); + return -1; + } + zval** values = GetUserValues(user_msg); + EXT_ZEND_FOREACH(&i, Z_ARRVAL_P(field_descriptors)) + { + ulong field_number; + zval** value; + zval** type; + zval** required; + bool repeated = false; + zval** field_descriptor; + zend_hash_get_current_key_ex(Z_ARRVAL_P(field_descriptors), NULL, NULL, &field_number, 0, &i); + zend_hash_get_current_data_ex(Z_ARRVAL_P(field_descriptors), (void **) &field_descriptor, &i); + if (zend_hash_index_find(Z_ARRVAL_PP(values), field_number, (void **) &value) == FAILURE) + { + return -1; + } + if (Z_TYPE_PP(value) == IS_NULL) + { + continue; + } + if (Z_TYPE_PP(value) == IS_ARRAY) + { + repeated = true; + } + if ((type = GetFieldType(field_descriptor, field_number)) == NULL) + { + return -1; + } + const char* field_name = GetFieldName(field_descriptor); + if (!field_name) + { + SLOG(ERROR, "get field_name failed in field %d", field_number); + return -1; + } + const google::protobuf::FieldDescriptor* field + = message_descriptor->FindFieldByName(field_name); + if (!field) + { + SLOG(ERROR, "field %s not found", field_name); + return -1; + } + if (Z_TYPE_PP(type) == IS_LONG) + { + if (Z_TYPE_PP(value) == IS_NULL) + { + if (zend_hash_find(Z_ARRVAL_PP(field_descriptor), + PB_FIELD_REQUIRED.c_str(), + PB_FIELD_REQUIRED.length(), + (void **) &required) == FAILURE) + { + return -1; + } + + if (Z_BVAL_PP(required)) + { + zend_throw_exception_ex(NULL, + 0 TSRMLS_CC, + "'%s' field is required and must be set", + field_name); + return -1; + } + continue; + } + if(ToSofaField(type, repeated, value, sofa_msg, field, reflection) != 0) + { + SLOG(ERROR, "transform to sofa field failed"); + return -1; + } + } + else if(Z_TYPE_PP(type) == IS_STRING) + { + if (repeated) + { + zval** array = value; + HashPosition i; + EXT_ZEND_FOREACH (&i, Z_ARRVAL_PP(value)) + { + zval** item; + google::protobuf::Message* sub_message_item = reflection->AddMessage(sofa_msg, field); + zend_hash_get_current_data_ex(Z_ARRVAL_PP(array), (void **) &item, &i); + PhpTransformToSofa(*item, sub_message_item); + } + } + else + { + google::protobuf::Message* sub_message = reflection->MutableMessage(sofa_msg, field); + PhpTransformToSofa(*value, sub_message); + } + } + } + return 0; +} + +int PhpRpcServiceStubImpl::ToUserField(zval* value, + const google::protobuf::Message* sofa_msg, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection) +{ + if (!field || ! reflection) + { + SLOG(ERROR, "transform to user field failed for sofa message not initialized"); + return -1; + } + google::protobuf::FieldDescriptor::Type sofa_type = field->type(); + const bool repeated = field->is_repeated(); + const bool required = field->is_required(); + int array_size; + if (repeated) + { + array_size = reflection->FieldSize(*sofa_msg, field); + } + switch (sofa_type) + { + case google::protobuf::FieldDescriptor::TYPE_DOUBLE: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + double sofa_item = reflection->GetRepeatedDouble(*sofa_msg, field, i); + ZVAL_DOUBLE(user_item, sofa_item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + double item = reflection->GetDouble(*sofa_msg, field); + ZVAL_DOUBLE(value, item); + } + break; + } + case google::protobuf::FieldDescriptor::TYPE_FLOAT: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + float item = reflection->GetRepeatedFloat(*sofa_msg, field, i); + ZVAL_DOUBLE(user_item, item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + float item = reflection->GetFloat(*sofa_msg, field); + ZVAL_DOUBLE(value, item); + } + break; + } + case google::protobuf::FieldDescriptor::TYPE_INT32: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + int32_t item = reflection->GetRepeatedInt32(*sofa_msg, field, i); + ZVAL_LONG(user_item, item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + int32_t item = reflection->GetInt32(*sofa_msg, field); + ZVAL_LONG(value, item); + } + break; + } + case google::protobuf::FieldDescriptor::TYPE_INT64: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + int64_t item = reflection->GetRepeatedInt64(*sofa_msg, field, i); + ZVAL_LONG(user_item, item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + int64_t item = reflection->GetInt64(*sofa_msg, field); + ZVAL_LONG(value, item); + } + break; + } + case google::protobuf::FieldDescriptor::TYPE_UINT64: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + uint64_t item = reflection->GetRepeatedUInt64(*sofa_msg, field, i); + ZVAL_LONG(user_item, item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + uint64_t item = reflection->GetUInt64(*sofa_msg, field); + ZVAL_LONG(value, item); + } + break; + } + case google::protobuf::FieldDescriptor::TYPE_BOOL: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + bool item = reflection->GetRepeatedBool(*sofa_msg, field, i); + ZVAL_BOOL(user_item, item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + bool item = reflection->GetBool(*sofa_msg, field); + ZVAL_BOOL(value, item); + } + break; + } + case google::protobuf::FieldDescriptor::TYPE_STRING: + { + if (repeated) + { + //array_init(value); + for (int i = 0; i < array_size; ++i) + { + zval* user_item; + ALLOC_INIT_ZVAL(user_item); + std::string item = reflection->GetRepeatedString(*sofa_msg, field, i); + ZVAL_STRINGL(user_item, item.c_str(), item.length(), 0); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + std::string item = reflection->GetString(*sofa_msg, field); + ZVAL_STRINGL(value, item.c_str(), item.length(), 0); + } + break; + } + default: + SLOG(ERROR, "transform sofa to user field failed for bad type"); + return -1; + } + return 0; +} + +int PhpRpcServiceStubImpl::CreateMessageDescriptor(zval* message, + zval* message_descriptor, + google::protobuf::DescriptorProto* message_proto, + google::protobuf::FileDescriptorProto* file_proto) +{ + int index = 1; + HashPosition i; + EXT_ZEND_FOREACH(&i, Z_ARRVAL_P(message_descriptor)) + { + ulong field_number; + zval** field_descriptor; + zval** value; + zval** type; + + zend_hash_get_current_key_ex(Z_ARRVAL_P(message_descriptor), NULL, NULL, &field_number, 0, &i); + zend_hash_get_current_data_ex(Z_ARRVAL_P(message_descriptor), (void **) &field_descriptor, &i); + std::string field_name = GetFieldName(field_descriptor); + if ((type = GetFieldType(field_descriptor, field_number)) == NULL) + { + return -1; + } + zval** repeated_zval = GetRepeatedLabel(field_descriptor, field_number); + bool repeated = repeated_zval? Z_BVAL_PP(repeated_zval):false; + zval** required_zval = GetRequiredLabel(field_descriptor, field_number); + bool required = required_zval? Z_BVAL_PP(required_zval):false; + if (Z_TYPE_PP(type) == IS_STRING) + { + std::string type_str = (const char*)Z_STRVAL_PP(type); + zval* sub_user_message = GetSubMessage(message, field_number); + CreateMessage(sub_user_message, type_str, file_proto); + } + google::protobuf::FieldDescriptorProto* field_proto = message_proto->add_field(); + field_proto->set_name(field_name); + google::protobuf::FieldDescriptorProto::Type sofa_type; + int ret = GetSofaFieldType(type, sofa_type); + if (-1 == ret) + { + SLOG(ERROR, "create message descriptor failed in field %s \ + for bad pb field type", field_name.c_str()); + return -1; + } + if (sofa_type == google::protobuf::FieldDescriptorProto::TYPE_MESSAGE) + { + field_proto->set_type_name((const char*)Z_STRVAL_PP(type)); + } + field_proto->set_type(sofa_type); + field_proto->set_number(index); + + if (required_zval) + { + if (Z_BVAL_PP(required_zval)) + { + field_proto->set_label(google::protobuf::FieldDescriptorProto::LABEL_REQUIRED); + } + else + { + field_proto->set_label(google::protobuf::FieldDescriptorProto::LABEL_OPTIONAL); + } + } + else if (repeated_zval != NULL && Z_BVAL_PP(repeated_zval)) + { + field_proto->set_label(google::protobuf::FieldDescriptorProto::LABEL_REPEATED); + } + else + { + SLOG(ERROR, "create message descriptor failed in field %s \ + for label not found", field_name.c_str()); + return -1; + } + ++index; + } + return 0; +} + +int PhpRpcServiceStubImpl::SofaTransformToPhp(zval* user_msg, + google::protobuf::Message* sofa_msg) +{ + const google::protobuf::Reflection* reflection = sofa_msg->GetReflection(); + const google::protobuf::Descriptor* message_descriptor = sofa_msg->GetDescriptor(); + + if (!message_descriptor || !reflection) + { + SLOG(ERROR, "transform to php message failed for sofa message is not initialized"); + return -1; + } + zval* field_descriptors = GetFieldDescriptors(user_msg); + if (!field_descriptors) + { + SLOG(ERROR, "transform to php message failed for field_descriptor is null"); + return -1; + } + zval** values = GetUserValues(user_msg); + zval* value = NULL; + zval** old_value = NULL; + for (int i = 0; i < message_descriptor->field_count(); ++i) + { + const google::protobuf::FieldDescriptor* field = message_descriptor->field(i); + bool required = field->is_required(); + bool repeated = field->is_repeated(); + const google::protobuf::FieldDescriptor::Type field_type = field->type(); + if (zend_hash_index_find(Z_ARRVAL_PP(values), i + 1, (void **)&old_value) == FAILURE) + { + return -1; + } + if (Z_TYPE_PP(old_value) == IS_ARRAY) + { + ALLOC_INIT_ZVAL(value); + add_next_index_zval(*old_value, value); + } + else + { + value = *old_value; + } + + if (field_type != google::protobuf::FieldDescriptor::TYPE_MESSAGE) + { + if (ToUserField(value, sofa_msg, field, reflection) != 0) + { + SLOG(ERROR, "transform to sofa field failed"); + return -1; + } + } + + if (field_type == google::protobuf::FieldDescriptor::TYPE_MESSAGE) + { + if (repeated) + { + for (int i = 0; i < reflection->FieldSize(*sofa_msg, field); ++i) + { + zval* user_item; + google::protobuf::Message* sub_message_item = reflection->MutableRepeatedMessage(sofa_msg, field, i); + SofaTransformToPhp(user_item, sub_message_item); + add_next_index_zval(value, user_item); + zval_ptr_dtor(&user_item); + } + } + else + { + google::protobuf::Message* sub_message = reflection->MutableMessage(sofa_msg, field); + SofaTransformToPhp(value, sub_message); + } + } + } + return 0; +} + +int PhpRpcServiceStubImpl::CreateMessage(zval* message, + const std::string& message_type, + google::protobuf::FileDescriptorProto* file_proto) +{ + if (_message_set->find(message_type) != _message_set->end()) + { + return 0; + } + google::protobuf::DescriptorProto* message_proto = file_proto->add_message_type(); + if (!message_proto) + { + SLOG(FATAL, "create message %s proto failed for add message type error", message_type.c_str()); + return -1; + } + message_proto->set_name(message_type); + zval* message_descriptor = GetFieldDescriptors(message); + if (!message_descriptor) + { + SLOG(ERROR, "create message %s proto failed for message_descriptor is null", message_type.c_str()); + return -1; + } + int ret = CreateMessageDescriptor(message, message_descriptor, message_proto, file_proto); + if (ret) + { + SLOG(FATAL, "create message %s failed for create message descriptor error", message_type.c_str()); + return -1; + } + _message_set->insert(message_type); + return 0; +} + +void PhpRpcServiceStubImpl::SetTimeout(long timeout) +{ + _timeout = timeout; +} + +bool PhpRpcServiceStubImpl::Failed() +{ + return _cntl->Failed(); +} + +std::string PhpRpcServiceStubImpl::ErrorText() +{ + return _cntl->ErrorText(); +} + +bool PhpRpcServiceStubImpl::RegisterMethod(const std::string& method_name, + zval* request, + zval* response) +{ + if (!_service_proto) + { + SLOG(FATAL, "init method failed for service not initialized"); + return false; + } + MethodBoard::iterator it = _method_board->find(method_name.data()); + if (_method_board->end() != it) + { + SLOG(NOTICE, "method %s already registered", method_name.data()); + return false; + } + google::protobuf::MethodDescriptorProto* method_proto = _service_proto->add_method(); + if (!method_proto) + { + SLOG(FATAL, "register method %s to service %s failed", \ + method_name.data(), + _service_name.c_str()); + return false; + } + method_proto->set_name(method_name.data()); + std::string request_type, response_type; + GetMessageType(request, request_type); + GetMessageType(response, response_type); + method_proto->set_input_type(request_type); + method_proto->set_output_type(response_type); + int ret = CreateMessage(request, request_type, _file_proto); + if (ret != 0) + { + SLOG(FATAL, "init method %s failed for create message %s failed", \ + method_name.data(), request_type.c_str()); + return false; + } + + ret = CreateMessage(response, response_type, _file_proto); + if (ret != 0) + { + SLOG(FATAL, "init method %s failed for create message %s failed", \ + method_name.data(), response_type.c_str()); + return false; + } + MethodWrapper* mwrapper = new MethodWrapper(); + _method_board->insert(std::make_pair(method_name.data(), mwrapper)); + return true; +} + +bool PhpRpcServiceStubImpl::InitMethods() +{ + _file_descriptor = _pool->BuildFile(*_file_proto); + if (!_file_descriptor) { + SLOG(FATAL, "init methods failed for build rpc descriptor failed"); + return false; + } + const google::protobuf::ServiceDescriptor* service_descriptor + = _file_descriptor->FindServiceByName(_service_name); + if (!service_descriptor) + { + SLOG(FATAL, "init methods failed for service %s not found in file proto", \ + _service_name.c_str()); + return false; + } + MethodBoard::iterator it = _method_board->begin(); + MethodBoard::iterator end = _method_board->end(); + for (; it != end; ++it) + { + std::string method_name = it->first; + MethodWrapper* mwrapper = it->second; + const google::protobuf::MethodDescriptor* method_descriptor + = service_descriptor->FindMethodByName(method_name); + if (!method_descriptor) + { + SLOG(ERROR, "init methods failed for method %s not found", method_name.c_str()); + return false; + } + const google::protobuf::Descriptor* request = method_descriptor->input_type(); + const google::protobuf::Descriptor* response = method_descriptor->output_type(); + mwrapper->_method = method_descriptor; + mwrapper->_request = request; + mwrapper->_response = response; + } + return true; +} + +void PhpRpcServiceStubImpl::CallMethod(const std::string& method_name, + zval* request, + zval* response, + zval* closure) +{ + MethodBoard::iterator it = _method_board->find(method_name.data()); + if (it == _method_board->end()) + { + SLOG(FATAL, "method %s not found", method_name.data()); + return; + } + MethodWrapper* mwrapper = it->second; + if (!mwrapper || !mwrapper->_method \ + || !mwrapper->_response) + { + SLOG(FATAL, "PhpRpcServiceStub call method failed for method %s not initialized", method_name.data()); + return; + } + google::protobuf::DynamicMessageFactory factory(_pool); + const google::protobuf::Message* request_message = factory.GetPrototype(mwrapper->_request); + const google::protobuf::Message* response_message = factory.GetPrototype(mwrapper->_response); + google::protobuf::Message* request_instance = request_message->New(); + google::protobuf::Message* response_instance = response_message->New(); + + if (0 != (PhpTransformToSofa(request, request_instance))) + { + delete request_instance; + delete response_instance; + return; + } + _cntl->Reset(); + _cntl->SetTimeout(_timeout); + _channel->CallMethod(mwrapper->_method, + _cntl, + request_instance, + response_instance, + NULL); + + SofaTransformToPhp(response, response_instance); + delete request_instance; + delete response_instance; +} + +} + +/* vim: set ts=4 sw=4 sts=4 tw=100 */ diff --git a/php/ext_rpc_service_stub_impl.h b/php/ext_rpc_service_stub_impl.h new file mode 100644 index 0000000..20a29af --- /dev/null +++ b/php/ext_rpc_service_stub_impl.h @@ -0,0 +1,164 @@ +// Copyright (c) 2016 Baidu.com, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Author: zhangdi05@baidu.com (Zhangdi Di) + +#ifndef EXT_RPC_SERVICE_STUB_IMPL_H +#define EXT_RPC_SERVICE_STUB_IMPL_H + +extern "C" { +#include +#include +#include +#include +} + +#include +#include +#include +#ifdef add_method +#undef add_method +#include +#endif +#include + +#include +#include +#include + +namespace sofa_php_ext +{ + +const static std::string FILE_PROTO = "file_proto"; + +struct MethodWrapper +{ + MethodWrapper() : _method(NULL), + _request(NULL), + _response(NULL) + { } + + ~MethodWrapper() + { } + + const google::protobuf::MethodDescriptor* _method; + const google::protobuf::Descriptor* _request; + const google::protobuf::Descriptor* _response; +}; + +typedef std::map MethodBoard; +typedef std::set MessageSet; + +enum PbType +{ + PB_TYPE_DOUBLE = 1, + PB_TYPE_FIXED32, + PB_TYPE_FIXED64, + PB_TYPE_FLOAT, + PB_TYPE_INT, + PB_TYPE_SIGNED_INT, + PB_TYPE_STRING, + PB_TYPE_BOOL, + PB_TYPE_UINT64 +}; + +class PhpRpcServiceStubImpl +{ +public: + PhpRpcServiceStubImpl(const std::string& server_addr, + const std::string& package_name, + const std::string& service_name); + + ~PhpRpcServiceStubImpl(); + + void SetTimeout(long timeout); + + bool Failed(); + + std::string ErrorText(); + + bool RegisterMethod(const std::string& method_name, + zval* request, + zval* response); + + bool InitMethods(); + + void CallMethod(const std::string& method_name, + zval* request, + zval* response, + zval* closure); + +private: + zval* GetFieldDescriptors(zval* obj); + + zval** GetFieldDescriptor(zval* obj, uint32_t field_number); + + zval** GetFieldType(zval** field_descriptor, uint32_t field_number); + + zval** GetRequiredLabel(zval** field_descriptor, uint32_t field_number); + + zval** GetRepeatedLabel(zval** field_descriptor, uint32_t field_number); + + const char* GetFieldName(zval** field_descriptor); + + zval** GetUserValues(zval* obj); + + void GetMessageType(zval* obj, + std::string& message_type); + + zval* GetSubMessage(zval* obj, + ulong field_number); + + int GetSofaFieldType(zval** type, + google::protobuf::FieldDescriptorProto::Type& pb_type); + + int ToSofaField(zval** type, + const bool repeated, + zval** value, + google::protobuf::Message* sofa_msg, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection); + + int PhpTransformToSofa(zval* user_msg, + google::protobuf::Message* sofa_msg); + + int ToUserField(zval* value, + const google::protobuf::Message* sofa_msg, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection); + + int CreateMessage(zval* message, + const std::string& message_type, + google::protobuf::FileDescriptorProto* file_proto); + + int CreateMessageDescriptor(zval* message, + zval* message_descriptor, + google::protobuf::DescriptorProto* message_proto, + google::protobuf::FileDescriptorProto* file_proto); + + + int SofaTransformToPhp(zval* user_msg, + google::protobuf::Message* sofa_msg); + +private: + long _timeout; + std::string _server_addr; + std::string _package_name; + std::string _service_name; + MethodBoard* _method_board; + MessageSet* _message_set; + + sofa::pbrpc::RpcChannel* _channel; + sofa::pbrpc::RpcController* _cntl; + google::protobuf::DescriptorPool* _pool; + google::protobuf::FileDescriptorProto* _file_proto; + google::protobuf::ServiceDescriptorProto* _service_proto; + const google::protobuf::FileDescriptor* _file_descriptor; +}; + +} + +#endif // EXT_RPC_SERVICE_STUB_IMPL_H + +/* vim: set ts=4 sw=4 sts=4 tw=100 */ diff --git a/php/genphp/ProtobufCompiler/CodeStringBuffer.php b/php/genphp/ProtobufCompiler/CodeStringBuffer.php new file mode 100644 index 0000000..d75b0fd --- /dev/null +++ b/php/genphp/ProtobufCompiler/CodeStringBuffer.php @@ -0,0 +1,108 @@ +identStr = $tabString; + $this->newLineStr = $newLineString; + } + + /** + * Adds new line to buffer + * + * @return CodeStringBuffer + */ + public function newline() + { + $this->append(''); + return $this; + } + + /** + * Appends lines to buffer + * + * @param string $lines Lines to append + * @param bool $newline Add extra newline after lines + * @param int $identOffset Extra ident code + * + * @return CodeStringBuffer + */ + public function append($lines, $newline = false, $identOffset = 0) + { + foreach (explode($this->newLineStr, $lines) as $line) { + if (strlen($line) > 0) { + $this->_buffer[] = $this->_getIdentationString($identOffset) . $line; + } else { + $this->_buffer[] = ''; + } + } + + if ($newline) { + $this->_buffer[] = $this->newLineStr; + } + + return $this; + } + + /** + * Increases identation + * + * @return CodeStringBuffer + */ + public function increaseIdentation() + { + $this->_identLevel++; + + return $this; + } + + /** + * Decreases identation + * + * @return CodeStringBuffer + */ + public function decreaseIdentation() + { + $this->_identLevel = max(0, $this->_identLevel - 1); + + return $this; + } + + /** + * Returns identation string + * + * @param int $identOffset Offset + * + * @return string + */ + private function _getIdentationString($identOffset = 0) + { + return str_repeat($this->identStr, $this->_identLevel + $identOffset); + } + + /** + * Returns buffer as string + * + * @return string + */ + public function __toString() + { + return implode($this->newLineStr, $this->_buffer); + } +} + diff --git a/php/genphp/ProtobufCompiler/CommentStringBuffer.php b/php/genphp/ProtobufCompiler/CommentStringBuffer.php new file mode 100644 index 0000000..2292aab --- /dev/null +++ b/php/genphp/ProtobufCompiler/CommentStringBuffer.php @@ -0,0 +1,71 @@ +append('@' . $param . ' ' . $value); + + return $this; + } + + /** + * Appends new line to docblock + * + * @return CommentStringBuffer + */ + public function newline() + { + $this->append(''); + + return $this; + } + + /** + * Appends new comment line to block + * + * @param string $line Lines to append + * @param bool $newline True to append extra line at the end + * @param int $identOffset Ident offset + * + * @return CommentStringBuffer + */ + public function append($line, $newline = false, $identOffset = 0) + { + if (strlen($line) > 0) { + parent::append( + self::COMMENT_LINE_PREFIX . ' ' . $line, $newline, $identOffset + ); + } else { + parent::append( + self::COMMENT_LINE_PREFIX, $newline, $identOffset + ); + } + + return $this; + } + + /** + * Returns buffer as as string + * + * @return string + */ + public function __toString() + { + return '/**' . $this->newLineStr . + parent::__toString() . + $this->newLineStr . ' */'; + } +} diff --git a/php/genphp/ProtobufCompiler/DescriptorInterface.php b/php/genphp/ProtobufCompiler/DescriptorInterface.php new file mode 100644 index 0000000..3fc3be3 --- /dev/null +++ b/php/genphp/ProtobufCompiler/DescriptorInterface.php @@ -0,0 +1,24 @@ +_name = $name; + $this->_file = $file; + $this->_containing = $containing; + + if (is_null($containing)) { + $file->addEnum($this); + } + + if (!is_null($containing)) { + $containing->addEnum($this); + } + } + + /** + * Adds new enum value + * + * @param EnumValueDescriptor $value Value + * + * @return null + */ + public function addValue(EnumValueDescriptor $value) + { + $this->_values[] = $value; + } + + /** + * Returns containing message + * + * @return MessageDescriptor + */ + public function getContaining() + { + return $this->_containing; + } + + /** + * Returns containing file + * + * @return FileDescriptor + */ + public function getFile() + { + return $this->_file; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns enum values descriptors + * + * @return EnumValueDescriptor[] + */ + public function getValues() + { + return $this->_values; + } +} diff --git a/php/genphp/ProtobufCompiler/EnumValueDescriptor.php b/php/genphp/ProtobufCompiler/EnumValueDescriptor.php new file mode 100644 index 0000000..522c965 --- /dev/null +++ b/php/genphp/ProtobufCompiler/EnumValueDescriptor.php @@ -0,0 +1,41 @@ +_name = $name; + $this->_value = $value; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns value + * + * @return string + */ + public function getValue() + { + return $this->_value; + } +} diff --git a/php/genphp/ProtobufCompiler/FieldDescriptor.php b/php/genphp/ProtobufCompiler/FieldDescriptor.php new file mode 100644 index 0000000..c36f629 --- /dev/null +++ b/php/genphp/ProtobufCompiler/FieldDescriptor.php @@ -0,0 +1,305 @@ + \ProtobufMessage::PB_TYPE_DOUBLE, + 'float' => \ProtobufMessage::PB_TYPE_FLOAT, + 'int32' => \ProtobufMessage::PB_TYPE_INT, + 'int64' => \ProtobufMessage::PB_TYPE_INT, + 'uint32' => \ProtobufMessage::PB_TYPE_INT, + //'uint64' => \ProtobufMessage::PB_TYPE_UINT64, + 'sint32' => \ProtobufMessage::PB_TYPE_SIGNED_INT, + 'sint64' => \ProtobufMessage::PB_TYPE_SIGNED_INT, + 'fixed32' => \ProtobufMessage::PB_TYPE_FIXED32, + 'fixed64' => \ProtobufMessage::PB_TYPE_FIXED64, + 'sfixed32' => \ProtobufMessage::PB_TYPE_FIXED32, + 'sfixed64' => \ProtobufMessage::PB_TYPE_FIXED64, + 'bool' => \ProtobufMessage::PB_TYPE_BOOL, + 'string' => \ProtobufMessage::PB_TYPE_STRING, + 'bytes' => \ProtobufMessage::PB_TYPE_STRING, + ); + + private static $_scalarNativeTypes = array( + 'double' => 'float', + 'float' => 'float', + 'int32' => 'int', + 'int64' => 'int', + 'uint32' => 'int', + 'uint64' => 'string', + 'sint32' => 'int', + 'sint64' => 'int', + 'fixed32' => 'int', + 'fixed64' => 'int', + 'sfixed32' => 'int', + 'sfixed64' => 'int', + 'bool' => 'bool', + 'string' => 'string', + 'bytes' => 'string', + ); + + private $_default; + private $_label; + private $_name; + private $_namespace = null; + private $_number; + private $_type; + private $_typeDescriptor = null; + + /** + * Returns default value + * + * @return string + */ + public function getDefault() + { + return $this->_default; + } + + /** + * Returns label + * + * @return string + */ + public function getLabel() + { + return $this->_label; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns camel case name + * + * @return string + */ + public function getCamelCaseName() + { + //$chunks = preg_split('/[^a-z0-9]/is', $this->getName()); + //return implode('', array_map('ucfirst', $chunks)); + return $this->getName(); + } + + /** + * Returns const name + * + * @return string + */ + public function getConstName() + { + $chunks = preg_split('/[^a-z0-9]/is', $this->getName()); + return implode('_', array_map('strtoupper', $chunks)); + } + + /** + * Returns namespace + * + * @return string + */ + public function getNamespace() + { + return $this->_namespace; + } + + /** + * Returns number + * + * @return int + */ + public function getNumber() + { + return $this->_number; + } + + /** + * Returns scalar type + * + * @return int + */ + public function getScalarType() + { + return self::$_scalarTypes[strtolower($this->_type)]; + } + + /** + * Returns PHP type + * + * @return string + */ + public function getTypeName() + { + if (isset(self::$_scalarNativeTypes[strtolower($this->_type)])) { + return self::$_scalarNativeTypes[strtolower($this->_type)]; + } else { + if ($this->_typeDescriptor instanceof \EnumDescriptor) { + return 'int'; + } else { + return $this->_type; + } + } + } + + /** + * Returns true if is native type + * + * @return bool + */ + public function isScalarType() + { + return isset(self::$_scalarNativeTypes[strtolower($this->_type)]); + } + + /** + * Returns type + * + * @return type + */ + public function getType() + { + return $this->_type; + } + + /** + * Returns type descriptor + * + * @return string + */ + public function getTypeDescriptor() + { + return $this->_typeDescriptor; + } + + /** + * Returns true if field is repeated + * + * @return bool + */ + public function isRepeated() + { + return $this->_label == FieldLabel::REPEATED; + } + + /** + * Returns true if is requireg + * + * @return bool + */ + public function isRequired() + { + return $this->_label == FieldLabel::REQUIRED; + } + + /** + * Returns true if is scalar + * + * @return bool + */ + public function isProtobufScalarType() + { + return isset(self::$_scalarTypes[strtolower($this->_type)]); + } + + /** + * Returns true if is optional + * + * @return bool + */ + public function isOptional() + { + return $this->_label == FieldLabel::OPTIONAL; + } + + /** + * Sets default value + * + * @param mixed $default Default value + * + * @return null + */ + public function setDefault($default) + { + $this->_default = $default; + } + + /** + * Sets label + * + * @param string $label Label + * + * @return null + */ + public function setLabel($label) + { + $this->_label = $label; + } + + /** + * Sets name + * + * @param string $name Name + * + * @return null + */ + public function setName($name) + { + $this->_name = $name; + } + + /** + * Sets namespace + * + * @param string $namespace Namespace + * + * @return null + */ + public function setNamespace($namespace) + { + $this->_namespace = $namespace; + } + + /** + * Sets number + * + * @param int $number Number + * + * @return null + */ + public function setNumber($number) + { + $this->_number = $number; + } + + /** + * Sets type + * + * @param int $type Type + * + * @return null + */ + public function setType($type) + { + $this->_type = $type; + } + + /** + * Sets type descriptor + * + * @param int $typeDescriptor Type descriptor + * + * @return null + */ + public function setTypeDescriptor($typeDescriptor) + { + $this->_typeDescriptor = $typeDescriptor; + } +} diff --git a/php/genphp/ProtobufCompiler/FieldLabel.php b/php/genphp/ProtobufCompiler/FieldLabel.php new file mode 100644 index 0000000..d9842e1 --- /dev/null +++ b/php/genphp/ProtobufCompiler/FieldLabel.php @@ -0,0 +1,11 @@ +_name = $name; + } + + /** + * Adds new dependency + * + * @param FileDescriptor $dependency Depending file + * + * @return null + */ + public function addDependency(FileDescriptor $dependency) + { + $this->_dependencies[] = $dependency; + } + + /** + * Adds new enum + * + * @param EnumDescriptor $enum Enum + * + * @return null + */ + public function addEnum(EnumDescriptor $enum) + { + $this->_enums[] = $enum; + } + + /** + * Adds new message + * + * @param MessageDescriptor $message Message + * + * @return null + */ + public function addMessage(MessageDescriptor $message) + { + $this->_messages[$message->getName()] = $message; + } + + /** + * Adds new service + * + * @param ServiceDescriptor $service + * + * @return null + */ + public function addService(ServiceDescriptor $service) + { + $this->_service = $service; + } + + /** + * Returns required files + * + * @return array|FileDescriptor[] + */ + public function getDependencies() + { + return $this->_dependencies; + } + + /** + * Returns defines enums + * + * @return array|EnumDescriptor[] + */ + public function getEnums() + { + return $this->_enums; + } + + /** + * Returns defined messages + * + * @return array|MessageDescriptor[] + */ + public function getMessages() + { + return $this->_messages; + } + + /** + * + * @param string $name name of message + * + * Returns MessageDescriptor with name $name + * + * @return MessageDescriptor + */ + public function findMessage($name) + { + return $this->_messages[$name]; + } + + /** + * + * @param string $name name of message + * + * Returns whether has mesage with name $name + * + * @return Boolean + */ + public function hasMessage($name) + { + return array_key_exists($name, $this->_messages); + } + + /** + * Returns defined service + * + * @return service + */ + public function getService() + { + return $this->_service; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns package + * + * @return string + */ + public function getPackage() + { + return $this->_package; + } + + /** + * Sets name + * + * @param string $name Name + * + * @return null + */ + public function setName($name) + { + $this->_name = $name; + } + + /** + * Sets package name + * + * @param string $package Package + * + * @return null + */ + public function setPackage($package) + { + $this->_package = $package; + } +} diff --git a/php/genphp/ProtobufCompiler/MessageDescriptor.php b/php/genphp/ProtobufCompiler/MessageDescriptor.php new file mode 100644 index 0000000..c528829 --- /dev/null +++ b/php/genphp/ProtobufCompiler/MessageDescriptor.php @@ -0,0 +1,168 @@ +_name = $name; + $this->_file = $file; + $this->_containing = $containing; + + if (is_null($containing)) { + $file->addMessage($this); + } + + if (!is_null($containing)) { + $containing->addNested($this); + } + } + + /** + * Adds new enum to message + * + * @param EnumDescriptor $enum Enum + * + * @return null + */ + public function addEnum(EnumDescriptor $enum) + { + $this->_enums[$enum->getName()] = $enum; + } + + /** + * Adds new field to message + * + * @param FieldDescriptor $field Field + * + * @return null + */ + public function addField(FieldDescriptor $field) + { + $this->_fields[] = $field; + } + + /** + * Adds nested message + * + * @param MessageDescriptor $nested Message + * + * @return null + */ + public function addNested(MessageDescriptor $nested) + { + $this->_nested[$nested->getName()] = $nested; + } + + /** + * Finds type in message for given name + * + * @param string $name Name to search for + * @param string $namespace Namespace to search in + * + * @return DescriptorInterface|boolean + */ + public function findType($name, $namespace = null) + { + $containing = $this; + + if (!is_null($namespace)) { + $namespace = explode('.', $namespace); + + foreach ($namespace as $n) { + if (!isset($containing->_nested[$n])) { + return false; + } else { + $containing = $containing->_nested[$n]; + } + } + } + + if (isset($containing->_nested[$name])) { + return $containing->_nested[$name]; + } + + if (isset($containing->_enums[$name])) { + return $containing->_enums[$name]; + } + + return false; + } + + /** + * Returns parent message + * + * @return MessageDescriptor + */ + public function getContaining() + { + return $this->_containing; + } + + /** + * Returns defined enums + * + * @return EnumDescriptor[] + */ + public function getEnums() + { + return $this->_enums; + } + + /** + * Returns defined messages + * + * @return FieldDescriptor[] + */ + public function getFields() + { + return $this->_fields; + } + + /** + * Returns file descriptor + * + * @return FileDescriptor + */ + public function getFile() + { + return $this->_file; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns sub-messages + * + * @return array + */ + public function getNested() + { + return $this->_nested; + } +} diff --git a/php/genphp/ProtobufCompiler/MethodDescriptor.php b/php/genphp/ProtobufCompiler/MethodDescriptor.php new file mode 100644 index 0000000..15e3b61 --- /dev/null +++ b/php/genphp/ProtobufCompiler/MethodDescriptor.php @@ -0,0 +1,101 @@ +_name = $name; + $this->_file = $file; + $this->_input = $input; + $this->_output = $output; + + if (is_null($service)) { + throw new Exception( + 'Service cannot be null' + ); + } + $service->addMethod($this); + } + + /** + * Returns parent message + * + * @return ServiceDescriptor + */ + public function getContaining() + { + return $this->_service; + } + + /** + * Returns file descriptor + * + * @return FileDescriptor + */ + public function getFile() + { + return $this->_file; + } + + /** + * Returns service descriptor + * + * @return ServiceDescriptor + */ + public function getService() + { + return $this->_service; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns input type + * + * @return string + */ + public function getInput() + { + return $this->_input; + } + + /** + * Returns output type + * + * @return string + */ + public function getOutput() + { + return $this->_output; + } +} + +?> diff --git a/php/genphp/ProtobufCompiler/ProtobufParser.php b/php/genphp/ProtobufCompiler/ProtobufParser.php new file mode 100644 index 0000000..bd7bd76 --- /dev/null +++ b/php/genphp/ProtobufCompiler/ProtobufParser.php @@ -0,0 +1,1630 @@ +_hasSplTypes = extension_loaded('SPL_Types'); + $this->_useNativeNamespaces = (boolean)$useNativeNamespaces; + } + + /** + * Returns namespaces + * + * @return array + */ + public function getNamespaces() + { + return $this->_namespaces; + } + + /** + * Returns namespace separator + * + * @return array + */ + public function getNamespaceSeparator() + { + return $this->_useNativeNamespaces + ? self::NAMESPACE_SEPARATOR_NATIVE : self::NAMESPACE_SEPARATOR; + } + + /** + * Creates package name based on proto package name value + * + * @param string $name Package name + * + * @return string + */ + public function createPackageName($name) + { + $components = explode('.', $name); + $name = ''; + + foreach ($components as $component) { + if (strlen($component) > 0) { + $name .= $this->getNamespaceSeparator() . ucfirst($component); + } + } + + return trim($name, $this->getNamespaceSeparator()); + } + + /** + * Created type name based on proto type name + * + * @param string $name Type name + * + * @return string + */ + public static function createTypeName($name) + { + $components = explode('_', $name); + $name = ''; + + foreach ($components as $component) { + if (strlen($component) > 0) { + $name .= ucfirst($component); + } + } + + return $name; + } + + /** + * Generates message files (PHP code) for proto file + * + * @param string $sourceFile Source filename (.proto) + * @param string $outputFile Output filename + * + * @return null + */ + public function parse($sourceFile, $outputFile = null) + { + $string = file_get_contents($sourceFile); + $this->_file = new FileDescriptor($sourceFile); + $this->_stripComments($string); + + $string = trim($string); + + $file = new FileDescriptor($sourceFile); + $this->_parseMessageType($file, $string); + + $this->_resolveNamespaces($file); + $buffer = new CodeStringBuffer(self::TAB, self::EOL); + $this->_createClassFile($file, $buffer, $outputFile); + + return $file; + } + + /** + * Constructs request and response instance + * + * @param FileDescriptor $file file descriptor of proto + * @param MessageDescriptor $msg message descriptor to constructs instance + * @param CodeStringBuffer $buffer buffer to write code in + * + * @return null + */ + private function _createParaConstruct(FileDescriptor $file, MessageDescriptor $msg, CodeStringBuffer $buffer) + { + $finished_type = self::$_finished; + if(in_array($msg->getName(), $finished_type)) + { + return; + } + $fields = $msg->getFields(); + $set_content = ''; + $parent_instance = "\$" . $msg->getName() . '_instance'; + $is_in_array = in_array($msg->getName(), $finished_type); + if ($is_in_array === false) + { + $buffer->append($parent_instance . ' = New ' . $msg->getName() . '();'); + } + + foreach ($fields as $index => $field) + { + $type = $field->getType(); + $label = $field->getLabel(); + $name = $field->getName(); + if ($file->hasMessage($type)) + { + $child = $file->findMessage($type); + $this->_createParaConstruct($file, $child, $buffer); + if ($label == 1 || $label == 2) + { + $buffer->append($parent_instance . '->set_' . $name . '($' . $type . '_instance' . ');'); + } + else if ($label == 3) + { + $buffer->append($parent_instance . '->append_' . $name . '($' . $type . '_instance' . ');'); + } + } + } + $finished_type = self::$_finished; + self::$_finished[] = $msg->getName(); + } + + /** + * Generates method description and write it to buffer + * + * @param MethodDescriptor $descriptor Method descriptor + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + private function _createRegisterMethod(FileDescriptor $file, MethodDescriptor $method, CodeStringBuffer $buffer) + { + self::$_finished = array(); + $name = $method->getName(); + $input_type = $method->getInput(); + $output_type = $method->getOutput(); + $input_descriptor = $file->findMessage($input_type); + $output_descriptor = $file->findMessage($output_type); + + $input_fields = $input_descriptor->getFields(); + $output_fields = $output_descriptor->getFields(); + + $buffer->increaseIdentation() + ->append('private function Register' . $name . '()') + ->append('{') + ->increaseIdentation(); + + $this->_createParaConstruct($file, $input_descriptor, $buffer); + $this->_createParaConstruct($file, $output_descriptor, $buffer); + + $request_instance = "\$" . $input_type . "_instance"; + $response_instance = "\$" . $output_type . "_instance"; + $buffer->append('$this->RegisterMethod(\'' . $name . '\', ' + . $request_instance . ', ' . $response_instance. ');') + ->decreaseIdentation() + ->append('}'); + return $name; + } + + /** + * Generates user rpc interface which is defined in proto, + * such as Failed() and ErrorText() + * + * @param MethodDescriptor $method MethodDescriptor to generate interface + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + + private function _createUserMethod(MethodDescriptor $method, CodeStringBuffer $buffer) + { + $buffer->append('') + ->append('public function ' . $method->getName() . '($request, $response, $closure)') + ->append('{') + ->increaseIdentation() + ->append('$this->CallMethod(\'' .$method->getName() . '\', $request, $response, $closure);') + ->decreaseIdentation() + ->append('}'); + } + + /** + * Generates common method in service and write it to buffer, + * such as Failed() and ErrorText() + * + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + + private function _createServiceCommonMethod(CodeStringBuffer $buffer) + { + $buffer->append('') + ->append('public function Failed()') + ->append('{') + ->increaseIdentation() + ->append('return $this->GetFailed();') + ->decreaseIdentation() + ->append('}') + ->append('') + ->append('public function ErrorText()') + ->append('{') + ->increaseIdentation() + ->append('return $this->GetErrorText();') + ->decreaseIdentation() + ->append('}'); + } + + /** + * Generates class description and write it to buffer + * + * @param FileDescriptor $descriptor File descriptor + * @param ServiceDescriptor $descriptor Service descriptor + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + private function _createService(FileDescriptor $file, ServiceDescriptor $descriptor, CodeStringBuffer $buffer) + { + $name = $descriptor->getName(); + $class_frame = "class " . $name . " extends " . self::SERVICE_CLASS . "\n{"; + $buffer->append($class_frame); + $register_func = 'Register'; + foreach ($descriptor->getMethods() as $method) { + $register_func = $register_func . $this->_createRegisterMethod($file, $method, $buffer); + $buffer->append(''); + } + + $buffer->append('function __construct($server_address)') + ->append('{') + ->increaseIdentation() + ->append('$this->InitService($server_address, ' . + '\'' . $file->getPackage() . '\'' . ', ' . + '\'' . $descriptor->getName() . '\')' . ';') + ->append('$this->' . $register_func . '()' . ';') + ->append('$this->InitMethods();') + ->decreaseIdentation() + ->append('}'); + foreach ($descriptor->getMethods() as $method) { + $this->_createUserMethod($method, $buffer); + $this->_createServiceCommonMethod($buffer); + } + $buffer->decreaseIdentation() + ->append('}'); + } + + /** + * Generates class description and write it to buffer + * + * @param MessageDescriptor $descriptor Message descriptor + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + private function _createClass( + MessageDescriptor $descriptor, CodeStringBuffer $buffer + ) { + foreach ($descriptor->getEnums() as $enum) { + $this->_createEnum($enum, $buffer); + } + + foreach ($descriptor->getNested() as $nested) { + $this->_createClass($nested, $buffer); + } + + $buffer->newline(); + + if ($this->_useNativeNamespaces) { + $name = self::createTypeName($descriptor->getName()); + $namespaceName = $this->_createNamespaceName($descriptor); + $buffer->append('namespace ' . $namespaceName . ' {'); + } else { + $name = $this->_createClassName($descriptor); + } + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $path = $this->_createEmbeddedMessagePath($descriptor); + if ($path) { + $comment->append($descriptor->getName() . ' message embedded in ' . $path . ' message'); + } else { + $comment->append($descriptor->getName() . ' message'); + } + + $buffer->append($comment); + + $buffer->append('class ' . $name . ' extends ProtobufMessage') + ->append('{') + ->increaseIdentation(); + + $this->_createClassConstructor($descriptor->getName(), $descriptor->getFields(), $buffer); + $this->_createClassBody($descriptor->getFields(), $buffer); + + $buffer->decreaseIdentation() + ->append('}'); + + if ($this->_useNativeNamespaces) { + $buffer->append('}'); + } + } + + /** + * Creates enum description + * + * @param EnumDescriptor $descriptor Enum descriptor + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + private function _createEnum( + EnumDescriptor $descriptor, CodeStringBuffer $buffer + ) { + $buffer->newline(); + + if ($this->_useNativeNamespaces) { + $name = self::createTypeName($descriptor->getName()); + $namespaceName = $this->_createNamespaceName($descriptor); + $buffer->append('namespace ' . $namespaceName . ' {'); + } else { + $name = $this->_createClassName($descriptor); + } + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $path = $this->_createEmbeddedMessagePath($descriptor); + if ($path) { + $comment->append($descriptor->getName() . ' enum embedded in ' . $path . ' message'); + } else { + $comment->append($descriptor->getName() . ' enum'); + } + + $buffer->append($comment); + + if ($this->_hasSplTypes) { + $buffer->append('final class ' . $name .' extends SplEnum') + ->append('{') + ->increaseIdentation(); + } else { + $buffer->append('final class ' . $name) + ->append('{') + ->increaseIdentation(); + } + + $this->_createEnumClassDefinition($descriptor->getValues(), $buffer); + + $buffer->decreaseIdentation() + ->append('}'); + + if ($this->_useNativeNamespaces) { + $buffer->append('}'); + } + } + + /** + * Creates class code for given file descriptor + * + * @param FileDescriptor $file File descriptor + * @param CodeStringBuffer $buffer Buffer to write code to + * @param string|null $outputFile Output filename + * + * @return null + */ + private function _createClassFile( + FileDescriptor $file, CodeStringBuffer $buffer, $outputFile = null + ) { + $comment = new CommentStringBuffer(self::TAB, self::EOL); + date_default_timezone_set("PRC"); + $date = strftime("%Y-%m-%d %H:%M:%S"); + $comment->append('Auto generated from ' . basename($file->getName()) . ' at ' . $date); + + if ($file->getPackage()) { + $comment->newline() + ->append($file->getPackage() . ' package'); + } + + $buffer->append($comment); + + foreach ($file->getEnums() as $name => $descriptor) { + $this->_createEnum($descriptor, $buffer); + } + + foreach ($file->getMessages() as $name => $descriptor) { + $this->_createClass($descriptor, $buffer); + } + + if (($descriptor = $file->getService())) { + $this->_createService($file, $descriptor, $buffer); + } + + $requiresString = ''; + + foreach ($file->getDependencies() as $dependency) { + $requiresString .= sprintf( + 'require_once \'%s\';', + $this->_createOutputFilename($dependency->getName()) + ); + } + + if ($this->_useNativeNamespaces && !empty($requiresString)) { + $requiresString = 'namespace {' . PHP_EOL . $requiresString . PHP_EOL . '}'; + } + + $buffer->append($requiresString); + + if ($outputFile == null) { + $outputFile = $this->_createOutputFilename($file->getName()); + } + + file_put_contents($outputFile, 'getContaining(); + + while (!is_null($containing)) { + $namespace[] = self::createTypeName($containing->getName()); + $containing = $containing->getContaining(); + } + + $package = $descriptor->getFile()->getPackage(); + + if (!empty($package)) { + $namespace[] = $this->createPackageName($package); + } + + $namespace = array_reverse($namespace); + + $name = implode($this->getNamespaceSeparator(), $namespace); + + return $name; + } + + /** + * Generates class name for given descriptor + * + * @param DescriptorInterface $descriptor Descriptor + * + * @return string + */ + private function _createClassName(DescriptorInterface $descriptor) + { + $name = $descriptor->getName(); + return $name; + } + + /** + * Generates filename for given source filename + * + * @param string $sourceFilename Filename + * + * @return string + */ + private function _createOutputFilename($sourceFilename) + { + $pathInfo = pathinfo($sourceFilename); + + //return 'pb_proto_' . $pathInfo['filename'] . '.php'; + return $pathInfo['filename'] . '.pb.php'; + } + + /** + * Creates embedded message path composed of ancestor messages + * seperated by slash "/". If message has no ancestor returns empty string. + * + * @param DescriptorInterface $descriptor message descriptor + * + * @return string + */ + private function _createEmbeddedMessagePath(DescriptorInterface $descriptor) + { + $containing = $descriptor->getContaining(); + $path = array(); + + while ($containing) { + $path[] = $containing->getName(); + $containing = $containing->getContaining(); + } + + array_reverse($path); + + return implode("/", $path); + } + + /** + * Generates type name for given descriptor + * + * @param FieldDescriptor $field Field descriptor + * + * @return string + */ + private function _getType(FieldDescriptor $field) + { + if ($field->isProtobufScalarType()) { + return $field->getScalarType(); + } else if ($field->getTypeDescriptor() instanceof EnumDescriptor) { + return ProtobufMessage::PB_TYPE_INT; + } else { + return $this->_createClassName($field->getTypeDescriptor()); + } + } + + /** + * Generates class body for given field descriptors list and + * writes it to buffer + * + * @param array $fields Array of FieldDescriptors + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + private function _createClassBody($fields, CodeStringBuffer $buffer) + { + $comment = new CommentStringBuffer(self::TAB, self::EOL); + + $comment->append('Returns class type') + ->newline() + ->appendParam('return', 'string'); + + $buffer->append($comment) + ->append('public function class_type()') + ->append('{') + ->append('return self::$class_type;', false, 1) + ->append('}') + ->append(''); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + + $comment->append('Returns field descriptors') + ->newline() + ->appendParam('return', 'array'); + + $buffer->append($comment) + ->append('public function fields()') + ->append('{') + ->append('return self::$fields;', false, 1) + ->append('}'); + + foreach ($fields as $field) { + if ($field->isRepeated()) { + $this->_describeRepeatedField($field, $buffer); + } else { + $this->_describeSingleField($field, $buffer); + } + } + } + + /** + * Describes repeated field + * + * @param FieldDescriptor $field Field descriptor + * @param CodeStringBuffer $buffer Buffer to write to + * + * @return null + */ + private function _describeRepeatedField( + FieldDescriptor $field, CodeStringBuffer $buffer + ) { + if ($field->isProtobufScalarType() || $field->getTypeDescriptor() instanceof EnumDescriptor) { + $typeName = $field->getTypeName(); + $argumentClass = ''; + } else { + $typeName = $this->_createClassName($field->getTypeDescriptor()); + $argumentClass = $typeName . ' '; + } + $comment = new CommentStringBuffer(self::TAB, self::EOL); + + $comment->append( + 'Sets value of \'' . $field->getName() . '\' property' + ) + ->newline() + ->appendParam( + 'param', + $typeName . ' $value Property value' + ) + ->newline() + ->appendParam('return', 'null'); + + $buffer->newline() + ->append($comment) + ->append( + 'public function set_' . $field->getCamelCaseName() . + '($value)' + ) + ->append('{') + ->append( + 'return $this->set(self::' . + $field->getConstName() . ', $value);', + false, + 1 + ) + ->append('}'); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Appends value to \'' . $field->getName() . '\' list') + ->newline() + ->appendParam('param', $typeName . ' $value Value to append') + ->newline() + ->appendParam('return', 'null'); + + $buffer->newline() + ->append($comment) + ->append( + 'public function append_' . $field->getCamelCaseName() . '(' . $argumentClass . '$value)' + ) + ->append('{') + ->append( + 'return $this->append(self::' . $field->getConstName() . ', $value);', + false, + 1 + ) + ->append('}'); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Clears \'' . $field->getName() . '\' list') + ->newline() + ->appendParam('return', 'null'); + + $buffer->newline() + ->append($comment) + ->append('public function clear_' . $field->getCamelCaseName() . '()') + ->append('{') + ->append( + 'return $this->clear(self::' . $field->getConstName() . ');', + false, + 1 + ) + ->append('}'); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Returns \'' . $field->getName() . '\' list') + ->newline() + ->appendParam('return', $typeName . '[]'); + + $buffer->newline() + ->append($comment) + ->append('public function get_' . $field->getCamelCaseName() . '()') + ->append('{') + ->append( + 'return $this->get(self::' . $field->getConstName() . ');', + false, + 1 + ) + ->append('}'); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Returns \'' . $field->getName() . '\' iterator') + ->newline() + ->appendParam('return', 'ArrayIterator'); + + $buffer->newline() + ->append($comment) + ->append( + 'public function get_' . $field->getCamelCaseName() . 'Iterator()' + ) + ->append('{') + ->append( + 'return new ArrayIterator($this->get(self::' . + $field->getConstName() . '));', + false, + 1 + ) + ->append('}'); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append( + 'Returns element from \'' . $field->getName() . + '\' list at given offset' + ) + ->newline() + ->appendParam('param', 'int $offset Position in list') + ->newline() + ->appendParam('return', $typeName); + + $buffer->newline() + ->append($comment) + ->append( + 'public function get_' . $field->getCamelCaseName() . 'At($offset)' + ) + ->append('{') + ->append( + 'return $this->get(self::' . + $field->getConstName() . ', $offset);', + false, + 1 + ) + ->append('}'); + + if (!$field->isScalarType()) + { + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append( + 'Returns element from \'' . $field->getName() . + '\' list at given offset' + ) + ->newline() + ->appendParam('param', 'int $offset Position in list') + ->newline() + ->appendParam('return', $typeName); + + $buffer->newline() + ->append($comment) + ->append( + 'public function get_' . $field->getCamelCaseName() . 'ObjectAt($offset)' + ) + ->append('{') + ->append( + '$value = ' . + '$this->get(self::' . $field->getConstName() . ', $offset);', + false, + 1 + ) + ->append( + '$instance = new ' . $field->getTypeName() . '();', + false, + 1 + ) + ->append('$instance->copy($value);', + false, + 1 + ) + ->append('return $instance;', + false, + 1 + ) + ->append('}'); + } + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Returns count of \'' . $field->getName() . '\' list') + ->newline() + ->appendParam('return', 'int'); + + $buffer->newline() + ->append($comment) + ->append('public function get_' . $field->getCamelCaseName() . 'Count()') + ->append('{') + ->append( + 'return $this->count(self::' . $field->getConstName() . ');', + false, + 1 + ) + ->append('}'); + } + + /** + * Describes non-repeated field descriptor + * + * @param FieldDescriptor $field Field descriptor + * @param CodeStringBuffer $buffer Buffer to write code to + * + * @return null + */ + private function _describeSingleField( + FieldDescriptor $field, CodeStringBuffer $buffer + ) { + if ($field->isProtobufScalarType() || $field->getTypeDescriptor() instanceof EnumDescriptor) { + $typeName = $field->getTypeName(); + $argumentClass = ''; + } else { + $typeName = $this->_createClassName($field->getTypeDescriptor()); + $argumentClass = $typeName . ' '; + } + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + + $comment->append( + 'Sets value of \'' . $field->getName() . '\' property' + ) + ->newline() + ->appendParam( + 'param', + $typeName . ' $value Property value' + ) + ->newline() + ->appendParam('return', 'null'); + + $buffer->newline() + ->append($comment) + ->append( + 'public function set_' . $field->getCamelCaseName() . + '(' . $argumentClass . '$value)' + ) + ->append('{') + ->append( + 'return $this->set(self::' . + $field->getConstName() . ', $value);', + false, + 1 + ) + ->append('}'); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Returns value of \'' . $field->getName() . '\' property') + ->newline() + ->appendParam('return', $typeName); + + $buffer->newline() + ->append($comment) + ->append('public function get_' . $field->getCamelCaseName() . '()') + ->append('{') + ->append( + 'return ' . + '$this->get(self::' . $field->getConstName() . ');', + false, + 1 + ) + ->append('}'); + + if (!$field->isScalarType()) + { + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Returns value of \'' . $field->getName() . '\' property') + ->newline() + ->appendParam('return', $typeName); + + $buffer->newline() + ->append($comment) + ->append('public function get_' . $field->getCamelCaseName() . 'Object()') + ->append('{') + ->append( + '$value = ' . + '$this->get(self::' . $field->getConstName() . ');', + false, + 1 + ) + ->append( + '$instance = new ' . $field->getTypeName() . '();', + false, + 1 + ) + ->append('$instance->copy($value);', + false, + 1 + ) + ->append('return $instance;', + false, + 1 + ) + ->append('}'); + } + } + + /** + * Generates enum class definition + * + * @param array $enums Enums descriptors list + * @param CodeStringBuffer $buffer Buffer to write code + * + * @return null + */ + private function _createEnumClassDefinition( + array $enums, CodeStringBuffer $buffer + ) { + foreach ($enums as $enum) { + $buffer->append( + 'const ' . $enum->getName() . ' = ' . $enum->getValue() . ';' + ); + } + + $buffer->newline(); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Returns defined enum values') + ->newline() + ->appendParam('return', 'int[]'); + + $buffer->append($comment) + ->append('public function getEnumValues()') + ->append('{'); + + if ($this->_hasSplTypes) { + $buffer->increaseIdentation() + ->append('return $this->getConstList(false);'); + } else { + $buffer->append('return array(', false, 1) + ->increaseIdentation() + ->increaseIdentation(); + + foreach ($enums as $enum) { + $buffer->append('\'' . $enum->getName() . '\' => self::' . $enum->getName() . ','); + } + + $buffer->decreaseIdentation() + ->append(');'); + + } + + $buffer->decreaseIdentation() + ->append('}'); + } + + /** + * Generates class constructor and params list + * + * @param FieldDescriptor[] $fields Field descriptors list + * @param CodeStringBuffer $buffer Code buffer to write to + * + * @return null + */ + private function _createClassConstructor($name, $fields, CodeStringBuffer $buffer) + { + $buffer->append('/* Field index constants */'); + + foreach ($fields as $field) { + $buffer->append( + 'const ' . $field->getConstName() . + ' = ' . $field->getNumber() . ';' + ); + } + + $buffer->newline(); + + $buffer->append('/* @var string class type */') + ->append('protected static $class_type = "' . $name . '";'); + $buffer->newline(); + + $buffer->append('/* @var array Field descriptors */') + ->append('protected static $fields = array(') + ->increaseIdentation(); + + foreach ($fields as $field) { + $type = $this->_getType($field); + + $buffer->append('self::' . $field->getConstName() . ' => array(') + ->increaseIdentation(); + + if (!is_null($field->getDefault())) { + if ($type == ProtobufMessage::PB_TYPE_STRING) { + $buffer->append( + '\'default\' => \'' . + addslashes($field->getDefault()) . '\', ' + ); + } else { + if ($field->isProtobufScalarType()) { + $buffer->append( + '\'default\' => ' . $field->getDefault() . ', ' + ); + } else { + $className = $this->_createClassName($field->getTypeDescriptor()); + $buffer->append( + '\'default\' => ' . $className . '::' . $field->getDefault() . ', ' + ); + } + } + } + + $buffer->append( + '\'name\' => \'' . addslashes($field->getName()) . '\'' . ',' + ); + + if (!$field->isRepeated()) { + $buffer->append( + '\'required\' => ' . + ($field->isOptional() ? 'false' : 'true') . ',' + ); + } else { + $buffer->append('\'repeated\' => true,'); + } + + if (is_int($type)) { + $buffer->append('\'type\' => ' . $type . ','); + } else { + $buffer->append('\'type\' => \'' . $type . '\''); + } + + $buffer->decreaseIdentation(); + $buffer->append('),'); + } + + $buffer->decreaseIdentation() + ->append(');') + ->newline(); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append( + 'Constructs new message container and clears its internal state' + ) + ->newline() + ->appendParam('return', 'null'); + + $buffer->append($comment) + ->append('public function __construct()') + ->append('{') + ->increaseIdentation() + ->append('$this->reset();') + ->decreaseIdentation() + ->append('}') + ->newline(); + + $buffer->append($comment) + ->append('public function copy($obj)') + ->append('{') + ->increaseIdentation() + ->append('$this->values = $obj->values;') + ->decreaseIdentation() + ->append('}') + ->newline(); + + $comment = new CommentStringBuffer(self::TAB, self::EOL); + $comment->append('Clears message values and sets default ones') + ->newline() + ->appendParam('return', 'null'); + + $buffer->append($comment) + ->append('public function reset()') + ->append('{') + ->increaseIdentation(); + + foreach ($fields as $field) { + $type = $this->_getType($field); + + if ($field->isRepeated()) { + $buffer->append( + '$this->values[self::' . $field->getConstName() . '] = array();' + ); + } else if ($field->isOptional() && !is_null($field->getDefault())) { + if ($field->isProtobufScalarType()) { + $buffer->append( + '$this->values[self::' . $field->getConstName() . '] = ' . + $field->getDefault() . ';' + ); + } else { + $className = $this->_createClassName($field->getTypeDescriptor()); + $buffer->append( + '$this->values[self::' . $field->getConstName() . '] = ' . + $className . '::' . $field->getDefault() . ';' + ); + } + } else { + $buffer->append( + '$this->values[self::' . $field->getConstName() . '] = null;' + ); + } + } + + $buffer->decreaseIdentation() + ->append('}') + ->newline(); + } + + /** + * Parses protobuf file and returns MessageDescriptor + * + * @param FileDescriptor $file File descriptors + * @param string $messageContent Protobuf message content + * @param MessageDescriptor $parent Parent message (if nested) + * + * @return MessageDescriptor + * + * @throws Exception + */ + private function _parseMessageType( + FileDescriptor $file, $messageContent, MessageDescriptor $parent = null, + ServiceDescriptor $service = null + ) { + if ($messageContent == '') { + return; + } + + while (strlen($messageContent) > 0) { + $next = ($this->_next($messageContent)); + + if (strtolower($next) == 'message') { + $messageContent = trim(substr($messageContent, strlen($next))); + $name = $this->_next($messageContent); + + $offset = $this->_getBeginEnd($messageContent, '{', '}'); + // now extract the content and call parse_message again + $content = trim( + substr( + $messageContent, + $offset['begin'] + 1, + $offset['end'] - $offset['begin'] - 2 + ) + ); + + $childMessage = new MessageDescriptor($name, $file, $parent); + $this->_parseMessageType($file, $content, $childMessage, $service); + + $messageContent = '' . trim(substr($messageContent, $offset['end'])); + } else if (strtolower($next) == 'service') { + $messageContent = trim(substr($messageContent, strlen($next))); + $name = $this->_next($messageContent); + + $offset = $this->_getBeginEnd($messageContent, '{', '}'); + // now extract the content and call parse_message again + $content = trim( + substr( + $messageContent, + $offset['begin'] + 1, + $offset['end'] - $offset['begin'] - 2 + ) + ); + + $rpcService = new ServiceDescriptor($name, $file, $service); + $this->_parseMessageType($file, $content, $parent, $rpcService); + + $messageContent = '' . trim(substr($messageContent, $offset['end'])); + } else if (strtolower($next) == 'rpc') { + $messageContent = trim(substr($messageContent, strlen($next))); + $func_input = $this->_next($messageContent); + $func_output = trim(substr($messageContent, strlen($func_input))); + $offset_input = $this->_getBeginEnd($func_input, '(', ')'); + $name = substr($func_input, 0, strpos($func_input, '(', 0)); + $input_type = trim( + substr( + $func_input, + $offset_input['begin'] + 1, + $offset_input['end'] - $offset_input['begin'] - 2 + ) + ); + $offset_output = $this->_getBeginEnd($func_output, '(', ')'); + $output_type = trim( + substr( + $func_output, + $offset_output['begin'] + 1, + $offset_output['end'] - $offset_output['begin'] - 2 + ) + ); + // now extract the content and call parse_message again + + $method = new MethodDescriptor($name, $input_type, $output_type, $file, $service); + // removing it from string + $messageContent = substr( + $messageContent, + strpos($messageContent, ';') + 1, + strlen($messageContent) + ); + $messageContent = '' . trim($messageContent); + } else if (strtolower($next) == 'enum') { + $messageContent = trim(substr($messageContent, strlen($next))); + $name = $this->_next($messageContent); + $offset = $this->_getBeginEnd($messageContent, '{', '}'); + // now extract the content and call parse_message again + $content = trim( + substr( + $messageContent, + $offset['begin'] + 1, + $offset['end'] - $offset['begin'] - 2 + ) + ); + + $enum = new EnumDescriptor($name, $file, $parent); + $this->_parseEnum($enum, $content); + // removing it from string + $messageContent = '' . trim(substr($messageContent, $offset['end'])); + } else if (strtolower($next) == 'import') { + $name = $this->_next($messageContent); + + $match = preg_match( + '/"([^"]+)";*\s?/', + $messageContent, + $matches, + PREG_OFFSET_CAPTURE + ); + + if (!$match) { + throw new Exception( + 'Malformed include / look at your import statement: ' . + $messageContent + ); + } + + $includedFilename = $matches[1][0]; + + if (!file_exists($includedFilename)) { + throw new Exception( + 'Included file ' . $includedFilename . ' does not exist' + ); + } + + $messageContent = trim( + substr( + $messageContent, + $matches[0][1] + strlen($matches[0][0]) + ) + ); + + $parserKey = realpath($includedFilename); + + if (!isset(self::$_parsers[$parserKey])) { + $pbp = new ProtobufParser($this->_useNativeNamespaces); + self::$_parsers[$parserKey] = $pbp; + } + + $file->addDependency($pbp->parse($includedFilename)); + + } else if (strtolower($next) == 'option') { + + // We don't support option parameters just yet, skip for now. + $messageContent = preg_replace('/^.+\n/', '', $messageContent); + + } else if (strtolower($next) == 'package') { + + $match = preg_match( + '/package[\s]+([^;]+);?/', + $messageContent, + $matches, + PREG_OFFSET_CAPTURE + ); + + if (!$match) { + throw new Exception('Malformed package'); + } + + $file->setPackage($matches[1][0]); + $messageContent = trim( + substr( + $messageContent, + $matches[0][1] + strlen($matches[0][0]) + ) + ); + } else { + // now a normal field + $match = preg_match( + '/(.*);/', + $messageContent, + $matches, + PREG_OFFSET_CAPTURE + ); + + if (!$match || !$parent) { + throw new Exception('Proto file missformed'); + } + + $parent->addField($this->_parseField($matches[0][0])); + + $messageContent = trim( + substr( + $messageContent, + $matches[0][1] + strlen($matches[0][0]) + ) + ); + } + } + } + + /** + * Parses protobuf field properties + * + * @param string $content Protobuf content + * + * @return FieldDescriptor + * @throws Exception + */ + private function _parseField($content) + { + $field = new FieldDescriptor(); + + // parse the default value + $match = preg_match( + '/\[\s?default\s?=\s?([^\[]*)\]\s?;/', + $content, + $matches, + PREG_OFFSET_CAPTURE + ); + + if ($match) { + $field->setDefault($matches[1][0]); + $content = trim(substr($content, 0, $matches[0][1])) . ';'; + } + + // parse the value + $match = preg_match( + '/(optional|required|repeated)[\s]+([^;^=^\s^]+)' . + '[\s]+([^;^=^\s]+)[\s]+=[\s]+([\d]+);/', + $content, + $matches + ); + + if ($match) { + switch ($matches[1]) { + + case 'optional': + $label = FieldLabel::OPTIONAL; + break; + + case 'required': + $label = FieldLabel::REQUIRED; + break; + + case 'repeated': + $label = FieldLabel::REPEATED; + break; + } + + $field->setLabel($label); + + if (($pos = strrpos($matches[2], '.')) !== false) { + $field->setType(substr($matches[2], $pos + 1)); + + if ($pos == 0) { + $field->setNamespace($matches[2][$pos]); + } else { + $field->setNamespace(substr($matches[2], 0, $pos)); + } + } else { + $field->setType($matches[2]); + } + + $field->setName($matches[3]); + $field->setNumber($matches[4]); + } else { + throw new Exception('Syntax error ' . $content); + } + + return $field; + } + + /** + * Resolves message field types + * + * @param MessageDescriptor $descriptor Message descriptor + * @param FileDescriptor $file File descriptor + * + * @throws Exception + * @return null + */ + private function _resolveMessageFieldTypes( + MessageDescriptor $descriptor, FileDescriptor $file + ) { + foreach ($descriptor->getFields() as $field) { + + if ($field->isProtobufScalarType()) { + continue; + } + + $namespace = $field->getNamespace(); + + if (is_null($namespace)) { + if (($type = $descriptor->findType($field->getType())) !== false) { + $field->setTypeDescriptor($type); + continue; + } + + $exists = $this->_namespaces[$file->getPackage()] + [$field->getType()]; + + if (isset($exists)) { + + $field->setTypeDescriptor( + $this->_namespaces[$file->getPackage()][$field->getType()] + ); + + continue; + } + + $exists = isset($this->_namespaces[self::$_globalNamespace]) + && isset($this->_namespaces[self::$_globalNamespace] + [$field->getType()]); + + if ($exists) { + + $field->setTypeDescriptor( + $this->_namespaces[self::GLOBAL_NAMESPACE][$field->getType()] + ); + + continue; + } + + throw new Exception('Type ' . $field->getType() . ' not defined'); + } else if ($namespace[0] == '.') { + if ($namespace == '.') { + $namespace = ''; + } else { + $namespace = substr($namespace, 1); + } + + if (!isset($this->_namespaces[$namespace])) { + throw new Exception( + 'Namespace \'' . $namespace . '\' for type ' . + $field->getType() . ' not defined' + ); + } + + if (!isset($this->_namespaces[$namespace][$field->getType()])) { + throw new Exception( + 'Type ' . $field->getType() . ' not defined in ' . $namespace + ); + } + + $field->setTypeDescriptor( + $this->_namespaces[$namespace][$field->getType()] + ); + + } else { + $type = $descriptor->findType( + $field->getType(), $field->getNamespace() + ); + + if ($type !== false) { + $field->setTypeDescriptor($type); + continue; + } + + if (!isset($this->_namespaces[$namespace])) { + throw new Exception( + 'Namespace ' . $namespace . ' for type ' . + $field->getType() . ' not defined' + ); + } + + $exists = isset($this->_namespaces[$namespace][$field->getType()]); + + if (!$exists) { + throw new Exception( + 'Type ' . $field->getType() . ' not defined in ' . $namespace + ); + } + + $field->setTypeDescriptor( + $this->_namespaces[$namespace][$field->getType()] + ); + } + } + + foreach ($descriptor->getNested() as $nested) { + $this->_resolveMessageFieldTypes($nested, $file); + } + } + + /** + * Resolves namespaces for given file descriptor + * + * @param FileDescriptor $file File descriptor + * + * @return null + */ + private function _resolveNamespaces(FileDescriptor $file) + { + foreach ($file->getEnums() as $descriptor) { + $this->_namespaces[$file->getPackage()] + [$descriptor->getName()] = $descriptor; + } + + foreach ($file->getMessages() as $name => $descriptor) { + $this->_namespaces[$file->getPackage()] + [$descriptor->getName()] = $descriptor; + } + + foreach (self::$_parsers as $parser) { + foreach ($parser->getNamespaces() as $namespace => $descriptors) { + foreach ($descriptors as $name => $descriptor) { + $this->_namespaces[$namespace][$name] = $descriptor; + } + } + } + + foreach ($file->getMessages() as $name => $descriptor) { + $this->_resolveMessageFieldTypes($descriptor, $file); + } + } + + /** + * Parses enum + * + * @param EnumDescriptor $enum Enum descriptor + * @param string $content Protobuf enum description + * + * @return EnumDescriptor + * @throws Exception + */ + private function _parseEnum(EnumDescriptor $enum, $content) + { + $match = preg_match_all('/(.*);\s?/', $content, $matches); + + if (!$match) { + throw new \Exception('Semantic error in Enum!'); + } + + foreach ($matches[1] as $match) { + $split = preg_split('/=/', $match); + + $enum->addValue( + new EnumValueDescriptor(trim($split[0]), trim($split[1])) + ); + } + + return $enum; + } + + /** + * Returns next token from parsed protobuf message + * + * @param string $message Message to parse + * + * @return int|string Next token or -1 if not found + */ + private function _next($message) + { + $match = preg_match( + '/([^\s^\{}]*)/', + $message, + $matches, + PREG_OFFSET_CAPTURE + ); + + if (!$match) { + return -1; + } else { + return trim($matches[0][0]); + } + } + + + /** + * Finds starting, ending char in string + * + * @param string $string String to search + * @param string $char Starting char + * @param string $charend Ending char + * + * @return array + * @throws Exception + */ + private function _getBeginEnd($string, $char, $charend) + { + $offsetBegin = strpos($string, $char); + + if ($offsetBegin === false) { + return array('begin' => -1, 'end' => -1); + } + + $offsetNumber = 1; + $offset = $offsetBegin + 1; + + while ($offsetNumber > 0 && $offset > 0) { + // now search after the end nested { } + $offsetOpen = strpos($string, $char, $offset); + $offsetClose = strpos($string, $charend, $offset); + + if ($offsetOpen < $offsetClose && !($offsetOpen === false)) { + $offset = $offsetOpen + 1; + $offsetNumber++; + } else if (!($offsetClose === false)) { + $offset = $offsetClose + 1; + $offsetNumber--; + } else { + $offset = -1; + } + } + + if ($offset == -1) { + throw new Exception('Protofile failure: ' . $char . ' not nested'); + } + + return array('begin' => $offsetBegin, 'end' => $offset); + } + + /** + * Strips comments in string + * + * @param string &$string String to be stripped + * + * @return null + */ + private function _stripComments(&$string) + { + $string = preg_replace('/\/\/.*/', '', $string); + // now replace empty lines and whitespaces in front + $string = preg_replace('/\\r?\\n\s*/', PHP_EOL, $string); + } +} diff --git a/php/genphp/ProtobufCompiler/ServiceDescriptor.php b/php/genphp/ProtobufCompiler/ServiceDescriptor.php new file mode 100644 index 0000000..637519a --- /dev/null +++ b/php/genphp/ProtobufCompiler/ServiceDescriptor.php @@ -0,0 +1,86 @@ +_name = $name; + $this->_file = $file; + + if (is_null($file)) { + throw new Exception( + 'file descriptor cannot be null' + ); + } + $file->addService($this); + } + + /** + * Returns file descriptor + * + * @return FileDescriptorDescriptor + */ + public function getContaining() + { + return $this->_file; + } + + /** + * Adds new method to service + * + * @param MethodDescriptor $method + * + * @return null + */ + public function addMethod(MethodDescriptor $method) + { + $this->_methods[$method->getName()] = $method; + } + + /** + * Returns file descriptor + * + * @return FileDescriptor + */ + public function getFile() + { + return $this->_file; + } + + /** + * Returns name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Returns get method descriptor + * + * @return MethodDescriptor Array + */ + public function getMethods() + { + return $this->_methods; + } +} + +?> diff --git a/php/genphp/protoc-php.php b/php/genphp/protoc-php.php new file mode 100644 index 0000000..ab27bfd --- /dev/null +++ b/php/genphp/protoc-php.php @@ -0,0 +1,55 @@ + $value) { + switch ($value) { + case '-n' : + case '--use-namespaces' : + $useNamespaces = true; + break; + default : + $optionError = true; + break; + } + array_splice($argv, $key, 1); + } + + if ($optionError || count($argv) != 2) { + printf('USAGE: %s [OPTIONS] PROTO_FILE' . PHP_EOL, $argv[0]); + printf(' -n, --use-namespaces Use native PHP namespaces' . PHP_EOL); + exit(1); + } + + $parser = new ProtobufParser($useNamespaces); + $file = $argv[1]; + + if (!file_exists($file)) { + printf($file . ' does not exist' . PHP_EOL); + exit(1); + } + + if (!is_file($file)) { + printf($file . ' is not a file' . PHP_EOL); + exit(1); + } + + try { + $parser->parse($file); + } catch (Exception $e) { + echo $e->getMessage() . PHP_EOL; + } +} diff --git a/php/sample/client.php b/php/sample/client.php new file mode 100644 index 0000000..e79ee02 --- /dev/null +++ b/php/sample/client.php @@ -0,0 +1,28 @@ +set_message('Hello from qinzuoyan01'); +$response = new EchoResponse(); + +$stub = new EchoServer("127.0.0.1:12321"); +$stub->SetTimeout(3000); +$closure = NULL; + +$stub->TestEcho($request, $response, $closure); +if (!$stub->Failed()) +{ + echo "request succeed : " . $response->get_message() . "\n"; +} +else +{ + echo $stub->ErrorText() . "\n"; +} + +?> diff --git a/php/sample/echo_service.proto b/php/sample/echo_service.proto new file mode 100644 index 0000000..43188f6 --- /dev/null +++ b/php/sample/echo_service.proto @@ -0,0 +1,16 @@ +package sofa.pbrpc.test; + +option cc_generic_services = true; +option java_generic_services = true; + +message EchoRequest { + required string message = 1; +} + +message EchoResponse { + required string message = 1; +} + +service EchoServer { + rpc TestEcho(EchoRequest) returns(EchoResponse); +} diff --git a/php/sample/server.cc b/php/sample/server.cc new file mode 100644 index 0000000..52a110f --- /dev/null +++ b/php/sample/server.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2014 Baidu.com, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Author: qinzuoyan01@baidu.com (Qin Zuoyan) + +#include +#include +#include +#include "echo_service.pb.h" + +class EchoServerImpl : public sofa::pbrpc::test::EchoServer +{ +public: + EchoServerImpl() {} + virtual ~EchoServerImpl() {} + +private: + virtual void TestEcho(google::protobuf::RpcController* controller, + const sofa::pbrpc::test::EchoRequest* request, + sofa::pbrpc::test::EchoResponse* response, + google::protobuf::Closure* done) + { + sofa::pbrpc::RpcController* cntl = static_cast(controller); + SLOG(INFO, "Echo(): request message from %s: %s", + cntl->RemoteAddress().c_str(), request->message().c_str()); + if (cntl->IsHttp()) { + SLOG(INFO, "HTTP-PATH=\"%s\"", cntl->HttpPath().c_str()); + std::map::const_iterator it; + const std::map& query_params = cntl->HttpQueryParams(); + for (it = query_params.begin(); it != query_params.end(); ++it) { + SLOG(INFO, "QueryParam[\"%s\"]=\"%s\"", it->first.c_str(), it->second.c_str()); + } + const std::map& headers = cntl->HttpHeaders(); + for (it = headers.begin(); it != headers.end(); ++it) { + SLOG(INFO, "Header[\"%s\"]=\"%s\"", it->first.c_str(), it->second.c_str()); + } + } + response->set_message("echo message: " + request->message()); + done->Run(); + } +}; + +bool thread_init_func() +{ + sleep(1); + SLOG(INFO, "Init work thread succeed"); + return true; +} + +void thread_dest_func() +{ + SLOG(INFO, "Destroy work thread succeed"); +} + +int main() +{ + SOFA_PBRPC_SET_LOG_LEVEL(NOTICE); + + // Define an rpc server. + sofa::pbrpc::RpcServerOptions options; + options.work_thread_init_func = sofa::pbrpc::NewPermanentExtClosure(&thread_init_func); + options.work_thread_dest_func = sofa::pbrpc::NewPermanentExtClosure(&thread_dest_func); + sofa::pbrpc::RpcServer rpc_server(options); + + // Start rpc server. + if (!rpc_server.Start("0.0.0.0:12321")) { + SLOG(ERROR, "start server failed"); + return EXIT_FAILURE; + } + + // Register service. + sofa::pbrpc::test::EchoServer* echo_service = new EchoServerImpl(); + if (!rpc_server.RegisterService(echo_service)) { + SLOG(ERROR, "export service failed"); + return EXIT_FAILURE; + } + + // Wait signal. + rpc_server.Run(); + + // Stop rpc server. + rpc_server.Stop(); + + // Delete closures. + // Attention: should delete the closures after server stopped, or may be crash. + delete options.work_thread_init_func; + delete options.work_thread_dest_func; + + return EXIT_SUCCESS; +} + +/* vim: set ts=4 sw=4 sts=4 tw=100 */