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",
+                           &microseconds) == 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