Added PHP to the global gRPC moe config
Change on 2014/12/09 by mlumish <mlumish@google.com>
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=81727766
diff --git a/src/php/.gitignore b/src/php/.gitignore
new file mode 100755
index 0000000..00fbd96
--- /dev/null
+++ b/src/php/.gitignore
@@ -0,0 +1,18 @@
+.libs/
+build/
+modules/
+autom4te.cache/
+*.lo
+*.la
+.deps
+acinclude.m4
+aclocal.m4
+config.*
+configure*
+Makefile*
+run-tests.php
+
+install-sh
+libtool
+missing
+mkinstalldirs
\ No newline at end of file
diff --git a/src/php/README.md b/src/php/README.md
new file mode 100755
index 0000000..176bcfa
--- /dev/null
+++ b/src/php/README.md
@@ -0,0 +1,56 @@
+# PHP wrapper for the GRPC interfaces.
+
+## LAYOUT
+
+Directory structure is as generated by the PHP utility
+[ext_skel](http://php.net/manual/en/internals2.buildsys.skeleton.php)
+
+## ENVIRONMENT
+
+To build a PHP environment that works with this extension, download and extract
+PHP 5.5 (5.6 may also work), configure it, and install it:
+
+```bash
+apt-get install libxml2 libxml2-dev
+curl http://php.net/get/php-5.5.16.tar.gz
+tar -xf php-5.5.16.tar.gz
+cd php-5.5.16
+./configure --with-zlib=/usr --with-libxml-dir=ext/libxml --with-openssl=/usr/local/ssl
+make
+make install
+```
+
+To also download and install the patched protoc and PHP code generator:
+
+```bash
+apt-get install -y procps
+curl -sSL https://get.rvm.io | sudo bash -s stable --ruby
+git clone sso://team/one-platform-grpc-team/protobuf
+cd protobuf
+./configure
+make
+make install
+git clone sso://team/one-platform-grpc-team/grpc-php-protobuf-php
+cd grpc-php-protobuf-php
+rake pear:package version=1.0
+pear install Protobuf-1.0.tgz
+```
+
+## BUILDING
+
+ 1. In ./ext/grpc, run the command `phpize` (distributed with PHP)
+ 2. Run `./ext/grpc/configure`
+ 3. In ./ext/grpc, run `make` and `sudo make install`
+ 4. In your php.ini file, add the line `extension=grpc.so` to load the
+ extension at PHP startup.
+
+## PHPUnit
+
+This repo now has PHPUnit tests, which can by run by executing
+`./bin/run_tests.sh` after building.
+
+There is also a generated code test (`./bin/run_gen_code_test.sh`), which tests
+the stub `./tests/generated_code/math.php` against a running localhost server
+serving the math service. That stub is generated from
+`./tests/generated_code/math.proto` with the head of the repo
+`sso://team/one-platform-grpc-team/grpc-php-protobuf-php`.
diff --git a/src/php/bin/run_gen_code_test.sh b/src/php/bin/run_gen_code_test.sh
new file mode 100755
index 0000000..ff1a618
--- /dev/null
+++ b/src/php/bin/run_gen_code_test.sh
@@ -0,0 +1,5 @@
+# Runs the generated code test against the ruby server
+cd $(dirname $0)
+GRPC_TEST_HOST=localhost:7070 php -d extension_dir=../ext/grpc/modules/ \
+ -d extension=grpc.so /usr/local/bin/phpunit -v --debug --strict \
+ ../tests/generated_code/GeneratedCodeTest.php
diff --git a/src/php/bin/run_tests.sh b/src/php/bin/run_tests.sh
new file mode 100755
index 0000000..cf4cc78
--- /dev/null
+++ b/src/php/bin/run_tests.sh
@@ -0,0 +1,5 @@
+# Loads the local shared library, and runs all of the test cases in tests/
+# against it
+cd $(dirname $0)
+php -d extension_dir=../ext/grpc/modules/ -d extension=grpc.so \
+ /usr/local/bin/phpunit -v --debug --strict ../tests/unit_tests
diff --git a/src/php/ext/grpc/byte_buffer.c b/src/php/ext/grpc/byte_buffer.c
new file mode 100755
index 0000000..db01831
--- /dev/null
+++ b/src/php/ext/grpc/byte_buffer.c
@@ -0,0 +1,38 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include <string.h>
+
+#include "byte_buffer.h"
+
+#include "grpc/grpc.h"
+#include "grpc/support/slice.h"
+
+grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length) {
+ gpr_slice slice = gpr_slice_malloc(length);
+ memcpy(GPR_SLICE_START_PTR(slice), string, length);
+ return grpc_byte_buffer_create(&slice, 1);
+}
+
+void byte_buffer_to_string(grpc_byte_buffer *buffer,
+ char **out_string,
+ size_t *out_length) {
+ size_t length = grpc_byte_buffer_length(buffer);
+ char *string = ecalloc(length+1, sizeof(char));
+ size_t offset = 0;
+ grpc_byte_buffer_reader *reader = grpc_byte_buffer_reader_create(buffer);
+ gpr_slice next;
+ while(grpc_byte_buffer_reader_next(reader, &next) != 0) {
+ memcpy(string+offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
+ offset += GPR_SLICE_LENGTH(next);
+ }
+ *out_string = string;
+ *out_length = length;
+}
diff --git a/src/php/ext/grpc/byte_buffer.h b/src/php/ext/grpc/byte_buffer.h
new file mode 100755
index 0000000..1dd4769
--- /dev/null
+++ b/src/php/ext/grpc/byte_buffer.h
@@ -0,0 +1,12 @@
+#ifndef NET_GRPC_PHP_GRPC_BYTE_BUFFER_H_
+#define NET_GRPC_PHP_GRPC_BYTE_BUFFER_H_
+
+#include "grpc/grpc.h"
+
+grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length);
+
+void byte_buffer_to_string(grpc_byte_buffer *buffer,
+ char **out_string,
+ size_t *out_length);
+
+#endif /* NET_GRPC_PHP_GRPC_BYTE_BUFFER_H_ */
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
new file mode 100755
index 0000000..be7969f
--- /dev/null
+++ b/src/php/ext/grpc/call.c
@@ -0,0 +1,454 @@
+#include "call.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+#include "zend_hash.h"
+
+#include <stdbool.h>
+
+#include "grpc/support/log.h"
+#include "grpc/grpc.h"
+
+#include "timeval.h"
+#include "channel.h"
+#include "completion_queue.h"
+#include "byte_buffer.h"
+
+/* Frees and destroys an instance of wrapped_grpc_call */
+void free_wrapped_grpc_call(void *object TSRMLS_DC){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)object;
+ if(call->wrapped != NULL){
+ grpc_call_destroy(call->wrapped);
+ }
+ efree(call);
+}
+
+/* Initializes an instance of wrapped_grpc_call to be associated with an object
+ * of a class specified by class_type */
+zend_object_value create_wrapped_grpc_call(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_call *intern;
+
+ intern = (wrapped_grpc_call*)emalloc(sizeof(wrapped_grpc_call));
+ memset(intern, 0, sizeof(wrapped_grpc_call));
+
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t) zend_objects_destroy_object,
+ free_wrapped_grpc_call,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+zval *grpc_php_wrap_call(grpc_call *wrapped){
+ zval *call_object;
+ MAKE_STD_ZVAL(call_object);
+ object_init_ex(call_object, grpc_ce_call);
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ call_object TSRMLS_CC);
+ call->wrapped = wrapped;
+ return call_object;
+}
+
+zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements){
+ int i;
+ zval *array;
+ zval **data = NULL;
+ HashTable *array_hash;
+ zval *inner_array;
+ char *str_key;
+ char *str_val;
+ size_t key_len;
+ MAKE_STD_ZVAL(array);
+ array_init(array);
+ array_hash = Z_ARRVAL_P(array);
+ grpc_metadata *elem;
+ for(i=0; i<count; i++){
+ elem = &elements[i];
+ key_len = strlen(elem->key);
+ str_key = ecalloc(key_len+1, sizeof(char));
+ memcpy(str_key, elem->key, key_len);
+ str_val = ecalloc(elem->value_length+1, sizeof(char));
+ memcpy(str_val, elem->value, elem->value_length);
+ if(zend_hash_find(array_hash,
+ str_key,
+ key_len,
+ (void**)data) == SUCCESS){
+ switch(Z_TYPE_P(*data)){
+ case IS_STRING:
+ MAKE_STD_ZVAL(inner_array);
+ array_init(inner_array);
+ add_next_index_zval(inner_array, *data);
+ add_assoc_zval(array, str_key, inner_array);
+ break;
+ case IS_ARRAY:
+ inner_array = *data;
+ break;
+ default:
+ zend_throw_exception(zend_exception_get_default(),
+ "Metadata hash somehow contains wrong types.",
+ 1 TSRMLS_CC);
+ efree(str_key);
+ efree(str_val);
+ return NULL;
+ }
+ add_next_index_stringl(inner_array,
+ str_val,
+ elem->value_length,
+ false);
+ } else {
+ add_assoc_stringl(array,
+ str_key,
+ str_val,
+ elem->value_length,
+ false);
+ }
+ }
+ return array;
+}
+
+int php_grpc_call_add_metadata_array_walk(void *elem TSRMLS_DC,
+ int num_args,
+ va_list args,
+ zend_hash_key *hash_key){
+ zval **data = (zval**)elem;
+ grpc_metadata metadata;
+ grpc_call *call = va_arg(args, grpc_call*);
+ gpr_uint32 flags = va_arg(args, gpr_uint32);
+ const char *key;
+ HashTable *inner_hash;
+ /* We assume that either two args were passed, and we are in the recursive
+ case (and the second argument is the key), or one arg was passed and
+ hash_key is the string key. */
+ if(num_args > 2){
+ key = va_arg(args, const char*);
+ } else {
+ /* TODO(mlumish): If possible, check that hash_key is a string */
+ key = hash_key->arKey;
+ }
+ switch(Z_TYPE_P(*data)){
+ case IS_STRING:
+ metadata.key = (char*)key;
+ metadata.value = Z_STRVAL_P(*data);
+ metadata.value_length = Z_STRLEN_P(*data);
+ grpc_call_add_metadata(call, &metadata, 0u);
+ break;
+ case IS_ARRAY:
+ inner_hash = Z_ARRVAL_P(*data);
+ zend_hash_apply_with_arguments(inner_hash TSRMLS_CC,
+ php_grpc_call_add_metadata_array_walk,
+ 3,
+ call,
+ flags,
+ key);
+ break;
+ default:
+ zend_throw_exception(zend_exception_get_default(),
+ "Metadata hash somehow contains wrong types.",
+ 1 TSRMLS_CC);
+ }
+ return ZEND_HASH_APPLY_KEEP;
+}
+
+/**
+ * Constructs a new instance of the Call class.
+ * @param Channel $channel The channel to associate the call with. Must not be
+ * closed.
+ * @param string $method The method to call
+ * @param Timeval $absolute_deadline The deadline for completing the call
+ */
+PHP_METHOD(Call, __construct){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ zval *channel_obj;
+ char *method;
+ int method_len;
+ zval *deadline_obj;
+ /* "OsO" == 1 Object, 1 string, 1 Object */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "OsO",
+ &channel_obj, grpc_ce_channel,
+ &method, &method_len,
+ &deadline_obj, grpc_ce_timeval) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "Call expects a Channel, a String, and a Timeval",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_channel *channel =
+ (wrapped_grpc_channel*)zend_object_store_get_object(channel_obj TSRMLS_CC);
+ if(channel->wrapped == NULL) {
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "Call cannot be constructed from a closed Channel",
+ 1 TSRMLS_CC);
+ return;
+ }
+ add_property_zval(getThis(), "channel", channel_obj);
+ wrapped_grpc_timeval *deadline =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(deadline_obj TSRMLS_CC);
+ call->wrapped = grpc_channel_create_call(channel->wrapped,
+ method,
+ channel->target,
+ deadline->wrapped);
+}
+
+/**
+ * Add metadata to the call. All array keys must be strings. If the value is a
+ * string, it is added as a key/value pair. If it is an array, each value is
+ * added paired with the same string
+ * @param array $metadata The metadata to add
+ * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
+ * (optional)
+ * @return Void
+ */
+PHP_METHOD(Call, add_metadata){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ zval *array;
+ HashTable *array_hash;
+ long flags = 0;
+ /* "a|l" == 1 array, 1 optional long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "a|l",
+ &array,
+ &flags) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "add_metadata expects an array and an optional long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ array_hash = Z_ARRVAL_P(array);
+ zend_hash_apply_with_arguments(array_hash TSRMLS_CC,
+ php_grpc_call_add_metadata_array_walk,
+ 2,
+ call->wrapped,
+ (gpr_uint32)flags);
+}
+
+/**
+ * Invoke the RPC. Starts sending metadata and request headers over the wire
+ * @param CompletionQueue $queue The completion queue to use with this call
+ * @param long $invoke_accepted_tag The tag to associate with this invocation
+ * @param long $metadata_tag The tag to associate with returned metadata
+ * @param long $finished_tag The tag to associate with the finished event
+ * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
+ * (optional)
+ * @return long Error code
+ */
+PHP_METHOD(Call, start_invoke){
+ long tag1;
+ long tag2;
+ long tag3;
+ zval *queue_obj;
+ long flags = 0;
+ /* "Olll|l" == 1 Object, 3 mandatory longs, 1 optional long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "Olll|l",
+ &queue_obj, grpc_ce_completion_queue,
+ &tag1,
+ &tag2,
+ &tag3,
+ &flags) == FAILURE){
+ zend_throw_exception(
+ spl_ce_InvalidArgumentException,
+ "start_invoke needs a CompletionQueue, 3 longs, and an optional long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ add_property_zval(getThis(), "completion_queue", queue_obj);
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ wrapped_grpc_completion_queue *queue =
+ (wrapped_grpc_completion_queue*)zend_object_store_get_object(
+ queue_obj TSRMLS_CC);
+ RETURN_LONG(grpc_call_start_invoke(call->wrapped,
+ queue->wrapped,
+ (void*)tag1,
+ (void*)tag2,
+ (void*)tag3,
+ (gpr_uint32)flags));
+}
+
+/**
+ * Accept an incoming RPC, binding a completion queue to it. To be called after
+ * adding metadata to the call, but before sending messages. Can only be called
+ * on the server
+ * @param CompletionQueue $queue The completion queue to use with this call
+ * @param long $finished_tag The tag to associate with the finished event
+ * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
+ * (optional)
+ * @return long Error code
+ */
+PHP_METHOD(Call, accept){
+ long tag;
+ zval *queue_obj;
+ long flags = 0;
+ /* "Ol|l" == 1 Object, 1 mandatory long, 1 optional long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "Ol|l",
+ &queue_obj, grpc_ce_completion_queue,
+ &tag,
+ &flags) == FAILURE){
+ zend_throw_exception(
+ spl_ce_InvalidArgumentException,
+ "accept expects a CompletionQueue, a long, and an optional long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ add_property_zval(getThis(), "completion_queue", queue_obj);
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ wrapped_grpc_completion_queue *queue =
+ (wrapped_grpc_completion_queue*)zend_object_store_get_object(
+ queue_obj TSRMLS_CC);
+ RETURN_LONG(grpc_call_accept(call->wrapped,
+ queue->wrapped,
+ (void*)tag,
+ (gpr_uint32)flags));
+}
+
+/**
+ * Called by clients to cancel an RPC on the server.
+ * @return long Error code
+ */
+PHP_METHOD(Call, cancel){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ RETURN_LONG(grpc_call_cancel(call->wrapped));
+}
+
+/**
+ * Queue a byte buffer for writing
+ * @param string $buffer The buffer to queue for writing
+ * @param long $tag The tag to associate with this write
+ * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
+ * (optional)
+ * @return long Error code
+ */
+PHP_METHOD(Call, start_write){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ char *buffer;
+ int buffer_len;
+ long tag;
+ long flags = 0;
+ /* "Ol|l" == 1 Object, 1 mandatory long, 1 optional long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "sl|l",
+ &buffer, &buffer_len,
+ &tag,
+ &flags) == FAILURE){
+ zend_throw_exception(
+ spl_ce_InvalidArgumentException,
+ "start_write expects a string and an optional long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ RETURN_LONG(grpc_call_start_write(call->wrapped,
+ string_to_byte_buffer(buffer, buffer_len),
+ (void*)tag,
+ (gpr_uint32)flags));
+}
+
+/**
+ * Queue a status for writing
+ * @param long $status_code The status code to send
+ * @param string $status_details The status details to send
+ * @param long $tag The tag to associate with this status
+ * @return long Error code
+ */
+PHP_METHOD(Call, start_write_status){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ long status_code;
+ int status_details_length;
+ long tag;
+ grpc_status status;
+ /* "lsl" == 1 long, 1 string, 1 long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "lsl",
+ &status_code,
+ &status.details, &status_details_length,
+ &tag) == FAILURE){
+ zend_throw_exception(
+ spl_ce_InvalidArgumentException,
+ "start_write_status expects a long, a string, and a long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ status.code = (gpr_uint32)status_code;
+ RETURN_LONG(grpc_call_start_write_status(call->wrapped,
+ status,
+ (void*)tag));
+}
+
+/**
+ * Indicate that there are no more messages to send
+ * @return long Error code
+ */
+PHP_METHOD(Call, writes_done){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ long tag;
+ /* "l" == 1 long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "writes_done expects a long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ RETURN_LONG(grpc_call_writes_done(call->wrapped, (void*)tag));
+}
+
+/**
+ * Initiate a read on a call. Output event contains a byte buffer with the
+ * result of the read
+ * @param long $tag The tag to associate with this read
+ * @return long Error code
+ */
+PHP_METHOD(Call, start_read){
+ wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ long tag;
+ /* "l" == 1 long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "start_read expects a long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ RETURN_LONG(grpc_call_start_read(call->wrapped, (void*)tag));
+}
+
+static zend_function_entry call_methods[] = {
+ PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+ PHP_ME(Call, accept, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, add_metadata, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, start_invoke, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, start_read, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, start_write, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, start_write_status, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Call, writes_done, NULL, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+void grpc_init_call(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\Call", call_methods);
+ ce.create_object = create_wrapped_grpc_call;
+ grpc_ce_call = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h
new file mode 100755
index 0000000..c433e6f
--- /dev/null
+++ b/src/php/ext/grpc/call.h
@@ -0,0 +1,35 @@
+#ifndef NET_GRPC_PHP_GRPC_CALL_H_
+#define NET_GRPC_PHP_GRPC_CALL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+
+/* Class entry for the Call PHP class */
+zend_class_entry *grpc_ce_call;
+
+/* Wrapper struct for grpc_call that can be associated with a PHP object */
+typedef struct wrapped_grpc_call {
+ zend_object std;
+
+ grpc_call *wrapped;
+} wrapped_grpc_call;
+
+/* Initializes the Call PHP class */
+void grpc_init_call(TSRMLS_D);
+
+/* Creates a Call object that wraps the given grpc_call struct */
+zval *grpc_php_wrap_call(grpc_call *wrapped);
+
+/* Creates and returns a PHP associative array of metadata from a C array of
+ * call metadata */
+zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements);
+
+#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */
diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c
new file mode 100755
index 0000000..c2847b9
--- /dev/null
+++ b/src/php/ext/grpc/channel.c
@@ -0,0 +1,182 @@
+#include "channel.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+
+#include <stdbool.h>
+
+#include "grpc/grpc.h"
+#include "grpc/support/log.h"
+#include "grpc/grpc_security.h"
+
+#include "completion_queue.h"
+#include "server.h"
+#include "credentials.h"
+
+/* Frees and destroys an instance of wrapped_grpc_channel */
+void free_wrapped_grpc_channel(void *object TSRMLS_DC){
+ wrapped_grpc_channel *channel = (wrapped_grpc_channel*)object;
+ if(channel->wrapped != NULL){
+ grpc_channel_destroy(channel->wrapped);
+ }
+ efree(channel);
+}
+
+/* Initializes an instance of wrapped_grpc_channel to be associated with an
+ * object of a class specified by class_type */
+zend_object_value create_wrapped_grpc_channel(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_channel *intern;
+ intern = (wrapped_grpc_channel*)emalloc(sizeof(wrapped_grpc_channel));
+ memset(intern, 0, sizeof(wrapped_grpc_channel));
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t)zend_objects_destroy_object,
+ free_wrapped_grpc_channel,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args){
+ HashTable *array_hash;
+ HashPosition array_pointer;
+ int args_index;
+ zval **data;
+ char *key;
+ uint key_len;
+ ulong index;
+ array_hash = Z_ARRVAL_P(args_array);
+ args->num_args = zend_hash_num_elements(array_hash);
+ args->args = ecalloc(args->num_args, sizeof(grpc_arg));
+ args_index = 0;
+ for(zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
+ zend_hash_get_current_data_ex(array_hash,
+ (void**)&data,
+ &array_pointer) == SUCCESS;
+ zend_hash_move_forward_ex(array_hash, &array_pointer)){
+ if(zend_hash_get_current_key_ex(array_hash,
+ &key,
+ &key_len,
+ &index,
+ 0,
+ &array_pointer) != HASH_KEY_IS_STRING){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "args keys must be strings",
+ 1 TSRMLS_CC);
+ return;
+ }
+ args->args[args_index].key = key;
+ switch(Z_TYPE_P(*data)){
+ case IS_LONG:
+ args->args[args_index].value.integer = (int)Z_LVAL_P(*data);
+ break;
+ case IS_STRING:
+ args->args[args_index].value.string = Z_STRVAL_P(*data);
+ break;
+ default:
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "args values must be int or string",
+ 1 TSRMLS_CC);
+ return;
+ }
+ args_index++;
+ }
+}
+
+/**
+ * Construct an instance of the Channel class. If the $args array contains a
+ * "credentials" key mapping to a Credentials object, a secure channel will be
+ * created with those credentials.
+ * @param string $target The hostname to associate with this channel
+ * @param array $args The arguments to pass to the Channel (optional)
+ */
+PHP_METHOD(Channel, __construct){
+ wrapped_grpc_channel *channel =
+ (wrapped_grpc_channel*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ char *target;
+ int target_length;
+ zval *args_array = NULL;
+ grpc_channel_args args;
+ HashTable *array_hash;
+ zval **creds_obj = NULL;
+ wrapped_grpc_credentials *creds = NULL;
+ /* "s|a" == 1 string, 1 optional array */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "s|a",
+ &target, &target_length,
+ &args_array) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "Channel expects a string and an array",
+ 1 TSRMLS_CC);
+ return;
+ }
+ if (args_array == NULL) {
+ channel->wrapped = grpc_channel_create(target, NULL);
+ } else {
+ array_hash = Z_ARRVAL_P(args_array);
+ if(zend_hash_find(array_hash,
+ "credentials",
+ sizeof("credentials"),
+ (void**)&creds_obj) == SUCCESS) {
+ if(zend_get_class_entry(*creds_obj TSRMLS_CC) != grpc_ce_credentials) {
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "credentials must be a Credentials object",
+ 1 TSRMLS_CC);
+ return;
+ }
+ creds = (wrapped_grpc_credentials*)zend_object_store_get_object(
+ *creds_obj TSRMLS_CC);
+ zend_hash_del(array_hash, "credentials", 12);
+ }
+ php_grpc_read_args_array(args_array, &args);
+ if (creds == NULL) {
+ channel->wrapped = grpc_channel_create(target, &args);
+ } else {
+ gpr_log(GPR_DEBUG, "Initialized secure channel");
+ channel->wrapped = grpc_secure_channel_create(creds->wrapped,
+ target,
+ &args);
+ }
+ efree(args.args);
+ }
+ channel->target = ecalloc(target_length+1, sizeof(char));
+ memcpy(channel->target, target, target_length);
+}
+
+/**
+ * Close the channel
+ */
+PHP_METHOD(Channel, close){
+ wrapped_grpc_channel *channel =
+ (wrapped_grpc_channel*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ if(channel->wrapped != NULL) {
+ grpc_channel_destroy(channel->wrapped);
+ channel->wrapped = NULL;
+ }
+}
+
+static zend_function_entry channel_methods[] = {
+ PHP_ME(Channel, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+ PHP_ME(Channel, close, NULL, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+void grpc_init_channel(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods);
+ ce.create_object = create_wrapped_grpc_channel;
+ grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/channel.h b/src/php/ext/grpc/channel.h
new file mode 100755
index 0000000..e36f130
--- /dev/null
+++ b/src/php/ext/grpc/channel.h
@@ -0,0 +1,32 @@
+#ifndef NET_GRPC_PHP_GRPC_CHANNEL_H_
+#define NET_GRPC_PHP_GRPC_CHANNEL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+
+/* Class entry for the PHP Channel class */
+zend_class_entry *grpc_ce_channel;
+
+/* Wrapper struct for grpc_channel that can be associated with a PHP object */
+typedef struct wrapped_grpc_channel {
+ zend_object std;
+
+ grpc_channel *wrapped;
+ char *target;
+} wrapped_grpc_channel;
+
+/* Initializes the Channel class */
+void grpc_init_channel(TSRMLS_D);
+
+/* Iterates through a PHP array and populates args with the contents */
+void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args);
+
+#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */
diff --git a/src/php/ext/grpc/completion_queue.c b/src/php/ext/grpc/completion_queue.c
new file mode 100755
index 0000000..5b7bcfa
--- /dev/null
+++ b/src/php/ext/grpc/completion_queue.c
@@ -0,0 +1,145 @@
+#include "completion_queue.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+
+#include <stdbool.h>
+
+#include "grpc/grpc.h"
+
+#include "event.h"
+#include "timeval.h"
+
+/* Frees and destroys a wrapped instance of grpc_completion_queue */
+void free_wrapped_grpc_completion_queue(void *object TSRMLS_DC){
+ wrapped_grpc_completion_queue *queue = NULL;
+ grpc_event *event;
+ queue = (wrapped_grpc_completion_queue*)object;
+ if(queue->wrapped != NULL){
+ grpc_completion_queue_shutdown(queue->wrapped);
+ event = grpc_completion_queue_next(queue->wrapped, gpr_inf_future);
+ while(event != NULL){
+ if(event->type == GRPC_QUEUE_SHUTDOWN){
+ break;
+ }
+ event = grpc_completion_queue_next(queue->wrapped, gpr_inf_future);
+ }
+ grpc_completion_queue_destroy(queue->wrapped);
+ }
+ efree(queue);
+}
+
+/* Initializes an instance of wrapped_grpc_channel to be associated with an
+ * object of a class specified by class_type */
+zend_object_value create_wrapped_grpc_completion_queue(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_completion_queue *intern;
+
+ intern = (wrapped_grpc_completion_queue*)emalloc(
+ sizeof(wrapped_grpc_completion_queue));
+ memset(intern, 0, sizeof(wrapped_grpc_completion_queue));
+
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t) zend_objects_destroy_object,
+ free_wrapped_grpc_completion_queue,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+/**
+ * Construct an instance of CompletionQueue
+ */
+PHP_METHOD(CompletionQueue, __construct){
+ wrapped_grpc_completion_queue *queue =
+ (wrapped_grpc_completion_queue*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ queue->wrapped = grpc_completion_queue_create();
+}
+
+/**
+ * Blocks until an event is available, the completion queue is being shutdown,
+ * or timeout is reached. Returns NULL on timeout, otherwise the event that
+ * occurred. Callers should call event.finish once they have processed the
+ * event.
+ * @param Timeval $timeout The timeout for the event
+ * @return Event The event that occurred
+ */
+PHP_METHOD(CompletionQueue, next){
+ zval *timeout;
+ /* "O" == 1 Object */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "O",
+ &timeout, grpc_ce_timeval)==FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "next needs a Timeval",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_completion_queue *completion_queue =
+ (wrapped_grpc_completion_queue*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ wrapped_grpc_timeval *wrapped_timeout =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(timeout TSRMLS_CC);
+ grpc_event *event = grpc_completion_queue_next(completion_queue->wrapped,
+ wrapped_timeout->wrapped);
+ if(event == NULL){
+ RETURN_NULL();
+ }
+ zval *wrapped_event = grpc_php_wrap_event(event);
+ RETURN_DESTROY_ZVAL(wrapped_event);
+}
+
+PHP_METHOD(CompletionQueue, pluck){
+ long tag;
+ zval *timeout;
+ /* "lO" == 1 long, 1 Object */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "lO",
+ &tag,
+ &timeout, grpc_ce_timeval)==FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "pluck needs a long and a Timeval",
+ 1 TSRMLS_CC);
+ }
+ wrapped_grpc_completion_queue *completion_queue =
+ (wrapped_grpc_completion_queue*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ wrapped_grpc_timeval *wrapped_timeout =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(timeout TSRMLS_CC);
+ grpc_event *event = grpc_completion_queue_pluck(completion_queue->wrapped,
+ (void*)tag,
+ wrapped_timeout->wrapped);
+ if(event == NULL){
+ RETURN_NULL();
+ }
+ zval *wrapped_event = grpc_php_wrap_event(event);
+ RETURN_DESTROY_ZVAL(wrapped_event);
+}
+
+static zend_function_entry completion_queue_methods[] = {
+ PHP_ME(CompletionQueue, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+ PHP_ME(CompletionQueue, next, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(CompletionQueue, pluck, NULL, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+void grpc_init_completion_queue(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\CompletionQueue", completion_queue_methods);
+ ce.create_object = create_wrapped_grpc_completion_queue;
+ grpc_ce_completion_queue = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/completion_queue.h b/src/php/ext/grpc/completion_queue.h
new file mode 100755
index 0000000..6bf5b16
--- /dev/null
+++ b/src/php/ext/grpc/completion_queue.h
@@ -0,0 +1,29 @@
+#ifndef NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_
+#define NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+
+/* Class entry for the PHP CompletionQueue class */
+zend_class_entry *grpc_ce_completion_queue;
+
+/* Wrapper class for grpc_completion_queue that can be associated with a
+ PHP object */
+typedef struct wrapped_grpc_completion_queue {
+ zend_object std;
+
+ grpc_completion_queue *wrapped;
+} wrapped_grpc_completion_queue;
+
+/* Initialize the CompletionQueue class */
+void grpc_init_completion_queue(TSRMLS_D);
+
+#endif /* NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_ */
diff --git a/src/php/ext/grpc/config.m4 b/src/php/ext/grpc/config.m4
new file mode 100755
index 0000000..2d0db1a
--- /dev/null
+++ b/src/php/ext/grpc/config.m4
@@ -0,0 +1,74 @@
+PHP_ARG_ENABLE(grpc, whether to enable grpc support,
+[ --enable-grpc Enable grpc support])
+
+if test "$PHP_GRPC" != "no"; then
+ dnl Write more examples of tests here...
+
+ dnl # --with-grpc -> check with-path
+ SEARCH_PATH="/usr/local /usr $HOME/grpc_dev" # you might want to change this
+ SEARCH_FOR="include/grpc/grpc.h" # you most likely want to change this
+ if test -r $PHP_GRPC/$SEARCH_FOR; then # path given as parameter
+ GRPC_DIR=$PHP_GRPC
+ else # search default path list
+ AC_MSG_CHECKING([for grpc files in default path])
+ for i in $SEARCH_PATH ; do
+ if test -r $i/$SEARCH_FOR; then
+ GRPC_DIR=$i
+ AC_MSG_RESULT(found in $i)
+ fi
+ done
+ fi
+ if test -z "$GRPC_DIR"; then
+ AC_MSG_RESULT([not found])
+ AC_MSG_ERROR([Please reinstall the grpc distribution])
+ fi
+
+ dnl # --with-grpc -> add include path
+ PHP_ADD_INCLUDE($GRPC_DIR/include)
+
+ LIBS="-lpthread $LIBS"
+
+ dnl PHP_ADD_LIBRARY(pthread,,GRPC_SHARED_LIBADD)
+ GRPC_SHARED_LIBADD="-lpthread $GRPC_SHARED_LIBADD"
+ PHP_ADD_LIBRARY(pthread)
+
+ PHP_ADD_LIBRARY(rt,,GRPC_SHARED_LIBADD)
+ PHP_ADD_LIBRARY(rt)
+
+ PHP_ADD_LIBPATH($GRPC_DIR/lib)
+
+ PHP_CHECK_LIBRARY(gpr,gpr_now,
+ [
+ PHP_ADD_LIBRARY(gpr,,GRPC_SHARED_LIBADD)
+ PHP_ADD_LIBRARY(gpr)
+ AC_DEFINE(HAVE_GPRLIB,1,[ ])
+ ],[
+ AC_MSG_ERROR([wrong gpr lib version or lib not found])
+ ],[
+ -L$GRPC_DIR/lib
+ ])
+
+ PHP_ADD_LIBRARY(event,,GRPC_SHARED_LIBADD)
+ PHP_ADD_LIBRARY(event)
+
+ PHP_ADD_LIBRARY(event_pthreads,,GRPC_SHARED_LIBADD)
+ PHP_ADD_LIBRARY(event_pthreads)
+
+ PHP_ADD_LIBRARY(event_core,,GRPC_SHARED_LIBADD)
+ PHP_ADD_LIBRARY(event_core)
+
+ PHP_CHECK_LIBRARY(grpc,grpc_channel_destroy,
+ [
+ PHP_ADD_LIBRARY(grpc,,GRPC_SHARED_LIBADD)
+ dnl PHP_ADD_LIBRARY_WITH_PATH(grpc, $GRPC_DIR/lib, GRPC_SHARED_LIBADD)
+ AC_DEFINE(HAVE_GRPCLIB,1,[ ])
+ ],[
+ AC_MSG_ERROR([wrong grpc lib version or lib not found])
+ ],[
+ -L$GRPC_DIR/lib
+ ])
+
+ PHP_SUBST(GRPC_SHARED_LIBADD)
+
+ PHP_NEW_EXTENSION(grpc, byte_buffer.c call.c channel.c completion_queue.c credentials.c event.c timeval.c server.c server_credentials.c php_grpc.c, $ext_shared, , -Wall -Werror -pedantic -std=c99)
+fi
diff --git a/src/php/ext/grpc/credentials.c b/src/php/ext/grpc/credentials.c
new file mode 100755
index 0000000..ffafdda
--- /dev/null
+++ b/src/php/ext/grpc/credentials.c
@@ -0,0 +1,171 @@
+#include "credentials.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+#include "zend_hash.h"
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+
+/* Frees and destroys an instance of wrapped_grpc_credentials */
+void free_wrapped_grpc_credentials(void *object TSRMLS_DC){
+ wrapped_grpc_credentials *creds = (wrapped_grpc_credentials*)object;
+ if(creds->wrapped != NULL) {
+ grpc_credentials_release(creds->wrapped);
+ }
+ efree(creds);
+}
+
+/* Initializes an instance of wrapped_grpc_credentials to be associated with an
+ * object of a class specified by class_type */
+zend_object_value create_wrapped_grpc_credentials(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_credentials *intern;
+
+ intern = (wrapped_grpc_credentials*)emalloc(sizeof(wrapped_grpc_credentials));
+ memset(intern, 0, sizeof(wrapped_grpc_credentials));
+
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t) zend_objects_destroy_object,
+ free_wrapped_grpc_credentials,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+zval *grpc_php_wrap_credentials(grpc_credentials *wrapped){
+ zval *credentials_object;
+ MAKE_STD_ZVAL(credentials_object);
+ object_init_ex(credentials_object, grpc_ce_credentials);
+ wrapped_grpc_credentials *credentials =
+ (wrapped_grpc_credentials*)zend_object_store_get_object(
+ credentials_object TSRMLS_CC);
+ credentials->wrapped = wrapped;
+ return credentials_object;
+}
+
+/**
+ * Create a default credentials object.
+ * @return Credentials The new default credentials object
+ */
+PHP_METHOD(Credentials, createDefault){
+ grpc_credentials *creds = grpc_default_credentials_create();
+ zval *creds_object = grpc_php_wrap_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+/**
+ * Create SSL credentials.
+ * @param string pem_root_certs PEM encoding of the server root certificates
+ * @param string pem_private_key PEM encoding of the client's private key
+ * (optional)
+ * @param string pem_cert_chain PEM encoding of the client's certificate chain
+ * (optional)
+ * @return Credentials The new SSL credentials object
+ */
+PHP_METHOD(Credentials, createSsl){
+ char *pem_root_certs;
+ char *pem_private_key = NULL;
+ char *pem_cert_chain = NULL;
+
+ int root_certs_length, private_key_length = 0, cert_chain_length = 0;
+
+ /* "s|s!s! == 1 string, 2 optional nullable strings */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "s|s!s!",
+ &pem_root_certs, &root_certs_length,
+ &pem_private_key, &private_key_length,
+ &pem_cert_chain, &cert_chain_length) == FAILURE) {
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "createSsl expects 1 to 3 strings",
+ 1 TSRMLS_CC);
+ return;
+ }
+ grpc_credentials *creds = grpc_ssl_credentials_create(
+ (unsigned char*)pem_root_certs, (size_t)root_certs_length,
+ (unsigned char*)pem_private_key, (size_t)private_key_length,
+ (unsigned char*)pem_cert_chain, (size_t)cert_chain_length);
+ zval *creds_object = grpc_php_wrap_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+/**
+ * Create composite credentials from two existing credentials.
+ * @param Credentials cred1 The first credential
+ * @param Credentials cred2 The second credential
+ * @return Credentials The new composite credentials object
+ */
+PHP_METHOD(Credentials, createComposite){
+ zval *cred1_obj;
+ zval *cred2_obj;
+
+ /* "OO" == 3 Objects */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "OO",
+ &cred1_obj, grpc_ce_credentials,
+ &cred2_obj, grpc_ce_credentials) == FAILURE) {
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "createComposite expects 2 Credentials",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_credentials *cred1 =
+ (wrapped_grpc_credentials*)zend_object_store_get_object(
+ cred1_obj TSRMLS_CC);
+ wrapped_grpc_credentials *cred2 =
+ (wrapped_grpc_credentials*)zend_object_store_get_object(
+ cred2_obj TSRMLS_CC);
+ grpc_credentials *creds = grpc_composite_credentials_create(cred1->wrapped,
+ cred2->wrapped);
+ zval *creds_object = grpc_php_wrap_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+/**
+ * Create Google Compute Engine credentials
+ * @return Credentials The new GCE credentials object
+ */
+PHP_METHOD(Credentials, createGce) {
+ grpc_credentials *creds = grpc_compute_engine_credentials_create();
+ zval *creds_object = grpc_php_wrap_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+/**
+ * Create fake credentials. Only to be used for testing.
+ * @return Credentials The new fake credentials object
+ */
+PHP_METHOD(Credentials, createFake) {
+ grpc_credentials *creds = grpc_fake_transport_security_credentials_create();
+ zval *creds_object = grpc_php_wrap_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+static zend_function_entry credentials_methods[] = {
+ PHP_ME(Credentials, createDefault, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Credentials, createSsl, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Credentials, createComposite, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Credentials, createGce, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Credentials, createFake, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_FE_END
+};
+
+void grpc_init_credentials(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\Credentials", credentials_methods);
+ ce.create_object = create_wrapped_grpc_credentials;
+ grpc_ce_credentials = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/credentials.h b/src/php/ext/grpc/credentials.h
new file mode 100755
index 0000000..ba2aa89
--- /dev/null
+++ b/src/php/ext/grpc/credentials.h
@@ -0,0 +1,30 @@
+#ifndef NET_GRPC_PHP_GRPC_CREDENTIALS_H_
+#define NET_GRPC_PHP_GRPC_CREDENTIALS_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+
+/* Class entry for the Credentials PHP class */
+zend_class_entry *grpc_ce_credentials;
+
+/* Wrapper struct for grpc_credentials that can be associated with a PHP
+ * object */
+typedef struct wrapped_grpc_credentials {
+ zend_object std;
+
+ grpc_credentials *wrapped;
+} wrapped_grpc_credentials;
+
+/* Initializes the Credentials PHP class */
+void grpc_init_credentials(TSRMLS_D);
+
+#endif /* NET_GRPC_PHP_GRPC_CREDENTIALS_H_ */
diff --git a/src/php/ext/grpc/event.c b/src/php/ext/grpc/event.c
new file mode 100755
index 0000000..8ec29c9
--- /dev/null
+++ b/src/php/ext/grpc/event.c
@@ -0,0 +1,191 @@
+#include "event.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include <stdbool.h>
+
+#include "grpc/grpc.h"
+
+#include "byte_buffer.h"
+#include "call.h"
+#include "timeval.h"
+
+/* Frees and finishes a wrapped instance of grpc_event */
+void free_wrapped_grpc_event(void *object TSRMLS_DC){
+ wrapped_grpc_event *event = (wrapped_grpc_event*)object;
+ if(event->wrapped != NULL){
+ grpc_event_finish(event->wrapped);
+ }
+ efree(event);
+}
+
+/* Initializes an instance of wrapped_grpc_channel to be associated with an
+ * object of a class specified by class_type */
+zend_object_value create_wrapped_grpc_event(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_event *intern;
+ intern = (wrapped_grpc_event*)emalloc(sizeof(wrapped_grpc_event));
+ memset(intern, 0, sizeof(wrapped_grpc_event));
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t)zend_objects_destroy_object,
+ free_wrapped_grpc_event,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+zval *grpc_php_wrap_event(grpc_event *wrapped){
+ zval *event_object;
+ MAKE_STD_ZVAL(event_object);
+
+ object_init_ex(event_object, grpc_ce_event);
+ wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object(
+ event_object TSRMLS_CC);
+ event->wrapped = wrapped;
+ return event_object;
+}
+
+/**
+ * Get the type of the event
+ * @return long Integer representing the type
+ */
+PHP_METHOD(Event, get_type){
+ wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ RETURN_LONG((long)(event->wrapped->type));
+}
+
+/**
+ * Get the tag of the event
+ * @return long The event's tag
+ */
+PHP_METHOD(Event, get_tag){
+ wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ RETURN_LONG((long)(event->wrapped->tag));
+}
+
+/**
+ * Get the call associated with the event
+ * @return Call The call
+ */
+PHP_METHOD(Event, get_call){
+ wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ zval *call_obj = grpc_php_wrap_call(event->wrapped->call);
+ RETURN_DESTROY_ZVAL(call_obj);
+}
+
+/**
+ * Get the data associated with the event
+ * @return object The data, with type depending on the type field
+ */
+PHP_METHOD(Event, get_data){
+ zval *retval;
+ wrapped_grpc_event *wrapped_event =
+ (wrapped_grpc_event*)zend_object_store_get_object(
+ getThis() TSRMLS_CC);
+ grpc_event *event = wrapped_event->wrapped;
+ char *detail_string;
+ size_t detail_len;
+ char *method_string;
+ size_t method_len;
+ char *host_string;
+ size_t host_len;
+ char *read_string;
+ size_t read_len;
+
+ switch(event->type){
+ case GRPC_QUEUE_SHUTDOWN: RETURN_NULL(); break;
+ case GRPC_READ:
+ if(event->data.read == NULL){
+ RETURN_NULL();
+ } else {
+ byte_buffer_to_string(event->data.read, &read_string, &read_len);
+ RETURN_STRINGL(read_string, read_len, true);
+ }
+ break;
+ case GRPC_INVOKE_ACCEPTED:
+ RETURN_LONG((long)event->data.invoke_accepted); break;
+ case GRPC_WRITE_ACCEPTED:
+ RETURN_LONG((long)event->data.write_accepted); break;
+ case GRPC_FINISH_ACCEPTED:
+ RETURN_LONG((long)event->data.finish_accepted); break;
+ case GRPC_CLIENT_METADATA_READ:
+ retval = grpc_call_create_metadata_array(
+ event->data.client_metadata_read.count,
+ event->data.client_metadata_read.elements);
+ break;
+ case GRPC_FINISHED:
+ MAKE_STD_ZVAL(retval);
+ object_init(retval);
+ add_property_long(retval, "code", event->data.finished.code);
+ if(event->data.finished.details == NULL){
+ add_property_null(retval, "details");
+ } else {
+ detail_len = strlen(event->data.finished.details);
+ detail_string = ecalloc(detail_len+1, sizeof(char));
+ memcpy(detail_string, event->data.finished.details, detail_len);
+ add_property_string(retval,
+ "details",
+ detail_string,
+ true);
+ }
+ break;
+ case GRPC_SERVER_RPC_NEW:
+ MAKE_STD_ZVAL(retval);
+ object_init(retval);
+ method_len = strlen(event->data.server_rpc_new.method);
+ method_string = ecalloc(method_len+1, sizeof(char));
+ memcpy(method_string, event->data.server_rpc_new.method, method_len);
+ add_property_string(retval,
+ "method",
+ method_string,
+ false);
+ host_len = strlen(event->data.server_rpc_new.host);
+ host_string = ecalloc(host_len+1, sizeof(char));
+ memcpy(host_string, event->data.server_rpc_new.host, host_len);
+ add_property_string(retval,
+ "host",
+ host_string,
+ false);
+ add_property_zval(retval,
+ "absolute_timeout",
+ grpc_php_wrap_timeval(
+ event->data.server_rpc_new.deadline));
+ add_property_zval(retval,
+ "metadata",
+ grpc_call_create_metadata_array(
+ event->data.server_rpc_new.metadata_count,
+ event->data.server_rpc_new.metadata_elements));
+ break;
+ default: RETURN_NULL(); break;
+ }
+ RETURN_DESTROY_ZVAL(retval);
+}
+
+static zend_function_entry event_methods[] = {
+ PHP_ME(Event, get_call, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Event, get_data, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Event, get_tag, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Event, get_type, NULL, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+void grpc_init_event(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\Event", event_methods);
+ ce.create_object = create_wrapped_grpc_event;
+ grpc_ce_event = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/event.h b/src/php/ext/grpc/event.h
new file mode 100755
index 0000000..9dc164e
--- /dev/null
+++ b/src/php/ext/grpc/event.h
@@ -0,0 +1,31 @@
+#ifndef NET_GRPC_PHP_GRPC_EVENT_H_
+#define NET_GRPC_PHP_GRPC_EVENT_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+
+/* Class entry for the PHP Event class */
+zend_class_entry *grpc_ce_event;
+
+/* Struct wrapping grpc_event that can be associated with a PHP object */
+typedef struct wrapped_grpc_event {
+ zend_object std;
+
+ grpc_event *wrapped;
+} wrapped_grpc_event;
+
+/* Initialize the Event class */
+void grpc_init_event(TSRMLS_D);
+
+/* Create a new Event object that wraps an existing grpc_event struct */
+zval *grpc_php_wrap_event(grpc_event *wrapped);
+
+#endif /* NET_GRPC_PHP_GRPC_COMPLETION_CHANNEL_H */
diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c
new file mode 100755
index 0000000..c49b845
--- /dev/null
+++ b/src/php/ext/grpc/php_grpc.c
@@ -0,0 +1,247 @@
+#include "call.h"
+#include "channel.h"
+#include "server.h"
+#include "completion_queue.h"
+#include "event.h"
+#include "timeval.h"
+#include "credentials.h"
+#include "server_credentials.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+//ZEND_DECLARE_MODULE_GLOBALS(grpc)
+
+/* {{{ grpc_functions[]
+ *
+ * Every user visible function must have an entry in grpc_functions[].
+ */
+const zend_function_entry grpc_functions[] = {
+ PHP_FE_END /* Must be the last line in grpc_functions[] */
+};
+/* }}} */
+
+/* {{{ grpc_module_entry
+ */
+zend_module_entry grpc_module_entry = {
+#if ZEND_MODULE_API_NO >= 20010901
+ STANDARD_MODULE_HEADER,
+#endif
+ "grpc",
+ grpc_functions,
+ PHP_MINIT(grpc),
+ PHP_MSHUTDOWN(grpc),
+ NULL,
+ NULL,
+ PHP_MINFO(grpc),
+#if ZEND_MODULE_API_NO >= 20010901
+ PHP_GRPC_VERSION,
+#endif
+ STANDARD_MODULE_PROPERTIES
+};
+/* }}} */
+
+#ifdef COMPILE_DL_GRPC
+ZEND_GET_MODULE(grpc)
+#endif
+
+/* {{{ PHP_INI
+ */
+/* Remove comments and fill if you need to have entries in php.ini
+PHP_INI_BEGIN()
+ STD_PHP_INI_ENTRY("grpc.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_grpc_globals, grpc_globals)
+ STD_PHP_INI_ENTRY("grpc.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_grpc_globals, grpc_globals)
+PHP_INI_END()
+*/
+/* }}} */
+
+/* {{{ php_grpc_init_globals
+ */
+/* Uncomment this function if you have INI entries
+static void php_grpc_init_globals(zend_grpc_globals *grpc_globals)
+{
+ grpc_globals->global_value = 0;
+ grpc_globals->global_string = NULL;
+}
+*/
+/* }}} */
+
+/* {{{ PHP_MINIT_FUNCTION
+ */
+PHP_MINIT_FUNCTION(grpc)
+{
+ /* If you have INI entries, uncomment these lines
+ REGISTER_INI_ENTRIES();
+ */
+ /* Register call error constants */
+ grpc_init();
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_OK", GRPC_CALL_OK, CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR", GRPC_CALL_ERROR, CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_ON_SERVER",
+ GRPC_CALL_ERROR_NOT_ON_SERVER,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_ON_CLIENT",
+ GRPC_CALL_ERROR_NOT_ON_CLIENT,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_ALREADY_INVOKED",
+ GRPC_CALL_ERROR_ALREADY_INVOKED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_INVOKED",
+ GRPC_CALL_ERROR_NOT_INVOKED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_ALREADY_FINISHED",
+ GRPC_CALL_ERROR_ALREADY_FINISHED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_TOO_MANY_OPERATIONS",
+ GRPC_CALL_ERROR_TOO_MANY_OPERATIONS,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_INVALID_FLAGS",
+ GRPC_CALL_ERROR_INVALID_FLAGS,
+ CONST_CS);
+
+ /* Register op error constants */
+ REGISTER_LONG_CONSTANT("Grpc\\OP_OK", GRPC_OP_OK, CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\OP_ERROR", GRPC_OP_ERROR, CONST_CS);
+
+ /* Register flag constants */
+ REGISTER_LONG_CONSTANT("Grpc\\WRITE_BUFFER_HINT",
+ GRPC_WRITE_BUFFER_HINT,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\WRITE_NO_COMPRESS",
+ GRPC_WRITE_NO_COMPRESS,
+ CONST_CS);
+
+ /* Register completion type constants */
+ REGISTER_LONG_CONSTANT("Grpc\\QUEUE_SHUTDOWN",
+ GRPC_QUEUE_SHUTDOWN,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\READ", GRPC_READ, CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\INVOKE_ACCEPTED",
+ GRPC_INVOKE_ACCEPTED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\WRITE_ACCEPTED",
+ GRPC_WRITE_ACCEPTED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\FINISH_ACCEPTED",
+ GRPC_FINISH_ACCEPTED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\CLIENT_METADATA_READ",
+ GRPC_CLIENT_METADATA_READ,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\FINISHED", GRPC_FINISHED, CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\SERVER_RPC_NEW",
+ GRPC_SERVER_RPC_NEW,
+ CONST_CS);
+
+ /* Register status constants */
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_OK",
+ GRPC_STATUS_OK,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_CANCELLED",
+ GRPC_STATUS_CANCELLED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNKNOWN",
+ GRPC_STATUS_UNKNOWN,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_INVALID_ARGUMENT",
+ GRPC_STATUS_INVALID_ARGUMENT,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_DEADLINE_EXCEEDED",
+ GRPC_STATUS_DEADLINE_EXCEEDED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_NOT_FOUND",
+ GRPC_STATUS_NOT_FOUND,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_ALREADY_EXISTS",
+ GRPC_STATUS_ALREADY_EXISTS,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_PERMISSION_DENIED",
+ GRPC_STATUS_PERMISSION_DENIED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNAUTHENTICATED",
+ GRPC_STATUS_UNAUTHENTICATED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_RESOURCE_EXHAUSTED",
+ GRPC_STATUS_RESOURCE_EXHAUSTED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_FAILED_PRECONDITION",
+ GRPC_STATUS_FAILED_PRECONDITION,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_ABORTED",
+ GRPC_STATUS_ABORTED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_OUT_OF_RANGE",
+ GRPC_STATUS_OUT_OF_RANGE,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNIMPLEMENTED",
+ GRPC_STATUS_UNIMPLEMENTED,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_INTERNAL",
+ GRPC_STATUS_INTERNAL,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNAVAILABLE",
+ GRPC_STATUS_UNAVAILABLE,
+ CONST_CS);
+ REGISTER_LONG_CONSTANT("Grpc\\STATUS_DATA_LOSS",
+ GRPC_STATUS_DATA_LOSS,
+ CONST_CS);
+
+ grpc_init_call(TSRMLS_C);
+ grpc_init_channel(TSRMLS_C);
+ grpc_init_server(TSRMLS_C);
+ grpc_init_completion_queue(TSRMLS_C);
+ grpc_init_event(TSRMLS_C);
+ grpc_init_timeval(TSRMLS_C);
+ grpc_init_credentials(TSRMLS_C);
+ grpc_init_server_credentials(TSRMLS_C);
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MSHUTDOWN_FUNCTION
+ */
+PHP_MSHUTDOWN_FUNCTION(grpc)
+{
+ /* uncomment this line if you have INI entries
+ UNREGISTER_INI_ENTRIES();
+ */
+ grpc_shutdown_timeval(TSRMLS_C);
+ grpc_shutdown();
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION
+ */
+PHP_MINFO_FUNCTION(grpc)
+{
+ php_info_print_table_start();
+ php_info_print_table_header(2, "grpc support", "enabled");
+ php_info_print_table_end();
+
+ /* Remove comments if you have entries in php.ini
+ DISPLAY_INI_ENTRIES();
+ */
+}
+/* }}} */
+/* The previous line is meant for vim and emacs, so it can correctly fold and
+ unfold functions in source code. See the corresponding marks just before
+ function definition, where the functions purpose is also documented. Please
+ follow this convention for the convenience of others editing your code.
+*/
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/src/php/ext/grpc/php_grpc.h b/src/php/ext/grpc/php_grpc.h
new file mode 100755
index 0000000..777e0c4
--- /dev/null
+++ b/src/php/ext/grpc/php_grpc.h
@@ -0,0 +1,66 @@
+
+#ifndef PHP_GRPC_H
+#define PHP_GRPC_H
+
+#include <stdbool.h>
+
+extern zend_module_entry grpc_module_entry;
+#define phpext_grpc_ptr &grpc_module_entry
+
+#define PHP_GRPC_VERSION "0.1.0" /* Replace with version number for your extension */
+
+#ifdef PHP_WIN32
+# define PHP_GRPC_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PHP_GRPC_API __attribute__ ((visibility("default")))
+#else
+# define PHP_GRPC_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+#include "php.h"
+
+#include "grpc/grpc.h"
+
+#define RETURN_DESTROY_ZVAL(val) \
+ RETURN_ZVAL( \
+ val, \
+ false /* Don't execute copy constructor */, \
+ true /* Dealloc original before returning */)
+
+/* These are all function declarations */
+/* Code that runs at module initialization */
+PHP_MINIT_FUNCTION(grpc);
+/* Code that runs at module shutdown */
+PHP_MSHUTDOWN_FUNCTION(grpc);
+/* Displays information about the module */
+PHP_MINFO_FUNCTION(grpc);
+
+/*
+ Declare any global variables you may need between the BEGIN
+ and END macros here:
+
+ZEND_BEGIN_MODULE_GLOBALS(grpc)
+ZEND_END_MODULE_GLOBALS(grpc)
+*/
+
+/* In every utility function you add that needs to use variables
+ in php_grpc_globals, call TSRMLS_FETCH(); after declaring other
+ variables used by that function, or better yet, pass in TSRMLS_CC
+ after the last function argument and declare your utility function
+ with TSRMLS_DC after the last declared argument. Always refer to
+ the globals in your function as GRPC_G(variable). You are
+ encouraged to rename these macros something shorter, see
+ examples in any other php module directory.
+*/
+
+#ifdef ZTS
+#define GRPC_G(v) TSRMG(grpc_globals_id, zend_grpc_globals *, v)
+#else
+#define GRPC_G(v) (grpc_globals.v)
+#endif
+
+#endif /* PHP_GRPC_H */
diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c
new file mode 100755
index 0000000..7e98713
--- /dev/null
+++ b/src/php/ext/grpc/server.c
@@ -0,0 +1,202 @@
+#include "call.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+
+#include <stdbool.h>
+
+#include "grpc/grpc.h"
+#include "grpc/support/log.h"
+#include "grpc/grpc_security.h"
+
+#include "server.h"
+#include "completion_queue.h"
+#include "channel.h"
+#include "server_credentials.h"
+
+/* Frees and destroys an instance of wrapped_grpc_server */
+void free_wrapped_grpc_server(void *object TSRMLS_DC){
+ wrapped_grpc_server *server = (wrapped_grpc_server*)object;
+ if(server->wrapped != NULL){
+ grpc_server_shutdown(server->wrapped);
+ grpc_server_destroy(server->wrapped);
+ }
+ efree(server);
+}
+
+/* Initializes an instance of wrapped_grpc_call to be associated with an object
+ * of a class specified by class_type */
+zend_object_value create_wrapped_grpc_server(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_server *intern;
+
+ intern = (wrapped_grpc_server*)emalloc(sizeof(wrapped_grpc_server));
+ memset(intern, 0, sizeof(wrapped_grpc_server));
+
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t) zend_objects_destroy_object,
+ free_wrapped_grpc_server,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+/**
+ * Constructs a new instance of the Server class
+ * @param CompletionQueue $queue The completion queue to use with the server
+ * @param array $args The arguments to pass to the server (optional)
+ */
+PHP_METHOD(Server, __construct){
+ wrapped_grpc_server *server =
+ (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ zval *queue_obj;
+ zval *args_array = NULL;
+ grpc_channel_args args;
+ HashTable *array_hash;
+ zval **creds_obj = NULL;
+ wrapped_grpc_server_credentials *creds = NULL;
+ /* "O|a" == 1 Object, 1 optional array */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "O|a",
+ &queue_obj, grpc_ce_completion_queue,
+ &args_array) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "Server expects a CompletionQueue and an array",
+ 1 TSRMLS_CC);
+ return;
+ }
+ add_property_zval(getThis(), "completion_queue", queue_obj);
+ wrapped_grpc_completion_queue *queue =
+ (wrapped_grpc_completion_queue*)zend_object_store_get_object(
+ queue_obj TSRMLS_CC);
+ if (args_array == NULL) {
+ server->wrapped = grpc_server_create(queue->wrapped, NULL);
+ } else {
+ array_hash = Z_ARRVAL_P(args_array);
+ if(zend_hash_find(array_hash,
+ "credentials",
+ sizeof("credentials"),
+ (void**)&creds_obj) == SUCCESS) {
+ if(zend_get_class_entry(*creds_obj TSRMLS_CC) !=
+ grpc_ce_server_credentials) {
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "credentials must be a ServerCredentials object",
+ 1 TSRMLS_CC);
+ return;
+ }
+ creds = (wrapped_grpc_server_credentials*)zend_object_store_get_object(
+ *creds_obj TSRMLS_CC);
+ zend_hash_del(array_hash, "credentials", sizeof("credentials"));
+ }
+ php_grpc_read_args_array(args_array, &args);
+ if (creds == NULL) {
+ server->wrapped = grpc_server_create(queue->wrapped, &args);
+ } else {
+ gpr_log(GPR_DEBUG, "Initialized secure server");
+ server->wrapped = grpc_secure_server_create(creds->wrapped,
+ queue->wrapped,
+ &args);
+ }
+ efree(args.args);
+ }
+}
+
+/**
+ * Request a call on a server. Creates a single GRPC_SERVER_RPC_NEW event.
+ * @param long $tag_new The tag to associate with the new request
+ * @param long $tag_cancel The tag to use if the call is cancelled
+ * @return Void
+ */
+PHP_METHOD(Server, request_call){
+ wrapped_grpc_server *server =
+ (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ long tag_new;
+ /* "l" == 1 long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "l",
+ &tag_new) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "request_call expects a long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ grpc_server_request_call(server->wrapped, (void*)tag_new);
+}
+
+/**
+ * Add a http2 over tcp listener.
+ * @param string $addr The address to add
+ * @return true on success, false on failure
+ */
+PHP_METHOD(Server, add_http2_port){
+ wrapped_grpc_server *server =
+ (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ const char *addr;
+ int addr_len;
+ /* "s" == 1 string */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "s",
+ &addr, &addr_len) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "add_http2_port expects a string",
+ 1 TSRMLS_CC);
+ return;
+ }
+ RETURN_BOOL(grpc_server_add_http2_port(server->wrapped, addr));
+}
+
+PHP_METHOD(Server, add_secure_http2_port){
+ wrapped_grpc_server *server =
+ (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ const char *addr;
+ int addr_len;
+ /* "s" == 1 string */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "s",
+ &addr, &addr_len) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "add_http2_port expects a string",
+ 1 TSRMLS_CC);
+ return;
+ }
+ RETURN_BOOL(grpc_server_add_secure_http2_port(server->wrapped, addr));
+}
+
+/**
+ * Start a server - tells all listeners to start listening
+ * @return Void
+ */
+PHP_METHOD(Server, start){
+ wrapped_grpc_server *server =
+ (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ grpc_server_start(server->wrapped);
+}
+
+static zend_function_entry server_methods[] = {
+ PHP_ME(Server, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+ PHP_ME(Server, request_call, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Server, add_http2_port, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Server, add_secure_http2_port, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Server, start, NULL, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+void grpc_init_server(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\Server", server_methods);
+ ce.create_object = create_wrapped_grpc_server;
+ grpc_ce_server = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/server.h b/src/php/ext/grpc/server.h
new file mode 100755
index 0000000..61ed825
--- /dev/null
+++ b/src/php/ext/grpc/server.h
@@ -0,0 +1,28 @@
+#ifndef NET_GRPC_PHP_GRPC_SERVER_H_
+#define NET_GRPC_PHP_GRPC_SERVER_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+
+/* Class entry for the Server PHP class */
+zend_class_entry *grpc_ce_server;
+
+/* Wrapper struct for grpc_server that can be associated with a PHP object */
+typedef struct wrapped_grpc_server {
+ zend_object std;
+
+ grpc_server *wrapped;
+} wrapped_grpc_server;
+
+/* Initializes the Server class */
+void grpc_init_server(TSRMLS_D);
+
+#endif /* NET_GRPC_PHP_GRPC_SERVER_H_ */
diff --git a/src/php/ext/grpc/server_credentials.c b/src/php/ext/grpc/server_credentials.c
new file mode 100755
index 0000000..b07790b
--- /dev/null
+++ b/src/php/ext/grpc/server_credentials.c
@@ -0,0 +1,117 @@
+#include "server_credentials.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+#include "zend_hash.h"
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+
+/* Frees and destroys an instace of wrapped_grpc_server_credentials */
+void free_wrapped_grpc_server_credentials(void *object TSRMLS_DC){
+ wrapped_grpc_server_credentials *creds =
+ (wrapped_grpc_server_credentials*)object;
+ if(creds->wrapped != NULL) {
+ grpc_server_credentials_release(creds->wrapped);
+ }
+ efree(creds);
+}
+
+/* Initializes an instace of wrapped_grpc_server_credentials to be associated
+ * with an object of a class specified by class_type */
+zend_object_value create_wrapped_grpc_server_credentials(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_server_credentials *intern;
+
+ intern = (wrapped_grpc_server_credentials*)emalloc(sizeof(
+ wrapped_grpc_server_credentials));
+ memset(intern, 0, sizeof(wrapped_grpc_server_credentials));
+
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t) zend_objects_destroy_object,
+ free_wrapped_grpc_server_credentials,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+zval *grpc_php_wrap_server_credentials(grpc_server_credentials *wrapped){
+ zval *server_credentials_object;
+ MAKE_STD_ZVAL(server_credentials_object);
+ object_init_ex(server_credentials_object, grpc_ce_server_credentials);
+ wrapped_grpc_server_credentials *server_credentials =
+ (wrapped_grpc_server_credentials*)zend_object_store_get_object(
+ server_credentials_object TSRMLS_CC);
+ server_credentials->wrapped = wrapped;
+ return server_credentials_object;
+}
+
+/**
+ * Create SSL credentials.
+ * @param string pem_root_certs PEM encoding of the server root certificates
+ * @param string pem_private_key PEM encoding of the client's private key
+ * @param string pem_cert_chain PEM encoding of the client's certificate chain
+ * @return Credentials The new SSL credentials object
+ */
+PHP_METHOD(ServerCredentials, createSsl){
+ char *pem_root_certs = 0;
+ char *pem_private_key;
+ char *pem_cert_chain;
+
+ int root_certs_length = 0, private_key_length, cert_chain_length;
+
+ /* "s!ss" == 1 nullable string, 2 strings */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "s!ss",
+ &pem_root_certs, &root_certs_length,
+ &pem_private_key, &private_key_length,
+ &pem_cert_chain, &cert_chain_length) == FAILURE) {
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "createSsl expects 3 strings",
+ 1 TSRMLS_CC);
+ return;
+ }
+ grpc_server_credentials *creds = grpc_ssl_server_credentials_create(
+ (unsigned char*)pem_root_certs, (size_t)root_certs_length,
+ (unsigned char*)pem_private_key, (size_t)private_key_length,
+ (unsigned char*)pem_cert_chain, (size_t)cert_chain_length);
+ zval *creds_object = grpc_php_wrap_server_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+/**
+ * Create fake credentials. Only to be used for testing.
+ * @return ServerCredentials The new fake credentials object
+ */
+PHP_METHOD(ServerCredentials, createFake){
+ grpc_server_credentials *creds =
+ grpc_fake_transport_security_server_credentials_create();
+ zval *creds_object = grpc_php_wrap_server_credentials(creds);
+ RETURN_DESTROY_ZVAL(creds_object);
+}
+
+static zend_function_entry server_credentials_methods[] = {
+ PHP_ME(ServerCredentials, createSsl, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(ServerCredentials, createFake, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_FE_END
+};
+
+void grpc_init_server_credentials(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\ServerCredentials", server_credentials_methods);
+ ce.create_object = create_wrapped_grpc_server_credentials;
+ grpc_ce_server_credentials = zend_register_internal_class(&ce TSRMLS_CC);
+}
diff --git a/src/php/ext/grpc/server_credentials.h b/src/php/ext/grpc/server_credentials.h
new file mode 100755
index 0000000..0a5c785
--- /dev/null
+++ b/src/php/ext/grpc/server_credentials.h
@@ -0,0 +1,30 @@
+#ifndef NET_GRPC_PHP_GRPC_SERVER_CREDENTIALS_H_
+#define NET_GRPC_PHP_GRPC_SERVER_CREDENTIALS_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+
+/* Class entry for the Server_Credentials PHP class */
+zend_class_entry *grpc_ce_server_credentials;
+
+/* Wrapper struct for grpc_server_credentials that can be associated with a PHP
+ * object */
+typedef struct wrapped_grpc_server_credentials {
+ zend_object std;
+
+ grpc_server_credentials *wrapped;
+} wrapped_grpc_server_credentials;
+
+/* Initializes the Server_Credentials PHP class */
+void grpc_init_server_credentials(TSRMLS_D);
+
+#endif /* NET_GRPC_PHP_GRPC_SERVER_CREDENTIALS_H_ */
diff --git a/src/php/ext/grpc/timeval.c b/src/php/ext/grpc/timeval.c
new file mode 100755
index 0000000..7b7e0e6
--- /dev/null
+++ b/src/php/ext/grpc/timeval.c
@@ -0,0 +1,255 @@
+#include "timeval.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/spl/spl_exceptions.h"
+#include "php_grpc.h"
+
+#include "zend_exceptions.h"
+
+#include <stdbool.h>
+
+#include "grpc/grpc.h"
+#include "grpc/support/time.h"
+
+/* Frees and destroys an instance of wrapped_grpc_call */
+void free_wrapped_grpc_timeval(void *object TSRMLS_DC){
+ efree(object);
+}
+
+/* Initializes an instance of wrapped_grpc_timeval to be associated with an
+ * object of a class specified by class_type */
+zend_object_value create_wrapped_grpc_timeval(
+ zend_class_entry *class_type TSRMLS_DC){
+ zend_object_value retval;
+ wrapped_grpc_timeval *intern;
+ intern = (wrapped_grpc_timeval*)emalloc(sizeof(wrapped_grpc_timeval));
+ memset(intern, 0, sizeof(wrapped_grpc_timeval));
+ zend_object_std_init(&intern->std, class_type TSRMLS_CC);
+ object_properties_init(&intern->std, class_type);
+ retval.handle = zend_objects_store_put(
+ intern,
+ (zend_objects_store_dtor_t)zend_objects_destroy_object,
+ free_wrapped_grpc_timeval,
+ NULL TSRMLS_CC);
+ retval.handlers = zend_get_std_object_handlers();
+ return retval;
+}
+
+zval *grpc_php_wrap_timeval(gpr_timespec wrapped){
+ zval *timeval_object;
+ MAKE_STD_ZVAL(timeval_object);
+ object_init_ex(timeval_object, grpc_ce_timeval);
+ wrapped_grpc_timeval *timeval =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(
+ timeval_object TSRMLS_CC);
+ memcpy(&timeval->wrapped, &wrapped, sizeof(gpr_timespec));
+ return timeval_object;
+}
+
+/**
+ * Constructs a new instance of the Timeval class
+ * @param long $usec The number of microseconds in the interval
+ */
+PHP_METHOD(Timeval, __construct){
+ wrapped_grpc_timeval *timeval =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ long microseconds;
+ /* "l" == 1 long */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "l",
+ µseconds) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "Timeval expects a long",
+ 1 TSRMLS_CC);
+ return;
+ }
+ gpr_timespec time = gpr_time_from_micros(microseconds);
+ memcpy(&timeval->wrapped, &time, sizeof(gpr_timespec));
+}
+
+/**
+ * Adds another Timeval to this one and returns the sum. Calculations saturate
+ * at infinities.
+ * @param Timeval $other The other Timeval object to add
+ * @return Timeval A new Timeval object containing the sum
+ */
+PHP_METHOD(Timeval, add){
+ zval *other_obj;
+ /* "O" == 1 Object */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "O",
+ &other_obj, grpc_ce_timeval) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "add expects a Timeval",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_timeval *self =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ wrapped_grpc_timeval *other =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(other_obj TSRMLS_CC);
+ zval *sum = grpc_php_wrap_timeval(gpr_time_add(self->wrapped,
+ other->wrapped));
+ RETURN_DESTROY_ZVAL(sum);
+}
+
+/**
+ * Subtracts another Timeval from this one and returns the difference.
+ * Calculations saturate at infinities.
+ * @param Timeval $other The other Timeval object to subtract
+ * @param Timeval A new Timeval object containing the sum
+ */
+PHP_METHOD(Timeval, subtract){
+ zval *other_obj;
+ /* "O" == 1 Object */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "O",
+ &other_obj, grpc_ce_timeval) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "subtract expects a Timeval",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_timeval *self =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ wrapped_grpc_timeval *other =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(other_obj TSRMLS_CC);
+ zval *diff = grpc_php_wrap_timeval(gpr_time_sub(self->wrapped,
+ other->wrapped));
+ RETURN_DESTROY_ZVAL(diff);
+}
+
+/**
+ * Return negative, 0, or positive according to whether a < b, a == b, or a > b
+ * respectively.
+ * @param Timeval $a The first time to compare
+ * @param Timeval $b The second time to compare
+ * @return long
+ */
+PHP_METHOD(Timeval, compare){
+ zval *a_obj, *b_obj;
+ /* "OO" == 2 Objects */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "OO",
+ &a_obj, grpc_ce_timeval,
+ &b_obj, grpc_ce_timeval) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "compare expects two Timevals",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_timeval *a =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(a_obj TSRMLS_CC);
+ wrapped_grpc_timeval *b =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(b_obj TSRMLS_CC);
+ long result = gpr_time_cmp(a->wrapped, b->wrapped);
+ RETURN_LONG(result);
+}
+
+/**
+ * Checks whether the two times are within $threshold of each other
+ * @param Timeval $a The first time to compare
+ * @param Timeval $b The second time to compare
+ * @param Timeval $threshold The threshold to check against
+ * @return bool True if $a and $b are within $threshold, False otherwise
+ */
+PHP_METHOD(Timeval, similar){
+ zval *a_obj, *b_obj, *thresh_obj;
+ /* "OOO" == 3 Objects */
+ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "OOO",
+ &a_obj, grpc_ce_timeval,
+ &b_obj, grpc_ce_timeval,
+ &thresh_obj, grpc_ce_timeval) == FAILURE){
+ zend_throw_exception(spl_ce_InvalidArgumentException,
+ "compare expects three Timevals",
+ 1 TSRMLS_CC);
+ return;
+ }
+ wrapped_grpc_timeval *a =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(a_obj TSRMLS_CC);
+ wrapped_grpc_timeval *b =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(b_obj TSRMLS_CC);
+ wrapped_grpc_timeval *thresh =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(thresh_obj TSRMLS_CC);
+ int result = gpr_time_similar(a->wrapped, b->wrapped, thresh->wrapped);
+ RETURN_BOOL(result);
+}
+
+/**
+ * Returns the current time as a timeval object
+ * @return Timeval The current time
+ */
+PHP_METHOD(Timeval, now){
+ zval *now = grpc_php_wrap_timeval(gpr_now());
+ RETURN_DESTROY_ZVAL(now);
+}
+
+/**
+ * Returns the zero time interval as a timeval object
+ * @return Timeval Zero length time interval
+ */
+PHP_METHOD(Timeval, zero){
+ zval *grpc_php_timeval_zero = grpc_php_wrap_timeval(gpr_time_0);
+ RETURN_ZVAL(grpc_php_timeval_zero,
+ false, /* Copy original before returning? */
+ true /* Destroy original before returning */);
+}
+
+/**
+ * Returns the infinite future time value as a timeval object
+ * @return Timeval Infinite future time value
+ */
+PHP_METHOD(Timeval, inf_future){
+ zval *grpc_php_timeval_inf_future = grpc_php_wrap_timeval(gpr_inf_future);
+ RETURN_DESTROY_ZVAL(grpc_php_timeval_inf_future);
+}
+
+/**
+ * Returns the infinite past time value as a timeval object
+ * @return Timeval Infinite past time value
+ */
+PHP_METHOD(Timeval, inf_past){
+ zval *grpc_php_timeval_inf_past = grpc_php_wrap_timeval(gpr_inf_past);
+ RETURN_DESTROY_ZVAL(grpc_php_timeval_inf_past);
+}
+
+/**
+ * Sleep until this time, interpreted as an absolute timeout
+ * @return void
+ */
+PHP_METHOD(Timeval, sleep_until){
+ wrapped_grpc_timeval *this =
+ (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC);
+ gpr_sleep_until(this->wrapped);
+}
+
+static zend_function_entry timeval_methods[] = {
+ PHP_ME(Timeval, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+ PHP_ME(Timeval, add, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Timeval, compare, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Timeval, inf_future, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Timeval, inf_past, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Timeval, now, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Timeval, similar, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Timeval, sleep_until, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Timeval, subtract, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Timeval, zero, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_FE_END
+};
+
+void grpc_init_timeval(TSRMLS_D){
+ zend_class_entry ce;
+ INIT_CLASS_ENTRY(ce, "Grpc\\Timeval", timeval_methods);
+ ce.create_object = create_wrapped_grpc_timeval;
+ grpc_ce_timeval = zend_register_internal_class(&ce TSRMLS_CC);
+}
+
+void grpc_shutdown_timeval(TSRMLS_D){
+}
diff --git a/src/php/ext/grpc/timeval.h b/src/php/ext/grpc/timeval.h
new file mode 100755
index 0000000..cfdb0c7
--- /dev/null
+++ b/src/php/ext/grpc/timeval.h
@@ -0,0 +1,35 @@
+#ifndef NET_GRPC_PHP_GRPC_TIMEVAL_H_
+#define NET_GRPC_PHP_GRPC_TIMEVAL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_grpc.h"
+
+#include "grpc/grpc.h"
+#include "grpc/support/time.h"
+
+/* Class entry for the Timeval PHP Class */
+zend_class_entry *grpc_ce_timeval;
+
+/* Wrapper struct for timeval that can be associated with a PHP object */
+typedef struct wrapped_grpc_timeval {
+ zend_object std;
+
+ gpr_timespec wrapped;
+} wrapped_grpc_timeval;
+
+/* Initialize the Timeval PHP class */
+void grpc_init_timeval(TSRMLS_D);
+
+/* Shutdown the Timeval PHP class */
+void grpc_shutdown_timeval(TSRMLS_D);
+
+/* Creates a Timeval object that wraps the given timeval struct */
+zval *grpc_php_wrap_timeval(gpr_timespec wrapped);
+
+#endif /* NET_GRPC_PHP_GRPC_TIMEVAL_H_ */
diff --git a/src/php/lib/Grpc/ActiveCall.php b/src/php/lib/Grpc/ActiveCall.php
new file mode 100755
index 0000000..10a37cd
--- /dev/null
+++ b/src/php/lib/Grpc/ActiveCall.php
@@ -0,0 +1,98 @@
+<?php
+namespace Grpc;
+
+/**
+ * Represents an active call that allows sending and recieving binary data
+ */
+class ActiveCall {
+ private $completion_queue;
+ private $call;
+ private $flags;
+ private $metadata;
+
+ /**
+ * Create a new active call.
+ * @param Channel $channel The channel to communicate on
+ * @param string $method The method to call on the remote server
+ * @param array $metadata Metadata to send with the call, if applicable
+ * @param long $flags Write flags to use with this call
+ */
+ public function __construct(Channel $channel,
+ $method,
+ $metadata = array(),
+ $flags = 0) {
+ $this->completion_queue = new CompletionQueue();
+ $this->call = new Call($channel, $method, Timeval::inf_future());
+ $this->call->add_metadata($metadata, 0);
+ $this->flags = $flags;
+
+ // Invoke the call.
+ $this->call->start_invoke($this->completion_queue,
+ INVOKE_ACCEPTED,
+ CLIENT_METADATA_READ,
+ FINISHED, 0);
+ $this->completion_queue->pluck(INVOKE_ACCEPTED,
+ Timeval::inf_future());
+ $metadata_event = $this->completion_queue->pluck(CLIENT_METADATA_READ,
+ Timeval::inf_future());
+ $this->metadata = $metadata_event->get_data();
+ }
+
+ /**
+ * @return The metadata sent by the server.
+ */
+ public function getMetadata() {
+ return $this->metadata;
+ }
+
+ /**
+ * Cancels the call
+ */
+ public function cancel() {
+ $this->call->cancel();
+ }
+
+ /**
+ * Read a single message from the server.
+ * @return The next message from the server, or null if there is none.
+ */
+ public function read() {
+ $this->call->start_read(READ);
+ $read_event = $this->completion_queue->pluck(READ, Timeval::inf_future());
+ return $read_event->get_data();
+ }
+
+ /**
+ * Write a single message to the server. This cannot be called after
+ * writesDone is called.
+ * @param ByteBuffer $data The data to write
+ */
+ public function write($data) {
+ if($this->call->start_write($data,
+ WRITE_ACCEPTED,
+ $this->flags) != OP_OK) {
+ // TODO(mlumish): more useful error
+ throw new \Exception("Cannot call write after writesDone");
+ }
+ $this->completion_queue->pluck(WRITE_ACCEPTED, Timeval::inf_future());
+ }
+
+ /**
+ * Indicate that no more writes will be sent.
+ */
+ public function writesDone() {
+ $this->call->writes_done(FINISH_ACCEPTED);
+ $this->completion_queue->pluck(FINISH_ACCEPTED, Timeval::inf_future());
+ }
+
+ /**
+ * Wait for the server to send the status, and return it.
+ * @return object The status object, with integer $code and string $details
+ * members
+ */
+ public function getStatus() {
+ $status_event = $this->completion_queue->pluck(FINISHED,
+ Timeval::inf_future());
+ return $status_event->get_data();
+ }
+}
\ No newline at end of file
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
new file mode 100755
index 0000000..7aa0c4a
--- /dev/null
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Grpc;
+
+/**
+ * Base class for generated client stubs. Stub methods are expected to call
+ * _simpleRequest or _streamRequest and return the result.
+ */
+class BaseStub {
+
+ private $channel;
+
+ public function __construct($hostname) {
+ $this->channel = new Channel($hostname, []);
+ }
+
+ /**
+ * Close the communication channel associated with this stub
+ */
+ public function close() {
+ $channel->close();
+ }
+
+ /* This class is intended to be subclassed by generated code, so all functions
+ begin with "_" to avoid name collisions. */
+
+ /**
+ * Call a remote method that takes a single argument and has a single output
+ *
+ * @param string $method The name of the method to call
+ * @param $argument The argument to the method
+ * @param callable $deserialize A function that deserializes the response
+ * @param array $metadata A metadata map to send to the server
+ * @return SimpleSurfaceActiveCall The active call object
+ */
+ protected function _simpleRequest($method,
+ $argument,
+ callable $deserialize,
+ $metadata = array()) {
+ return new SimpleSurfaceActiveCall($this->channel,
+ $method,
+ $deserialize,
+ $argument,
+ $metadata);
+ }
+
+ /**
+ * Call a remote method that takes a stream of arguments and has a single
+ * output
+ *
+ * @param string $method The name of the method to call
+ * @param $arguments An array or Traversable of arguments to stream to the
+ * server
+ * @param callable $deserialize A function that deserializes the response
+ * @param array $metadata A metadata map to send to the server
+ * @return ClientStreamingSurfaceActiveCall The active call object
+ */
+ protected function _clientStreamRequest($method,
+ $arguments,
+ callable $deserialize,
+ $metadata = array()) {
+ return new ClientStreamingSurfaceActiveCall($this->channel,
+ $method,
+ $deserialize,
+ $arguments,
+ $metadata);
+ }
+
+ /**
+ * Call a remote method that takes a single argument and returns a stream of
+ * responses
+ *
+ * @param string $method The name of the method to call
+ * @param $argument The argument to the method
+ * @param callable $deserialize A function that deserializes the responses
+ * @param array $metadata A metadata map to send to the server
+ * @return ServerStreamingSurfaceActiveCall The active call object
+ */
+ protected function _serverStreamRequest($method,
+ $argument,
+ callable $deserialize,
+ $metadata = array()) {
+ return new ServerStreamingSurfaceActiveCall($this->channel,
+ $method,
+ $deserialize,
+ $argument,
+ $metadata);
+ }
+
+ /**
+ * Call a remote method with messages streaming in both directions
+ *
+ * @param string $method The name of the method to call
+ * @param callable $deserialize A function that deserializes the responses
+ * @param array $metadata A metadata map to send to the server
+ * @return BidiStreamingSurfaceActiveCall The active call object
+ */
+ protected function _bidiRequest($method,
+ callable $deserialize,
+ $metadata = array()) {
+ return new BidiStreamingSurfaceActiveCall($this->channel,
+ $method,
+ $deserialize,
+ $metadata);
+ }
+}
diff --git a/src/php/lib/Grpc/SurfaceActiveCall.php b/src/php/lib/Grpc/SurfaceActiveCall.php
new file mode 100755
index 0000000..5f94303
--- /dev/null
+++ b/src/php/lib/Grpc/SurfaceActiveCall.php
@@ -0,0 +1,211 @@
+<?php
+namespace Grpc;
+
+/**
+ * Represents an active call that allows sending and recieving messages.
+ * Subclasses restrict how data can be sent and recieved.
+ */
+abstract class AbstractSurfaceActiveCall {
+ private $active_call;
+ private $deserialize;
+
+ /**
+ * Create a new surface active call.
+ * @param Channel $channel The channel to communicate on
+ * @param string $method The method to call on the remote server
+ * @param callable $deserialize The function to deserialize a value
+ * @param array $metadata Metadata to send with the call, if applicable
+ * @param long $flags Write flags to use with this call
+ */
+ public function __construct(Channel $channel,
+ $method,
+ callable $deserialize,
+ $metadata = array(),
+ $flags = 0) {
+ $this->active_call = new ActiveCall($channel, $method, $metadata, $flags);
+ $this->deserialize = $deserialize;
+ }
+
+ /**
+ * @return The metadata sent by the server
+ */
+ public function getMetadata() {
+ return $this->metadata();
+ }
+
+ /**
+ * Cancels the call
+ */
+ public function cancel() {
+ $this->active_call->cancel();
+ }
+
+ protected function _read() {
+ $response = $this->active_call->read();
+ if ($response == null) {
+ return null;
+ }
+ return call_user_func($this->deserialize, $response);
+ }
+
+ protected function _write($value) {
+ return $this->active_call->write($value->serialize());
+ }
+
+ protected function _writesDone() {
+ $this->active_call->writesDone();
+ }
+
+ protected function _getStatus() {
+ return $this->active_call->getStatus();
+ }
+}
+
+/**
+ * Represents an active call that sends a single message and then gets a single
+ * response.
+ */
+class SimpleSurfaceActiveCall extends AbstractSurfaceActiveCall {
+ /**
+ * Create a new simple (single request/single response) active call.
+ * @param Channel $channel The channel to communicate on
+ * @param string $method The method to call on the remote server
+ * @param callable $deserialize The function to deserialize a value
+ * @param $arg The argument to send
+ * @param array $metadata Metadata to send with the call, if applicable
+ */
+ public function __construct(Channel $channel,
+ $method,
+ callable $deserialize,
+ $arg,
+ $metadata = array()) {
+ parent::__construct($channel, $method, $deserialize, $metadata,
+ \Grpc\WRITE_BUFFER_HINT);
+ $this->_write($arg);
+ $this->_writesDone();
+ }
+
+ /**
+ * Wait for the server to respond with data and a status
+ * @return [response data, status]
+ */
+ public function wait() {
+ $response = $this->_read();
+ $status = $this->_getStatus();
+ return array($response, $status);
+ }
+}
+
+/**
+ * Represents an active call that sends a stream of messages and then gets a
+ * single response.
+ */
+class ClientStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
+ /**
+ * Create a new simple (single request/single response) active call.
+ * @param Channel $channel The channel to communicate on
+ * @param string $method The method to call on the remote server
+ * @param callable $deserialize The function to deserialize a value
+ * @param Traversable $arg_iter The iterator of arguments to send
+ * @param array $metadata Metadata to send with the call, if applicable
+ */
+ public function __construct(Channel $channel,
+ $method,
+ callable $deserialize,
+ $arg_iter,
+ $metadata = array()) {
+ parent::__construct($channel, $method, $deserialize, $metadata, 0);
+ foreach($arg_iter as $arg) {
+ $this->_write($arg);
+ }
+ $this->_writesDone();
+ }
+
+ /**
+ * Wait for the server to respond with data and a status
+ * @return [response data, status]
+ */
+ public function wait() {
+ $response = $this->_read();
+ $status = $this->_getStatus();
+ return array($response, $status);
+ }
+}
+
+/**
+ * Represents an active call that sends a single message and then gets a stream
+ * of reponses
+ */
+class ServerStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
+ /**
+ * Create a new simple (single request/single response) active call.
+ * @param Channel $channel The channel to communicate on
+ * @param string $method The method to call on the remote server
+ * @param callable $deserialize The function to deserialize a value
+ * @param $arg The argument to send
+ * @param array $metadata Metadata to send with the call, if applicable
+ */
+ public function __construct(Channel $channel,
+ $method,
+ callable $deserialize,
+ $arg,
+ $metadata = array()) {
+ parent::__construct($channel, $method, $deserialize, $metadata,
+ \Grpc\WRITE_BUFFER_HINT);
+ $this->_write($arg);
+ $this->_writesDone();
+ }
+
+ /**
+ * @return An iterator of response values
+ */
+ public function responses() {
+ while(($response = $this->_read()) != null) {
+ yield $response;
+ }
+ }
+
+ public function getStatus() {
+ return $this->_getStatus();
+ }
+}
+
+/**
+ * Represents an active call that allows for sending and recieving messages in
+ * streams in any order.
+ */
+class BidiStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
+
+ /**
+ * Reads the next value from the server.
+ * @return The next value from the server, or null if there is none
+ */
+ public function read() {
+ return $this->_read();
+ }
+
+ /**
+ * Writes a single message to the server. This cannot be called after
+ * writesDone is called.
+ * @param $value The message to send
+ */
+ public function write($value) {
+ $this->_write($value);
+ }
+
+ /**
+ * Indicate that no more writes will be sent
+ */
+ public function writesDone() {
+ $this->_writesDone();
+ }
+
+ /**
+ * Wait for the server to send the status, and return it.
+ * @return object The status object, with integer $code and string $details
+ * members
+ */
+ public function getStatus() {
+ return $this->_getStatus();
+ }
+}
\ No newline at end of file
diff --git a/src/php/tests/generated_code/GeneratedCodeTest.php b/src/php/tests/generated_code/GeneratedCodeTest.php
new file mode 100755
index 0000000..d8d726e
--- /dev/null
+++ b/src/php/tests/generated_code/GeneratedCodeTest.php
@@ -0,0 +1,70 @@
+<?php
+require __DIR__ . '/../../lib/Grpc/ActiveCall.php';
+require __DIR__ . '/../../lib/Grpc/SurfaceActiveCall.php';
+require __DIR__ . '/../../lib/Grpc/BaseStub.php';
+require 'DrSlump/Protobuf.php';
+\DrSlump\Protobuf::autoload();
+require 'math.php';
+class GeneratedCodeTest extends PHPUnit_Framework_TestCase {
+ /* These tests require that a server exporting the math service must be
+ * running on $GRPC_TEST_HOST */
+ protected static $client;
+ protected static $timeout;
+ public static function setUpBeforeClass() {
+ self::$client = new math\MathClient(getenv('GRPC_TEST_HOST'));
+ }
+
+ public function testSimpleRequest() {
+ $div_arg = new math\DivArgs();
+ $div_arg->setDividend(7);
+ $div_arg->setDivisor(4);
+ list($response, $status) = self::$client->Div($div_arg)->wait();
+ $this->assertEquals(1, $response->getQuotient());
+ $this->assertEquals(3, $response->getRemainder());
+ $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+ }
+
+ public function testServerStreaming() {
+ $fib_arg = new math\FibArgs();
+ $fib_arg->setLimit(7);
+ $call = self::$client->Fib($fib_arg);
+ $result_array = iterator_to_array($call->responses());
+ $extract_num = function($num){
+ return $num->getNum();
+ };
+ $values = array_map($extract_num, $result_array);
+ $this->assertEquals([1, 1, 2, 3, 5, 8, 13], $values);
+ $status = $call->getStatus();
+ $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+ }
+
+ public function testClientStreaming() {
+ $num_iter = function() {
+ for ($i = 0; $i < 7; $i++) {
+ $num = new math\Num();
+ $num->setNum($i);
+ yield $num;
+ }
+ };
+ $call = self::$client->Sum($num_iter());
+ list($response, $status) = $call->wait();
+ $this->assertEquals(21, $response->getNum());
+ $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+ }
+
+ public function testBidiStreaming() {
+ $call = self::$client->DivMany();
+ for ($i = 0; $i < 7; $i++) {
+ $div_arg = new math\DivArgs();
+ $div_arg->setDividend(2 * $i + 1);
+ $div_arg->setDivisor(2);
+ $call->write($div_arg);
+ $response = $call->read();
+ $this->assertEquals($i, $response->getQuotient());
+ $this->assertEquals(1, $response->getRemainder());
+ }
+ $call->writesDone();
+ $status = $call->getStatus();
+ $this->assertEquals(\Grpc\STATUS_OK, $status->code);
+ }
+}
\ No newline at end of file
diff --git a/src/php/tests/generated_code/math.php b/src/php/tests/generated_code/math.php
new file mode 100755
index 0000000..d50f94e
--- /dev/null
+++ b/src/php/tests/generated_code/math.php
@@ -0,0 +1,479 @@
+<?php
+// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
+// Source: math.proto
+// Date: 2014-11-14 00:00:41
+
+namespace math {
+
+ class DivArgs extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $dividend = null;
+
+ /** @var int */
+ public $divisor = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.DivArgs');
+
+ // REQUIRED INT64 dividend = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "dividend";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_REQUIRED;
+ $descriptor->addField($f);
+
+ // REQUIRED INT64 divisor = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "divisor";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_REQUIRED;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <dividend> has a value
+ *
+ * @return boolean
+ */
+ public function hasDividend(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <dividend> value
+ *
+ * @return \math\DivArgs
+ */
+ public function clearDividend(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <dividend> value
+ *
+ * @return int
+ */
+ public function getDividend(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <dividend> value
+ *
+ * @param int $value
+ * @return \math\DivArgs
+ */
+ public function setDividend( $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <divisor> has a value
+ *
+ * @return boolean
+ */
+ public function hasDivisor(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <divisor> value
+ *
+ * @return \math\DivArgs
+ */
+ public function clearDivisor(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <divisor> value
+ *
+ * @return int
+ */
+ public function getDivisor(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Set <divisor> value
+ *
+ * @param int $value
+ * @return \math\DivArgs
+ */
+ public function setDivisor( $value){
+ return $this->_set(2, $value);
+ }
+ }
+}
+
+namespace math {
+
+ class DivReply extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $quotient = null;
+
+ /** @var int */
+ public $remainder = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.DivReply');
+
+ // REQUIRED INT64 quotient = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "quotient";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_REQUIRED;
+ $descriptor->addField($f);
+
+ // REQUIRED INT64 remainder = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "remainder";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_REQUIRED;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <quotient> has a value
+ *
+ * @return boolean
+ */
+ public function hasQuotient(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <quotient> value
+ *
+ * @return \math\DivReply
+ */
+ public function clearQuotient(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <quotient> value
+ *
+ * @return int
+ */
+ public function getQuotient(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <quotient> value
+ *
+ * @param int $value
+ * @return \math\DivReply
+ */
+ public function setQuotient( $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <remainder> has a value
+ *
+ * @return boolean
+ */
+ public function hasRemainder(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <remainder> value
+ *
+ * @return \math\DivReply
+ */
+ public function clearRemainder(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <remainder> value
+ *
+ * @return int
+ */
+ public function getRemainder(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Set <remainder> value
+ *
+ * @param int $value
+ * @return \math\DivReply
+ */
+ public function setRemainder( $value){
+ return $this->_set(2, $value);
+ }
+ }
+}
+
+namespace math {
+
+ class FibArgs extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $limit = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.FibArgs');
+
+ // OPTIONAL INT64 limit = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "limit";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <limit> has a value
+ *
+ * @return boolean
+ */
+ public function hasLimit(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <limit> value
+ *
+ * @return \math\FibArgs
+ */
+ public function clearLimit(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <limit> value
+ *
+ * @return int
+ */
+ public function getLimit(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <limit> value
+ *
+ * @param int $value
+ * @return \math\FibArgs
+ */
+ public function setLimit( $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace math {
+
+ class Num extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $num = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.Num');
+
+ // REQUIRED INT64 num = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "num";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_REQUIRED;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <num> has a value
+ *
+ * @return boolean
+ */
+ public function hasNum(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <num> value
+ *
+ * @return \math\Num
+ */
+ public function clearNum(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <num> value
+ *
+ * @return int
+ */
+ public function getNum(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <num> value
+ *
+ * @param int $value
+ * @return \math\Num
+ */
+ public function setNum( $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace math {
+
+ class FibReply extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $count = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.FibReply');
+
+ // REQUIRED INT64 count = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "count";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_REQUIRED;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <count> has a value
+ *
+ * @return boolean
+ */
+ public function hasCount(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <count> value
+ *
+ * @return \math\FibReply
+ */
+ public function clearCount(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <count> value
+ *
+ * @return int
+ */
+ public function getCount(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <count> value
+ *
+ * @param int $value
+ * @return \math\FibReply
+ */
+ public function setCount( $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace math {
+
+ class MathClient extends \Grpc\BaseStub {
+ /**
+ * @param math\DivArgs $input
+ * @return math\DivReply
+ */
+ public function Div(\math\DivArgs $argument, $metadata = array()) {
+ return $this->_simpleRequest('/Math/Div', $argument, '\math\DivReply::deserialize', $metadata);
+ }
+ /**
+ * @param math\DivArgs $input
+ * @return math\DivReply
+ */
+ public function DivMany($metadata = array()) {
+ return $this->_bidiRequest('/Math/DivMany', '\math\DivReply::deserialize', $metadata);
+ }
+ /**
+ * @param math\FibArgs $input
+ * @return math\Num
+ */
+ public function Fib($argument, $metadata = array()) {
+ return $this->_serverStreamRequest('/Math/Fib', $argument, '\math\Num::deserialize', $metadata);
+ }
+ /**
+ * @param math\Num $input
+ * @return math\Num
+ */
+ public function Sum($arguments, $metadata = array()) {
+ return $this->_clientStreamRequest('/Math/Sum', $arguments, '\math\Num::deserialize', $metadata);
+ }
+ }
+}
diff --git a/src/php/tests/interop/empty.php b/src/php/tests/interop/empty.php
new file mode 100755
index 0000000..0107f25
--- /dev/null
+++ b/src/php/tests/interop/empty.php
@@ -0,0 +1,26 @@
+<?php
+// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
+// Source: net/proto2/proto/empty.proto
+// Date: 2014-12-03 22:02:20
+
+namespace proto2 {
+
+ class EmptyMessage extends \DrSlump\Protobuf\Message {
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'proto2.EmptyMessage');
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+ }
+}
+
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
new file mode 100755
index 0000000..9810c86
--- /dev/null
+++ b/src/php/tests/interop/interop_client.php
@@ -0,0 +1,191 @@
+<?php
+require __DIR__ . '/../../lib/Grpc/ActiveCall.php';
+require __DIR__ . '/../../lib/Grpc/SurfaceActiveCall.php';
+require __DIR__ . '/../../lib/Grpc/BaseStub.php';
+require 'DrSlump/Protobuf.php';
+\DrSlump\Protobuf::autoload();
+require 'empty.php';
+require 'message_set.php';
+require 'messages.php';
+require 'test.php';
+/**
+ * Assertion function that always exits with an error code if the assertion is
+ * falsy
+ * @param $value Assertion value. Should be true.
+ * @param $error_message Message to display if the assertion is false
+ */
+function hardAssert($value, $error_message) {
+ if(!$value) {
+ echo $error_message . "\n";
+ exit(1);
+ }
+}
+
+/**
+ * Run the empty_unary test.
+ * Currently not tested against any server as of 2014-12-04
+ * @param $stub Stub object that has service methods
+ */
+function emptyUnary($stub) {
+ list($result, $status) = $stub->EmptyCall(new proto2\EmptyMessage())->wait();
+ hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully');
+ hardAssert($result != null, 'Call completed with a null response');
+}
+
+/**
+ * Run the large_unary test.
+ * Passes when run against the C++ server as of 2014-12-04
+ * Not tested against any other server as of 2014-12-04
+ * @param $stub Stub object that has service methods
+ */
+function largeUnary($stub) {
+ $request_len = 271828;
+ $response_len = 314159;
+
+ $request = new grpc\testing\SimpleRequest();
+ $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE);
+ $request->setResponseSize($response_len);
+ $payload = new grpc\testing\Payload();
+ $payload->setType(grpc\testing\PayloadType::COMPRESSABLE);
+ $payload->setBody(str_repeat("\0", $request_len));
+ $request->setPayload($payload);
+
+ list($result, $status) = $stub->UnaryCall($request)->wait();
+ hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully');
+ hardAssert($result != null, 'Call returned a null response');
+ $payload = $result->getPayload();
+ hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE,
+ 'Payload had the wrong type');
+ hardAssert(strlen($payload->getBody()) == $response_len,
+ 'Payload had the wrong length');
+ hardAssert($payload->getBody() == str_repeat("\0", $response_len),
+ 'Payload had the wrong content');
+}
+
+/**
+ * Run the client_streaming test.
+ * Not tested against any server as of 2014-12-04.
+ * @param $stub Stub object that has service methods
+ */
+function clientStreaming($stub) {
+ $request_lengths = array(27182, 8, 1828, 45904);
+
+ $requests = array_map(
+ function($length) {
+ $request = new grpc\testing\StreamingInputCallRequest();
+ $payload = new grpc\testing\Payload();
+ $payload->setBody(str_repeat("\0", $length));
+ $request->setPayload($payload);
+ return $request;
+ }, $request_lengths);
+
+ list($result, $status) = $stub->StreamingInputCall($requests)->wait();
+ hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully');
+ hardAssert($result->getAggregatedPayloadSize() == 74922,
+ 'aggregated_payload_size was incorrect');
+}
+
+/**
+ * Run the server_streaming test.
+ * Not tested against any server as of 2014-12-04.
+ * @param $stub Stub object that has service methods.
+ */
+function serverStreaming($stub) {
+ $sizes = array(31415, 9, 2653, 58979);
+
+ $request = new grpc\testing\StreamingOutputCallRequest();
+ $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE);
+ foreach($sizes as $size) {
+ $response_parameters = new grpc\testing\ResponseParameters();
+ $response_parameters->setSize($size);
+ $request->addResponseParameters($response_parameters);
+ }
+
+ $call = $stub->StreamingOutputCall($request);
+ hardAssert($call->getStatus()->code == Grpc\STATUS_OK,
+ 'Call did not complete successfully');
+ $i = 0;
+ foreach($call->responses() as $value) {
+ hardAssert($i < 4, 'Too many responses');
+ $payload = $value->getPayload();
+ hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE,
+ 'Payload ' . $i . ' had the wrong type');
+ hardAssert(strlen($payload->getBody()) == $sizes[$i],
+ 'Response ' . $i . ' had the wrong length');
+ }
+}
+
+/**
+ * Run the ping_pong test.
+ * Not tested against any server as of 2014-12-04.
+ * @param $stub Stub object that has service methods.
+ */
+function pingPong($stub) {
+ $request_lengths = array(27182, 8, 1828, 45904);
+ $response_lengths = array(31415, 9, 2653, 58979);
+
+ $call = $stub->FullDuplexCall();
+ for($i = 0; $i < 4; $i++) {
+ $request = new grpc\testing\StreamingOutputCallRequest();
+ $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE);
+ $response_parameters = new grpc\testing\ResponseParameters();
+ $response_parameters->setSize($response_lengths[$i]);
+ $request->addResponseParameters($response_parameters);
+ $payload = new grpc\testing\Payload();
+ $payload->setBody(str_repeat("\0", $request_lengths[$i]));
+ $request->setPayload($payload);
+
+ $call->write($request);
+ $response = $call->read();
+
+ hardAssert($response != null, 'Server returned too few responses');
+ $payload = $response->getPayload();
+ hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE,
+ 'Payload ' . $i . ' had the wrong type');
+ hardAssert(strlen($payload->getBody()) == $response_lengths[$i],
+ 'Payload ' . $i . ' had the wrong length');
+ }
+ $call->writesDone();
+ hardAssert($call->read() == null, 'Server returned too many responses');
+ hardAssert($call->getStatus()->code == Grpc\STATUS_OK,
+ 'Call did not complete successfully');
+}
+
+$args = getopt('', array('server_host:', 'server_port:', 'test_case:'));
+if (!array_key_exists('server_host', $args) ||
+ !array_key_exists('server_port', $args) ||
+ !array_key_exists('test_case', $args)) {
+ throw new Exception('Missing argument');
+}
+
+$server_address = $args['server_host'] . ':' . $args['server_port'];
+
+$credentials = Grpc\Credentials::createSsl(
+ file_get_contents(dirname(__FILE__) . '/../data/ca.pem'));
+$stub = new grpc\testing\TestServiceClient(
+ $server_address,
+ [
+ 'grpc.ssl_target_name_override' => 'foo.test.google.com',
+ 'credentials' => $credentials
+ ]);
+
+echo "Connecting to $server_address\n";
+echo "Running test case $args[test_case]\n";
+
+switch($args['test_case']) {
+ case 'empty_unary':
+ emptyUnary($stub);
+ break;
+ case 'large_unary':
+ largeUnary($stub);
+ break;
+ case 'client_streaming':
+ clientStreaming($stub);
+ break;
+ case 'server_streaming':
+ serverStreaming($stub);
+ break;
+ case 'ping_pong':
+ pingPong($stub);
+ break;
+}
\ No newline at end of file
diff --git a/src/php/tests/interop/message_set.php b/src/php/tests/interop/message_set.php
new file mode 100755
index 0000000..c35c6d7
--- /dev/null
+++ b/src/php/tests/interop/message_set.php
@@ -0,0 +1,26 @@
+<?php
+// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
+// Source: net/proto2/bridge/proto/message_set.proto
+// Date: 2014-12-03 22:02:20
+
+namespace proto2\bridge {
+
+ class MessageSet extends \DrSlump\Protobuf\Message {
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'proto2.bridge.MessageSet');
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+ }
+}
+
diff --git a/src/php/tests/interop/messages.php b/src/php/tests/interop/messages.php
new file mode 100755
index 0000000..beaec7c
--- /dev/null
+++ b/src/php/tests/interop/messages.php
@@ -0,0 +1,1011 @@
+<?php
+// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
+// Source: third_party/stubby/testing/proto/messages.proto
+// Date: 2014-12-03 22:02:20
+
+namespace grpc\testing {
+
+ class PayloadType extends \DrSlump\Protobuf\Enum {
+ const COMPRESSABLE = 0;
+ const UNCOMPRESSABLE = 1;
+ const RANDOM = 2;
+ }
+}
+namespace grpc\testing {
+
+ class Payload extends \DrSlump\Protobuf\Message {
+
+ /** @var int - \grpc\testing\PayloadType */
+ public $type = null;
+
+ /** @var string */
+ public $body = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.Payload');
+
+ // OPTIONAL ENUM type = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "type";
+ $f->type = \DrSlump\Protobuf::TYPE_ENUM;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\PayloadType';
+ $descriptor->addField($f);
+
+ // OPTIONAL BYTES body = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "body";
+ $f->type = \DrSlump\Protobuf::TYPE_BYTES;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <type> has a value
+ *
+ * @return boolean
+ */
+ public function hasType(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <type> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function clearType(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <type> value
+ *
+ * @return int - \grpc\testing\PayloadType
+ */
+ public function getType(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <type> value
+ *
+ * @param int - \grpc\testing\PayloadType $value
+ * @return \grpc\testing\Payload
+ */
+ public function setType( $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <body> has a value
+ *
+ * @return boolean
+ */
+ public function hasBody(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <body> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function clearBody(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <body> value
+ *
+ * @return string
+ */
+ public function getBody(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Set <body> value
+ *
+ * @param string $value
+ * @return \grpc\testing\Payload
+ */
+ public function setBody( $value){
+ return $this->_set(2, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class SimpleRequest extends \DrSlump\Protobuf\Message {
+
+ /** @var int - \grpc\testing\PayloadType */
+ public $response_type = null;
+
+ /** @var int */
+ public $response_size = null;
+
+ /** @var \grpc\testing\Payload */
+ public $payload = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleRequest');
+
+ // OPTIONAL ENUM response_type = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "response_type";
+ $f->type = \DrSlump\Protobuf::TYPE_ENUM;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\PayloadType';
+ $descriptor->addField($f);
+
+ // OPTIONAL INT32 response_size = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "response_size";
+ $f->type = \DrSlump\Protobuf::TYPE_INT32;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ // OPTIONAL MESSAGE payload = 3
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 3;
+ $f->name = "payload";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\Payload';
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <response_type> has a value
+ *
+ * @return boolean
+ */
+ public function hasResponseType(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <response_type> value
+ *
+ * @return \grpc\testing\SimpleRequest
+ */
+ public function clearResponseType(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <response_type> value
+ *
+ * @return int - \grpc\testing\PayloadType
+ */
+ public function getResponseType(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <response_type> value
+ *
+ * @param int - \grpc\testing\PayloadType $value
+ * @return \grpc\testing\SimpleRequest
+ */
+ public function setResponseType( $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <response_size> has a value
+ *
+ * @return boolean
+ */
+ public function hasResponseSize(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <response_size> value
+ *
+ * @return \grpc\testing\SimpleRequest
+ */
+ public function clearResponseSize(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <response_size> value
+ *
+ * @return int
+ */
+ public function getResponseSize(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Set <response_size> value
+ *
+ * @param int $value
+ * @return \grpc\testing\SimpleRequest
+ */
+ public function setResponseSize( $value){
+ return $this->_set(2, $value);
+ }
+
+ /**
+ * Check if <payload> has a value
+ *
+ * @return boolean
+ */
+ public function hasPayload(){
+ return $this->_has(3);
+ }
+
+ /**
+ * Clear <payload> value
+ *
+ * @return \grpc\testing\SimpleRequest
+ */
+ public function clearPayload(){
+ return $this->_clear(3);
+ }
+
+ /**
+ * Get <payload> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function getPayload(){
+ return $this->_get(3);
+ }
+
+ /**
+ * Set <payload> value
+ *
+ * @param \grpc\testing\Payload $value
+ * @return \grpc\testing\SimpleRequest
+ */
+ public function setPayload(\grpc\testing\Payload $value){
+ return $this->_set(3, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class SimpleResponse extends \DrSlump\Protobuf\Message {
+
+ /** @var \grpc\testing\Payload */
+ public $payload = null;
+
+ /** @var int */
+ public $effective_gaia_user_id = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleResponse');
+
+ // OPTIONAL MESSAGE payload = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "payload";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\Payload';
+ $descriptor->addField($f);
+
+ // OPTIONAL INT64 effective_gaia_user_id = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "effective_gaia_user_id";
+ $f->type = \DrSlump\Protobuf::TYPE_INT64;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <payload> has a value
+ *
+ * @return boolean
+ */
+ public function hasPayload(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <payload> value
+ *
+ * @return \grpc\testing\SimpleResponse
+ */
+ public function clearPayload(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <payload> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function getPayload(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <payload> value
+ *
+ * @param \grpc\testing\Payload $value
+ * @return \grpc\testing\SimpleResponse
+ */
+ public function setPayload(\grpc\testing\Payload $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <effective_gaia_user_id> has a value
+ *
+ * @return boolean
+ */
+ public function hasEffectiveGaiaUserId(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <effective_gaia_user_id> value
+ *
+ * @return \grpc\testing\SimpleResponse
+ */
+ public function clearEffectiveGaiaUserId(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <effective_gaia_user_id> value
+ *
+ * @return int
+ */
+ public function getEffectiveGaiaUserId(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Set <effective_gaia_user_id> value
+ *
+ * @param int $value
+ * @return \grpc\testing\SimpleResponse
+ */
+ public function setEffectiveGaiaUserId( $value){
+ return $this->_set(2, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class SimpleContext extends \DrSlump\Protobuf\Message {
+
+ /** @var string */
+ public $value = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleContext');
+
+ // OPTIONAL STRING value = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "value";
+ $f->type = \DrSlump\Protobuf::TYPE_STRING;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <value> has a value
+ *
+ * @return boolean
+ */
+ public function hasValue(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <value> value
+ *
+ * @return \grpc\testing\SimpleContext
+ */
+ public function clearValue(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <value> value
+ *
+ * @return string
+ */
+ public function getValue(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <value> value
+ *
+ * @param string $value
+ * @return \grpc\testing\SimpleContext
+ */
+ public function setValue( $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class StreamingInputCallRequest extends \DrSlump\Protobuf\Message {
+
+ /** @var \grpc\testing\Payload */
+ public $payload = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingInputCallRequest');
+
+ // OPTIONAL MESSAGE payload = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "payload";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\Payload';
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <payload> has a value
+ *
+ * @return boolean
+ */
+ public function hasPayload(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <payload> value
+ *
+ * @return \grpc\testing\StreamingInputCallRequest
+ */
+ public function clearPayload(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <payload> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function getPayload(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <payload> value
+ *
+ * @param \grpc\testing\Payload $value
+ * @return \grpc\testing\StreamingInputCallRequest
+ */
+ public function setPayload(\grpc\testing\Payload $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class StreamingInputCallResponse extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $aggregated_payload_size = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingInputCallResponse');
+
+ // OPTIONAL INT32 aggregated_payload_size = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "aggregated_payload_size";
+ $f->type = \DrSlump\Protobuf::TYPE_INT32;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <aggregated_payload_size> has a value
+ *
+ * @return boolean
+ */
+ public function hasAggregatedPayloadSize(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <aggregated_payload_size> value
+ *
+ * @return \grpc\testing\StreamingInputCallResponse
+ */
+ public function clearAggregatedPayloadSize(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <aggregated_payload_size> value
+ *
+ * @return int
+ */
+ public function getAggregatedPayloadSize(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <aggregated_payload_size> value
+ *
+ * @param int $value
+ * @return \grpc\testing\StreamingInputCallResponse
+ */
+ public function setAggregatedPayloadSize( $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class ResponseParameters extends \DrSlump\Protobuf\Message {
+
+ /** @var int */
+ public $size = null;
+
+ /** @var int */
+ public $interval_us = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.ResponseParameters');
+
+ // OPTIONAL INT32 size = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "size";
+ $f->type = \DrSlump\Protobuf::TYPE_INT32;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ // OPTIONAL INT32 interval_us = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "interval_us";
+ $f->type = \DrSlump\Protobuf::TYPE_INT32;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <size> has a value
+ *
+ * @return boolean
+ */
+ public function hasSize(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <size> value
+ *
+ * @return \grpc\testing\ResponseParameters
+ */
+ public function clearSize(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <size> value
+ *
+ * @return int
+ */
+ public function getSize(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <size> value
+ *
+ * @param int $value
+ * @return \grpc\testing\ResponseParameters
+ */
+ public function setSize( $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <interval_us> has a value
+ *
+ * @return boolean
+ */
+ public function hasIntervalUs(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <interval_us> value
+ *
+ * @return \grpc\testing\ResponseParameters
+ */
+ public function clearIntervalUs(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <interval_us> value
+ *
+ * @return int
+ */
+ public function getIntervalUs(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Set <interval_us> value
+ *
+ * @param int $value
+ * @return \grpc\testing\ResponseParameters
+ */
+ public function setIntervalUs( $value){
+ return $this->_set(2, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class StreamingOutputCallRequest extends \DrSlump\Protobuf\Message {
+
+ /** @var int - \grpc\testing\PayloadType */
+ public $response_type = null;
+
+ /** @var \grpc\testing\ResponseParameters[] */
+ public $response_parameters = array();
+
+ /** @var \grpc\testing\Payload */
+ public $payload = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingOutputCallRequest');
+
+ // OPTIONAL ENUM response_type = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "response_type";
+ $f->type = \DrSlump\Protobuf::TYPE_ENUM;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\PayloadType';
+ $descriptor->addField($f);
+
+ // REPEATED MESSAGE response_parameters = 2
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 2;
+ $f->name = "response_parameters";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_REPEATED;
+ $f->reference = '\grpc\testing\ResponseParameters';
+ $descriptor->addField($f);
+
+ // OPTIONAL MESSAGE payload = 3
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 3;
+ $f->name = "payload";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\Payload';
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <response_type> has a value
+ *
+ * @return boolean
+ */
+ public function hasResponseType(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <response_type> value
+ *
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function clearResponseType(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <response_type> value
+ *
+ * @return int - \grpc\testing\PayloadType
+ */
+ public function getResponseType(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <response_type> value
+ *
+ * @param int - \grpc\testing\PayloadType $value
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function setResponseType( $value){
+ return $this->_set(1, $value);
+ }
+
+ /**
+ * Check if <response_parameters> has a value
+ *
+ * @return boolean
+ */
+ public function hasResponseParameters(){
+ return $this->_has(2);
+ }
+
+ /**
+ * Clear <response_parameters> value
+ *
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function clearResponseParameters(){
+ return $this->_clear(2);
+ }
+
+ /**
+ * Get <response_parameters> value
+ *
+ * @param int $idx
+ * @return \grpc\testing\ResponseParameters
+ */
+ public function getResponseParameters($idx = NULL){
+ return $this->_get(2, $idx);
+ }
+
+ /**
+ * Set <response_parameters> value
+ *
+ * @param \grpc\testing\ResponseParameters $value
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function setResponseParameters(\grpc\testing\ResponseParameters $value, $idx = NULL){
+ return $this->_set(2, $value, $idx);
+ }
+
+ /**
+ * Get all elements of <response_parameters>
+ *
+ * @return \grpc\testing\ResponseParameters[]
+ */
+ public function getResponseParametersList(){
+ return $this->_get(2);
+ }
+
+ /**
+ * Add a new element to <response_parameters>
+ *
+ * @param \grpc\testing\ResponseParameters $value
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function addResponseParameters(\grpc\testing\ResponseParameters $value){
+ return $this->_add(2, $value);
+ }
+
+ /**
+ * Check if <payload> has a value
+ *
+ * @return boolean
+ */
+ public function hasPayload(){
+ return $this->_has(3);
+ }
+
+ /**
+ * Clear <payload> value
+ *
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function clearPayload(){
+ return $this->_clear(3);
+ }
+
+ /**
+ * Get <payload> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function getPayload(){
+ return $this->_get(3);
+ }
+
+ /**
+ * Set <payload> value
+ *
+ * @param \grpc\testing\Payload $value
+ * @return \grpc\testing\StreamingOutputCallRequest
+ */
+ public function setPayload(\grpc\testing\Payload $value){
+ return $this->_set(3, $value);
+ }
+ }
+}
+
+namespace grpc\testing {
+
+ class StreamingOutputCallResponse extends \DrSlump\Protobuf\Message {
+
+ /** @var \grpc\testing\Payload */
+ public $payload = null;
+
+
+ /** @var \Closure[] */
+ protected static $__extensions = array();
+
+ public static function descriptor()
+ {
+ $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingOutputCallResponse');
+
+ // OPTIONAL MESSAGE payload = 1
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 1;
+ $f->name = "payload";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\Payload';
+ $descriptor->addField($f);
+
+ foreach (self::$__extensions as $cb) {
+ $descriptor->addField($cb(), true);
+ }
+
+ return $descriptor;
+ }
+
+ /**
+ * Check if <payload> has a value
+ *
+ * @return boolean
+ */
+ public function hasPayload(){
+ return $this->_has(1);
+ }
+
+ /**
+ * Clear <payload> value
+ *
+ * @return \grpc\testing\StreamingOutputCallResponse
+ */
+ public function clearPayload(){
+ return $this->_clear(1);
+ }
+
+ /**
+ * Get <payload> value
+ *
+ * @return \grpc\testing\Payload
+ */
+ public function getPayload(){
+ return $this->_get(1);
+ }
+
+ /**
+ * Set <payload> value
+ *
+ * @param \grpc\testing\Payload $value
+ * @return \grpc\testing\StreamingOutputCallResponse
+ */
+ public function setPayload(\grpc\testing\Payload $value){
+ return $this->_set(1, $value);
+ }
+ }
+}
+
+namespace {
+ \proto2\bridge\MessageSet::extension(function(){
+ // OPTIONAL MESSAGE grpc\testing\SimpleContext\message_set_extension = 71139615
+ $f = new \DrSlump\Protobuf\Field();
+ $f->number = 71139615;
+ $f->name = "grpc\testing\SimpleContext\message_set_extension";
+ $f->type = \DrSlump\Protobuf::TYPE_MESSAGE;
+ $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL;
+ $f->reference = '\grpc\testing\SimpleContext';
+ return $f;
+ });
+}
\ No newline at end of file
diff --git a/src/php/tests/interop/test.php b/src/php/tests/interop/test.php
new file mode 100755
index 0000000..fe6d0fb
--- /dev/null
+++ b/src/php/tests/interop/test.php
@@ -0,0 +1,52 @@
+<?php
+// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0
+// Source: third_party/stubby/testing/proto/test.proto
+// Date: 2014-12-03 22:02:20
+
+namespace grpc\testing {
+
+ class TestServiceClient extends \Grpc\BaseStub {
+ /**
+ * @param proto2\EmptyMessage $input
+ * @return proto2\EmptyMessage
+ */
+ public function EmptyCall(\proto2\EmptyMessage $argument, $metadata = array()) {
+ return $this->_simpleRequest('/TestService/EmptyCall', $argument, '\proto2\EmptyMessage::deserialize', $metadata);
+ }
+ /**
+ * @param grpc\testing\SimpleRequest $input
+ * @return grpc\testing\SimpleResponse
+ */
+ public function UnaryCall(\grpc\testing\SimpleRequest $argument, $metadata = array()) {
+ return $this->_simpleRequest('/TestService/UnaryCall', $argument, '\grpc\testing\SimpleResponse::deserialize', $metadata);
+ }
+ /**
+ * @param grpc\testing\StreamingOutputCallRequest $input
+ * @return grpc\testing\StreamingOutputCallResponse
+ */
+ public function StreamingOutputCall($argument, $metadata = array()) {
+ return $this->_serverStreamRequest('/TestService/StreamingOutputCall', $argument, '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
+ }
+ /**
+ * @param grpc\testing\StreamingInputCallRequest $input
+ * @return grpc\testing\StreamingInputCallResponse
+ */
+ public function StreamingInputCall($arguments, $metadata = array()) {
+ return $this->_clientStreamRequest('/TestService/StreamingInputCall', $arguments, '\grpc\testing\StreamingInputCallResponse::deserialize', $metadata);
+ }
+ /**
+ * @param grpc\testing\StreamingOutputCallRequest $input
+ * @return grpc\testing\StreamingOutputCallResponse
+ */
+ public function FullDuplexCall($metadata = array()) {
+ return $this->_bidiRequest('/TestService/FullDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
+ }
+ /**
+ * @param grpc\testing\StreamingOutputCallRequest $input
+ * @return grpc\testing\StreamingOutputCallResponse
+ */
+ public function HalfDuplexCall($metadata = array()) {
+ return $this->_bidiRequest('/TestService/HalfDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata);
+ }
+ }
+}
diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php
new file mode 100755
index 0000000..150b8c3
--- /dev/null
+++ b/src/php/tests/unit_tests/CallTest.php
@@ -0,0 +1,48 @@
+<?php
+class CallTest extends PHPUnit_Framework_TestCase{
+ static $server;
+
+ public static function setUpBeforeClass() {
+ $cq = new Grpc\CompletionQueue();
+ self::$server = new Grpc\Server($cq, []);
+ self::$server->add_http2_port('localhost:9001');
+ }
+
+ public function setUp() {
+ $this->channel = new Grpc\Channel('localhost:9001', []);
+ $this->call = new Grpc\Call($this->channel,
+ '/foo',
+ Grpc\Timeval::inf_future());
+ }
+
+ /* These test methods with assertTrue(true) at the end just check that the
+ method calls completed without errors. PHPUnit warns for tests with no
+ asserts, and this avoids that warning without changing the meaning of the
+ tests */
+
+ public function testAddEmptyMetadata() {
+ $this->call->add_metadata([], 0);
+ /* Dummy assert: Checks that the previous call completed without error */
+ $this->assertTrue(true);
+ }
+
+ public function testAddSingleMetadata() {
+ $this->call->add_metadata(['key' => 'value'], 0);
+ /* Dummy assert: Checks that the previous call completed without error */
+ $this->assertTrue(true);
+ }
+
+ public function testAddMultiValueMetadata() {
+ $this->call->add_metadata(['key' => ['value1', 'value2']], 0);
+ /* Dummy assert: Checks that the previous call completed without error */
+ $this->assertTrue(true);
+ }
+
+ public function testAddSingleAndMultiValueMetadata() {
+ $this->call->add_metadata(
+ ['key1' => 'value1',
+ 'key2' => ['value2', 'value3']], 0);
+ /* Dummy assert: Checks that the previous call completed without error */
+ $this->assertTrue(true);
+ }
+}
diff --git a/src/php/tests/unit_tests/CompletionQueueTest.php b/src/php/tests/unit_tests/CompletionQueueTest.php
new file mode 100755
index 0000000..f88cd63
--- /dev/null
+++ b/src/php/tests/unit_tests/CompletionQueueTest.php
@@ -0,0 +1,14 @@
+<?php
+class CompletionQueueTest extends PHPUnit_Framework_TestCase{
+ public function testNextReturnsNullWithNoCall() {
+ $cq = new Grpc\CompletionQueue();
+ $event = $cq->next(Grpc\Timeval::zero());
+ $this->assertNull($event);
+ }
+
+ public function testPluckReturnsNullWithNoCall() {
+ $cq = new Grpc\CompletionQueue();
+ $event = $cq->pluck(0, Grpc\Timeval::zero());
+ $this->assertNull($event);
+ }
+}
\ No newline at end of file
diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php
new file mode 100755
index 0000000..a6e4a89
--- /dev/null
+++ b/src/php/tests/unit_tests/EndToEndTest.php
@@ -0,0 +1,185 @@
+<?php
+class EndToEndTest extends PHPUnit_Framework_TestCase{
+ public function setUp() {
+ $this->client_queue = new Grpc\CompletionQueue();
+ $this->server_queue = new Grpc\CompletionQueue();
+ $this->server = new Grpc\Server($this->server_queue, []);
+ $this->server->add_http2_port('localhost:9000');
+ $this->channel = new Grpc\Channel('localhost:9000', []);
+ }
+
+ public function tearDown() {
+ unset($this->channel);
+ unset($this->server);
+ unset($this->client_queue);
+ unset($this->server_queue);
+ }
+
+ public function testSimpleRequestBody() {
+ $deadline = Grpc\Timeval::inf_future();
+ $status_text = 'xyz';
+ $call = new Grpc\Call($this->channel,
+ 'dummy_method',
+ $deadline);
+ $tag = 1;
+ $this->assertEquals(Grpc\CALL_OK,
+ $call->start_invoke($this->client_queue,
+ $tag,
+ $tag,
+ $tag));
+
+ $server_tag = 2;
+
+ // the client invocation was accepted
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type());
+
+ $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // check that a server rpc new was received
+ $this->server->start();
+ $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type());
+ $server_call = $event->get_call();
+ $this->assertNotNull($server_call);
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->accept($this->server_queue, $server_tag));
+
+ // the server sends the status
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->start_write_status(Grpc\STATUS_OK,
+ $status_text,
+ $server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // the client gets CLIENT_METADATA_READ
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type());
+
+ // the client gets FINISHED
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+ $status = $event->get_data();
+ $this->assertEquals(Grpc\STATUS_OK, $status->code);
+ $this->assertEquals($status_text, $status->details);
+
+ // and the server gets FINISHED
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+ $status = $event->get_data();
+
+ unset($call);
+ unset($server_call);
+ }
+
+ public function testClientServerFullRequestResponse() {
+ $deadline = Grpc\Timeval::inf_future();
+ $req_text = 'client_server_full_request_response';
+ $reply_text = 'reply:client_server_full_request_response';
+ $status_text = 'status:client_server_full_response_text';
+
+ $call = new Grpc\Call($this->channel,
+ 'dummy_method',
+ $deadline);
+ $tag = 1;
+ $this->assertEquals(Grpc\CALL_OK,
+ $call->start_invoke($this->client_queue,
+ $tag,
+ $tag,
+ $tag));
+
+ $server_tag = 2;
+
+ // the client invocation was accepted
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type());
+
+ // the client writes
+ $this->assertEquals(Grpc\CALL_OK, $call->start_write($req_text, $tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type());
+
+ // check that a server rpc new was received
+ $this->server->start();
+ $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type());
+ $server_call = $event->get_call();
+ $this->assertNotNull($server_call);
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->accept($this->server_queue, $server_tag));
+
+ // start the server read
+ $this->assertEquals(Grpc\CALL_OK, $server_call->start_read($server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\READ, $event->get_type());
+ $this->assertEquals($req_text, $event->get_data());
+
+ // the server replies
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->start_write($reply_text, $server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type());
+
+ // the client reads the metadata
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type());
+
+ // the client reads the reply
+ $this->assertEquals(Grpc\CALL_OK, $call->start_read($tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\READ, $event->get_type());
+ $this->assertEquals($reply_text, $event->get_data());
+
+ // the client sends writes done
+ $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // the server sends the status
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->start_write_status(GRPC\STATUS_OK,
+ $status_text,
+ $server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // the client gets FINISHED
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+ $status = $event->get_data();
+ $this->assertEquals(Grpc\STATUS_OK, $status->code);
+ $this->assertEquals($status_text, $status->details);
+
+ // and the server gets FINISHED
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+
+ unset($call);
+ unset($server_call);
+ }
+}
\ No newline at end of file
diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php
new file mode 100755
index 0000000..d645c03
--- /dev/null
+++ b/src/php/tests/unit_tests/SecureEndToEndTest.php
@@ -0,0 +1,196 @@
+<?php
+class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
+ public function setUp() {
+ $this->client_queue = new Grpc\CompletionQueue();
+ $this->server_queue = new Grpc\CompletionQueue();
+ $credentials = Grpc\Credentials::createSsl(
+ file_get_contents(dirname(__FILE__) . '/../data/ca.pem'));
+ $server_credentials = Grpc\ServerCredentials::createSsl(
+ null,
+ file_get_contents(dirname(__FILE__) . '/../data/server1.key'),
+ file_get_contents(dirname(__FILE__) . '/../data/server1.pem'));
+ $this->server = new Grpc\Server($this->server_queue,
+ ['credentials' => $server_credentials]);
+ $this->server->add_secure_http2_port('localhost:9000');
+ $this->channel = new Grpc\Channel(
+ 'localhost:9000',
+ [
+ 'grpc.ssl_target_name_override' => 'foo.test.google.com',
+ 'credentials' => $credentials
+ ]);
+ }
+
+ public function tearDown() {
+ unset($this->channel);
+ unset($this->server);
+ unset($this->client_queue);
+ unset($this->server_queue);
+ }
+
+ public function testSimpleRequestBody() {
+ $this->server->start();
+ $deadline = Grpc\Timeval::inf_future();
+ $status_text = 'xyz';
+ $call = new Grpc\Call($this->channel,
+ 'dummy_method',
+ $deadline);
+ $tag = 1;
+ $this->assertEquals(Grpc\CALL_OK,
+ $call->start_invoke($this->client_queue,
+ $tag,
+ $tag,
+ $tag));
+ $server_tag = 2;
+
+ // the client invocation was accepted
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type());
+
+ $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // check that a server rpc new was received
+ $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type());
+ $server_call = $event->get_call();
+ $this->assertNotNull($server_call);
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->accept($this->server_queue, $server_tag));
+
+ // the server sends the status
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->start_write_status(Grpc\STATUS_OK,
+ $status_text,
+ $server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // the client gets CLIENT_METADATA_READ
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type());
+
+ // the client gets FINISHED
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+ $status = $event->get_data();
+ $this->assertEquals(Grpc\STATUS_OK, $status->code);
+ $this->assertEquals($status_text, $status->details);
+
+ // and the server gets FINISHED
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+ $status = $event->get_data();
+
+ unset($call);
+ unset($server_call);
+ }
+
+ public function testClientServerFullRequestResponse() {
+ $this->server->start();
+ $deadline = Grpc\Timeval::inf_future();
+ $req_text = 'client_server_full_request_response';
+ $reply_text = 'reply:client_server_full_request_response';
+ $status_text = 'status:client_server_full_response_text';
+
+ $call = new Grpc\Call($this->channel,
+ 'dummy_method',
+ $deadline);
+ $tag = 1;
+ $this->assertEquals(Grpc\CALL_OK,
+ $call->start_invoke($this->client_queue,
+ $tag,
+ $tag,
+ $tag));
+
+ $server_tag = 2;
+
+ // the client invocation was accepted
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type());
+
+ // the client writes
+ $this->assertEquals(Grpc\CALL_OK, $call->start_write($req_text, $tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type());
+
+ // check that a server rpc new was received
+ $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type());
+ $server_call = $event->get_call();
+ $this->assertNotNull($server_call);
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->accept($this->server_queue, $server_tag));
+
+ // start the server read
+ $this->assertEquals(Grpc\CALL_OK, $server_call->start_read($server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\READ, $event->get_type());
+ $this->assertEquals($req_text, $event->get_data());
+
+ // the server replies
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->start_write($reply_text, $server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type());
+
+ // the client reads the metadata
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type());
+
+ // the client reads the reply
+ $this->assertEquals(Grpc\CALL_OK, $call->start_read($tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\READ, $event->get_type());
+ $this->assertEquals($reply_text, $event->get_data());
+
+ // the client sends writes done
+ $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag));
+ $event = $this->client_queue->next($deadline);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // the server sends the status
+ $this->assertEquals(Grpc\CALL_OK,
+ $server_call->start_write_status(GRPC\STATUS_OK,
+ $status_text,
+ $server_tag));
+ $event = $this->server_queue->next($deadline);
+ $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type());
+ $this->assertEquals(Grpc\OP_OK, $event->get_data());
+
+ // the client gets FINISHED
+ $event = $this->client_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+ $status = $event->get_data();
+ $this->assertEquals(Grpc\STATUS_OK, $status->code);
+ $this->assertEquals($status_text, $status->details);
+
+ // and the server gets FINISHED
+ $event = $this->server_queue->next($deadline);
+ $this->assertNotNull($event);
+ $this->assertEquals(Grpc\FINISHED, $event->get_type());
+
+ unset($call);
+ unset($server_call);
+ }
+}
\ No newline at end of file
diff --git a/src/php/tests/unit_tests/TimevalTest.php b/src/php/tests/unit_tests/TimevalTest.php
new file mode 100755
index 0000000..6af9fba
--- /dev/null
+++ b/src/php/tests/unit_tests/TimevalTest.php
@@ -0,0 +1,32 @@
+<?php
+class TimevalTest extends PHPUnit_Framework_TestCase{
+ public function testCompareSame() {
+ $zero = Grpc\Timeval::zero();
+ $this->assertEquals(0, Grpc\Timeval::compare($zero, $zero));
+ }
+
+ public function testPastIsLessThanZero() {
+ $zero = Grpc\Timeval::zero();
+ $past = Grpc\Timeval::inf_past();
+ $this->assertLessThan(0, Grpc\Timeval::compare($past, $zero));
+ $this->assertGreaterThan(0, Grpc\Timeval::compare($zero, $past));
+ }
+
+ public function testFutureIsGreaterThanZero() {
+ $zero = Grpc\Timeval::zero();
+ $future = Grpc\Timeval::inf_future();
+ $this->assertLessThan(0, Grpc\Timeval::compare($zero, $future));
+ $this->assertGreaterThan(0, Grpc\Timeval::compare($future, $zero));
+ }
+
+ /**
+ * @depends testFutureIsGreaterThanZero
+ */
+ public function testNowIsBetweenZeroAndFuture() {
+ $zero = Grpc\Timeval::zero();
+ $future = Grpc\Timeval::inf_future();
+ $now = Grpc\Timeval::now();
+ $this->assertLessThan(0, Grpc\Timeval::compare($zero, $now));
+ $this->assertLessThan(0, Grpc\Timeval::compare($now, $future));
+ }
+}
\ No newline at end of file