Merge branch 'master' of github.com:vjpai/grpc
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
index 7987471..d0e324e 100644
--- a/src/php/ext/grpc/call.c
+++ b/src/php/ext/grpc/call.c
@@ -49,11 +49,11 @@
 #include <stdbool.h>
 
 #include "grpc/support/log.h"
+#include "grpc/support/alloc.h"
 #include "grpc/grpc.h"
 
 #include "timeval.h"
 #include "channel.h"
-#include "completion_queue.h"
 #include "byte_buffer.h"
 
 zend_class_entry *grpc_ce_call;
@@ -61,7 +61,19 @@
 /* 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;
+  grpc_event *event;
   if (call->owned && call->wrapped != NULL) {
+    if (call->queue != NULL) {
+      grpc_completion_queue_shutdown(call->queue);
+      event = grpc_completion_queue_next(call->queue, gpr_inf_future);
+      while (event != NULL) {
+        if (event->type == GRPC_QUEUE_SHUTDOWN) {
+          break;
+        }
+        event = grpc_completion_queue_next(call->queue, gpr_inf_future);
+      }
+      grpc_completion_queue_destroy(call->queue);
+    }
     grpc_call_destroy(call->wrapped);
   }
   efree(call);
@@ -88,17 +100,23 @@
 
 /* Wraps a grpc_call struct in a PHP object. Owned indicates whether the struct
    should be destroyed at the end of the object's lifecycle */
-zval *grpc_php_wrap_call(grpc_call *wrapped, bool owned) {
+zval *grpc_php_wrap_call(grpc_call *wrapped, grpc_completion_queue *queue,
+                         bool owned) {
   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;
+  call->queue = queue;
   return call_object;
 }
 
-zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements) {
+/* Creates and returns a PHP array object with the data in a
+ * grpc_metadata_array. Returns NULL on failure */
+zval *grpc_parse_metadata_array(grpc_metadata_array *metadata_array) {
+  int count = metadata_array->count;
+  grpc_metadata *elements = metadata_array->metadata;
   int i;
   zval *array;
   zval **data = NULL;
@@ -139,6 +157,64 @@
   return array;
 }
 
+/* Populates a grpc_metadata_array with the data in a PHP array object.
+   Returns true on success and false on failure */
+bool create_metadata_array(zval *array, grpc_metadata_array *metadata) {
+  zval **inner_array;
+  zval **value;
+  HashTable *array_hash;
+  HashPosition array_pointer;
+  HashTable *inner_array_hash;
+  HashPosition inner_array_pointer;
+  char *key;
+  uint key_len;
+  ulong index;
+  if (Z_TYPE_P(array) != IS_ARRAY) {
+    return false;
+  }
+  grpc_metadata_array_init(metadata);
+  array_hash = Z_ARRVAL_P(array);
+  for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
+       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+                                     &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) {
+      return false;
+    }
+    if (Z_TYPE_P(*inner_array) != IS_ARRAY) {
+      return false;
+    }
+    inner_array_hash = Z_ARRVAL_P(*inner_array);
+    metadata->capacity += zend_hash_num_elements(inner_array_hash);
+  }
+  metadata->metadata = gpr_malloc(metadata->capacity * sizeof(grpc_metadata));
+  for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
+       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+                                     &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) {
+      return false;
+    }
+    inner_array_hash = Z_ARRVAL_P(*inner_array);
+    for (zend_hash_internal_pointer_reset_ex(inner_array_hash,
+                                             &inner_array_pointer);
+         zend_hash_get_current_data_ex(inner_array_hash, (void**)&value,
+                                       &inner_array_pointer) == SUCCESS;
+         zend_hash_move_forward_ex(inner_array_hash, &inner_array_pointer)) {
+      if (Z_TYPE_P(*value) != IS_STRING) {
+        return false;
+      }
+      metadata->metadata[metadata->count].key = key;
+      metadata->metadata[metadata->count].value = Z_STRVAL_P(*value);
+      metadata->metadata[metadata->count].value_length = Z_STRLEN_P(*value);
+      metadata->count += 1;
+    }
+  }
+  return true;
+}
+
 /**
  * Constructs a new instance of the Call class.
  * @param Channel $channel The channel to associate the call with. Must not be
@@ -157,9 +233,10 @@
   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);
+    zend_throw_exception(
+        spl_ce_InvalidArgumentException,
+        "Call expects a Channel, a String, and a Timeval",
+        1 TSRMLS_CC);
     return;
   }
   wrapped_grpc_channel *channel =
@@ -175,289 +252,235 @@
   wrapped_grpc_timeval *deadline =
       (wrapped_grpc_timeval *)zend_object_store_get_object(
           deadline_obj TSRMLS_CC);
-  call->wrapped = grpc_channel_create_call_old(
-      channel->wrapped, method, channel->target, deadline->wrapped);
+  call->queue = grpc_completion_queue_create();
+  call->wrapped = grpc_channel_create_call(
+      channel->wrapped, call->queue, 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
+ * Start a batch of RPC actions.
+ * @param array batch Array of actions to take
+ * @return object Object with results of all actions
  */
-PHP_METHOD(Call, add_metadata) {
+PHP_METHOD(Call, start_batch) {
   wrapped_grpc_call *call =
       (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  grpc_metadata metadata;
-  grpc_call_error error_code;
+  grpc_op ops[8];
+  size_t op_num = 0;
   zval *array;
-  zval **inner_array;
   zval **value;
+  zval **inner_value;
   HashTable *array_hash;
   HashPosition array_pointer;
-  HashTable *inner_array_hash;
-  HashPosition inner_array_pointer;
+  HashTable *status_hash;
   char *key;
   uint key_len;
   ulong index;
-  long flags = 0;
-  /* "a|l" == 1 array, 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &array, &flags) ==
+  grpc_metadata_array metadata;
+  grpc_metadata_array trailing_metadata;
+  grpc_metadata_array recv_metadata;
+  grpc_metadata_array recv_trailing_metadata;
+  grpc_status_code status;
+  char *status_details = NULL;
+  size_t status_details_capacity = 0;
+  grpc_byte_buffer *message;
+  int cancelled;
+  grpc_call_error error;
+  grpc_event *event;
+  zval *result;
+  char *message_str;
+  size_t message_len;
+  zval *recv_status;
+  grpc_metadata_array_init(&metadata);
+  grpc_metadata_array_init(&trailing_metadata);
+  grpc_metadata_array_init(&recv_metadata);
+  grpc_metadata_array_init(&recv_trailing_metadata);
+  MAKE_STD_ZVAL(result);
+  object_init(result);
+  /* "a" == 1 array */
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &array) ==
       FAILURE) {
     zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "add_metadata expects an array and an optional long",
-                         1 TSRMLS_CC);
-    return;
+                         "start_batch expects an array", 1 TSRMLS_CC);
+    goto cleanup;
   }
   array_hash = Z_ARRVAL_P(array);
   for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
-       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+       zend_hash_get_current_data_ex(array_hash, (void**)&value,
                                      &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) {
+                                     &array_pointer) != HASH_KEY_IS_LONG) {
       zend_throw_exception(spl_ce_InvalidArgumentException,
-                           "metadata keys must be strings", 1 TSRMLS_CC);
-      return;
+                           "batch keys must be integers", 1 TSRMLS_CC);
+      goto cleanup;
     }
-    if (Z_TYPE_P(*inner_array) != IS_ARRAY) {
-      zend_throw_exception(spl_ce_InvalidArgumentException,
-                           "metadata values must be arrays",
-                           1 TSRMLS_CC);
-      return;
-    }
-    inner_array_hash = Z_ARRVAL_P(*inner_array);
-    for (zend_hash_internal_pointer_reset_ex(inner_array_hash,
-                                             &inner_array_pointer);
-         zend_hash_get_current_data_ex(inner_array_hash, (void**)&value,
-                                       &inner_array_pointer) == SUCCESS;
-         zend_hash_move_forward_ex(inner_array_hash, &inner_array_pointer)) {
-      if (Z_TYPE_P(*value) != IS_STRING) {
+    switch(index) {
+      case GRPC_OP_SEND_INITIAL_METADATA:
+        if (!create_metadata_array(*value, &metadata)) {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "Bad metadata value given", 1 TSRMLS_CC);
+          goto cleanup;
+        }
+        ops[op_num].data.send_initial_metadata.count =
+            metadata.count;
+        ops[op_num].data.send_initial_metadata.metadata =
+            metadata.metadata;
+        break;
+      case GRPC_OP_SEND_MESSAGE:
+        if (Z_TYPE_PP(value) != IS_STRING) {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "Expected a string for send message",
+                               1 TSRMLS_CC);
+        }
+        ops[op_num].data.send_message =
+            string_to_byte_buffer(Z_STRVAL_PP(value), Z_STRLEN_PP(value));
+        break;
+      case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+        break;
+      case GRPC_OP_SEND_STATUS_FROM_SERVER:
+        status_hash = Z_ARRVAL_PP(value);
+        if (zend_hash_find(status_hash, "metadata", sizeof("metadata"),
+                           (void **)&inner_value) == SUCCESS) {
+          if (!create_metadata_array(*inner_value, &trailing_metadata)) {
+            zend_throw_exception(spl_ce_InvalidArgumentException,
+                                 "Bad trailing metadata value given",
+                                 1 TSRMLS_CC);
+            goto cleanup;
+          }
+          ops[op_num].data.send_status_from_server.trailing_metadata =
+              trailing_metadata.metadata;
+          ops[op_num].data.send_status_from_server.trailing_metadata_count =
+              trailing_metadata.count;
+        }
+        if (zend_hash_find(status_hash, "code", sizeof("code"),
+                           (void**)&inner_value) == SUCCESS) {
+          if (Z_TYPE_PP(inner_value) != IS_LONG) {
+            zend_throw_exception(spl_ce_InvalidArgumentException,
+                                 "Status code must be an integer",
+                                 1 TSRMLS_CC);
+            goto cleanup;
+          }
+          ops[op_num].data.send_status_from_server.status =
+              Z_LVAL_PP(inner_value);
+        } else {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "Integer status code is required",
+                               1 TSRMLS_CC);
+          goto cleanup;
+        }
+        if (zend_hash_find(status_hash, "details", sizeof("details"),
+                           (void**)&inner_value) == SUCCESS) {
+          if (Z_TYPE_PP(inner_value) != IS_STRING) {
+            zend_throw_exception(spl_ce_InvalidArgumentException,
+                                 "Status details must be a string",
+                                 1 TSRMLS_CC);
+            goto cleanup;
+          }
+          ops[op_num].data.send_status_from_server.status_details =
+              Z_STRVAL_PP(inner_value);
+        } else {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "String status details is required",
+                               1 TSRMLS_CC);
+          goto cleanup;
+        }
+        break;
+      case GRPC_OP_RECV_INITIAL_METADATA:
+        ops[op_num].data.recv_initial_metadata = &recv_metadata;
+        break;
+      case GRPC_OP_RECV_MESSAGE:
+        ops[op_num].data.recv_message = &message;
+        break;
+      case GRPC_OP_RECV_STATUS_ON_CLIENT:
+        ops[op_num].data.recv_status_on_client.trailing_metadata =
+            &recv_trailing_metadata;
+        ops[op_num].data.recv_status_on_client.status = &status;
+        ops[op_num].data.recv_status_on_client.status_details =
+            &status_details;
+        ops[op_num].data.recv_status_on_client.status_details_capacity =
+            &status_details_capacity;
+        break;
+      case GRPC_OP_RECV_CLOSE_ON_SERVER:
+        ops[op_num].data.recv_close_on_server.cancelled = &cancelled;
+        break;
+      default:
         zend_throw_exception(spl_ce_InvalidArgumentException,
-                             "metadata values must be arrays of strings",
-                             1 TSRMLS_CC);
-        return;
-      }
-      metadata.key = key;
-      metadata.value = Z_STRVAL_P(*value);
-      metadata.value_length = Z_STRLEN_P(*value);
-      error_code = grpc_call_add_metadata_old(call->wrapped, &metadata, 0u);
-      MAYBE_THROW_CALL_ERROR(add_metadata, error_code);
+                             "Unrecognized key in batch", 1 TSRMLS_CC);
+        goto cleanup;
+    }
+    ops[op_num].op = (grpc_op_type)index;
+    op_num++;
+  }
+  error = grpc_call_start_batch(call->wrapped, ops, op_num, call->wrapped);
+  if (error != GRPC_CALL_OK) {
+    zend_throw_exception(spl_ce_LogicException,
+                         "start_batch was called incorrectly",
+                         (long)error TSRMLS_CC);
+    goto cleanup;
+  }
+  event = grpc_completion_queue_pluck(call->queue, call->wrapped,
+                                      gpr_inf_future);
+  if (event->data.op_complete != GRPC_OP_OK) {
+    zend_throw_exception(spl_ce_LogicException,
+                         "The batch failed for some reason",
+                         1 TSRMLS_CC);
+    goto cleanup;
+  }
+  for (int i = 0; i < op_num; i++) {
+    switch(ops[i].op) {
+      case GRPC_OP_SEND_INITIAL_METADATA:
+        add_property_bool(result, "send_metadata", true);
+        break;
+      case GRPC_OP_SEND_MESSAGE:
+        add_property_bool(result, "send_message", true);
+        break;
+      case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+        add_property_bool(result, "send_close", true);
+        break;
+      case GRPC_OP_SEND_STATUS_FROM_SERVER:
+        add_property_bool(result, "send_status", true);
+        break;
+      case GRPC_OP_RECV_INITIAL_METADATA:
+        add_property_zval(result, "metadata",
+                          grpc_parse_metadata_array(&recv_metadata));
+        break;
+      case GRPC_OP_RECV_MESSAGE:
+        byte_buffer_to_string(message, &message_str, &message_len);
+        add_property_stringl(result, "message", message_str, message_len,
+                             false);
+        break;
+      case GRPC_OP_RECV_STATUS_ON_CLIENT:
+        MAKE_STD_ZVAL(recv_status);
+        object_init(recv_status);
+        add_property_zval(recv_status, "metadata",
+                          grpc_parse_metadata_array(&recv_trailing_metadata));
+        add_property_long(recv_status, "code", status);
+        add_property_string(recv_status, "details", status_details, true);
+        add_property_zval(result, "status", recv_status);
+        break;
+      case GRPC_OP_RECV_CLOSE_ON_SERVER:
+        add_property_bool(result, "cancelled", cancelled);
+        break;
+      default:
+        break;
     }
   }
-}
-
-/**
- * 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 $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 Void
- */
-PHP_METHOD(Call, invoke) {
-  grpc_call_error error_code;
-  long tag1;
-  long tag2;
-  zval *queue_obj;
-  long flags = 0;
-  /* "Oll|l" == 1 Object, 3 mandatory longs, 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oll|l", &queue_obj,
-                            grpc_ce_completion_queue, &tag1, &tag2,
-                            &flags) == FAILURE) {
-    zend_throw_exception(
-        spl_ce_InvalidArgumentException,
-        "invoke needs a CompletionQueue, 2 longs, and an optional long",
-        1 TSRMLS_CC);
-    return;
+cleanup:
+  grpc_metadata_array_destroy(&metadata);
+  grpc_metadata_array_destroy(&trailing_metadata);
+  grpc_metadata_array_destroy(&recv_metadata);
+  grpc_metadata_array_destroy(&recv_trailing_metadata);
+  if (status_details != NULL) {
+    gpr_free(status_details);
   }
-  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);
-  error_code = grpc_call_invoke_old(call->wrapped, queue->wrapped, (void *)tag1,
-                                    (void *)tag2, (gpr_uint32)flags);
-  MAYBE_THROW_CALL_ERROR(invoke, error_code);
-}
-
-/**
- * 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 Void
- */
-PHP_METHOD(Call, server_accept) {
-  long tag;
-  zval *queue_obj;
-  grpc_call_error error_code;
-  /* "Ol|l" == 1 Object, 1 long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ol", &queue_obj,
-                            grpc_ce_completion_queue, &tag) == FAILURE) {
-    zend_throw_exception(
-        spl_ce_InvalidArgumentException,
-        "server_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);
-  error_code =
-      grpc_call_server_accept_old(call->wrapped, queue->wrapped, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(server_accept, error_code);
-}
-
-PHP_METHOD(Call, server_end_initial_metadata) {
-  grpc_call_error error_code;
-  long flags = 0;
-  /* "|l" == 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags) ==
-      FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "server_end_initial_metadata expects an optional long",
-                         1 TSRMLS_CC);
-  }
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  error_code = grpc_call_server_end_initial_metadata_old(call->wrapped, flags);
-  MAYBE_THROW_CALL_ERROR(server_end_initial_metadata, error_code);
-}
-
-/**
- * Called by clients to cancel an RPC on the server.
- * @return Void
- */
-PHP_METHOD(Call, cancel) {
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  grpc_call_error error_code = grpc_call_cancel(call->wrapped);
-  MAYBE_THROW_CALL_ERROR(cancel, error_code);
-}
-
-/**
- * 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 Void
- */
-PHP_METHOD(Call, start_write) {
-  grpc_call_error error_code;
-  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;
-  }
-  error_code = grpc_call_start_write_old(
-      call->wrapped, string_to_byte_buffer(buffer, buffer_len), (void *)tag,
-      (gpr_uint32)flags);
-  MAYBE_THROW_CALL_ERROR(start_write, error_code);
-}
-
-/**
- * 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 Void
- */
-PHP_METHOD(Call, start_write_status) {
-  grpc_call_error error_code;
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  long status_code;
-  int status_details_length;
-  long tag;
-  char *status_details;
-  /* "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;
-  }
-  error_code = grpc_call_start_write_status_old(call->wrapped,
-                                                (grpc_status_code)status_code,
-                                                status_details, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(start_write_status, error_code);
-}
-
-/**
- * Indicate that there are no more messages to send
- * @return Void
- */
-PHP_METHOD(Call, writes_done) {
-  grpc_call_error error_code;
-  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;
-  }
-  error_code = grpc_call_writes_done_old(call->wrapped, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(writes_done, error_code);
-}
-
-/**
- * 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 Void
- */
-PHP_METHOD(Call, start_read) {
-  grpc_call_error error_code;
-  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;
-  }
-  error_code = grpc_call_start_read_old(call->wrapped, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(start_read, error_code);
+  RETURN_DESTROY_ZVAL(result);
 }
 
 static zend_function_entry call_methods[] = {
     PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
-    PHP_ME(Call, server_accept, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, server_end_initial_metadata, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, add_metadata, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, 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};
+    PHP_ME(Call, start_batch, NULL, ZEND_ACC_PUBLIC) PHP_FE_END};
 
 void grpc_init_call(TSRMLS_D) {
   zend_class_entry ce;
diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h
index bce5d82..743effe 100644
--- a/src/php/ext/grpc/call.h
+++ b/src/php/ext/grpc/call.h
@@ -45,17 +45,6 @@
 
 #include "grpc/grpc.h"
 
-// Throw an exception if error_code is not OK
-#define MAYBE_THROW_CALL_ERROR(func_name, error_code)            \
-  do {                                                           \
-    if (error_code != GRPC_CALL_OK) {                            \
-      zend_throw_exception(spl_ce_LogicException,                \
-                           #func_name " was called incorrectly", \
-                           (long)error_code TSRMLS_CC);          \
-      return;                                                    \
-    }                                                            \
-  } while (0)
-
 /* Class entry for the Call PHP class */
 extern zend_class_entry *grpc_ce_call;
 
@@ -65,16 +54,18 @@
 
   bool owned;
   grpc_call *wrapped;
+  grpc_completion_queue *queue;
 } 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, bool owned);
+zval *grpc_php_wrap_call(grpc_call *wrapped, grpc_completion_queue *queue,
+                         bool owned);
 
 /* 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);
+zval *grpc_parse_metadata_array(grpc_metadata_array *metadata_array);
 
 #endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */
diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c
index 5e99332..c96fb12 100644
--- a/src/php/ext/grpc/channel.c
+++ b/src/php/ext/grpc/channel.c
@@ -51,7 +51,6 @@
 #include "grpc/support/log.h"
 #include "grpc/grpc_security.h"
 
-#include "completion_queue.h"
 #include "server.h"
 #include "credentials.h"
 
@@ -139,6 +138,9 @@
   HashTable *array_hash;
   zval **creds_obj = NULL;
   wrapped_grpc_credentials *creds = NULL;
+  zval **override_obj;
+  char *override;
+  int override_len;
   /* "s|a" == 1 string, 1 optional array */
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &target,
                             &target_length, &args_array) == FAILURE) {
@@ -146,6 +148,8 @@
                          "Channel expects a string and an array", 1 TSRMLS_CC);
     return;
   }
+  override = target;
+  override_len = target_length;
   if (args_array == NULL) {
     channel->wrapped = grpc_channel_create(target, NULL);
   } else {
@@ -162,6 +166,19 @@
           *creds_obj TSRMLS_CC);
       zend_hash_del(array_hash, "credentials", 12);
     }
+    if (zend_hash_find(array_hash, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
+                       sizeof(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
+                       (void **)&override_obj) == SUCCESS) {
+      if (Z_TYPE_PP(override_obj) != IS_STRING) {
+        zend_throw_exception(spl_ce_InvalidArgumentException,
+                             GRPC_SSL_TARGET_NAME_OVERRIDE_ARG
+                             " must be a string",
+                             1 TSRMLS_CC);
+        return;
+      }
+      override = Z_STRVAL_PP(override_obj);
+      override_len = Z_STRLEN_PP(override_obj);
+    }
     php_grpc_read_args_array(args_array, &args);
     if (creds == NULL) {
       channel->wrapped = grpc_channel_create(target, &args);
@@ -172,8 +189,8 @@
     }
     efree(args.args);
   }
-  channel->target = ecalloc(target_length + 1, sizeof(char));
-  memcpy(channel->target, target, target_length);
+  channel->target = ecalloc(override_len + 1, sizeof(char));
+  memcpy(channel->target, override, override_len);
 }
 
 /**
diff --git a/src/php/ext/grpc/completion_queue.c b/src/php/ext/grpc/completion_queue.c
deleted file mode 100644
index 93abf5d..0000000
--- a/src/php/ext/grpc/completion_queue.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#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"
-
-zend_class_entry *grpc_ce_completion_queue;
-
-/* 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_convert_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_convert_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
deleted file mode 100755
index 1d386cc..0000000
--- a/src/php/ext/grpc/completion_queue.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#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 */
-extern 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
index 27c6778..d1a8dec 100755
--- a/src/php/ext/grpc/config.m4
+++ b/src/php/ext/grpc/config.m4
@@ -66,5 +66,5 @@
 
   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)
+  PHP_NEW_EXTENSION(grpc, byte_buffer.c call.c channel.c credentials.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/event.c b/src/php/ext/grpc/event.c
deleted file mode 100644
index 452c4b8..0000000
--- a/src/php/ext/grpc/event.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#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"
-
-/* Create a new PHP object containing the event data in the event struct.
-   event must not be used after this function is called */
-zval *grpc_php_convert_event(grpc_event *event) {
-  zval *data_object;
-  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;
-
-  zval *event_object;
-
-  if (event == NULL) {
-    return NULL;
-  }
-
-  MAKE_STD_ZVAL(event_object);
-  object_init(event_object);
-
-  add_property_zval(
-      event_object, "call",
-      grpc_php_wrap_call(event->call, event->type == GRPC_SERVER_RPC_NEW));
-  add_property_long(event_object, "type", event->type);
-  add_property_long(event_object, "tag", (long)event->tag);
-
-  switch (event->type) {
-    case GRPC_QUEUE_SHUTDOWN:
-      add_property_null(event_object, "data");
-      break;
-    case GRPC_READ:
-      if (event->data.read == NULL) {
-        add_property_null(event_object, "data");
-      } else {
-        byte_buffer_to_string(event->data.read, &read_string, &read_len);
-        add_property_stringl(event_object, "data", read_string, read_len, true);
-      }
-      break;
-    case GRPC_WRITE_ACCEPTED:
-      add_property_long(event_object, "data", (long)event->data.write_accepted);
-      break;
-    case GRPC_FINISH_ACCEPTED:
-      add_property_long(event_object, "data",
-                        (long)event->data.finish_accepted);
-      break;
-    case GRPC_CLIENT_METADATA_READ:
-      data_object = grpc_call_create_metadata_array(
-          event->data.client_metadata_read.count,
-          event->data.client_metadata_read.elements);
-      add_property_zval(event_object, "data", data_object);
-      break;
-    case GRPC_FINISHED:
-      MAKE_STD_ZVAL(data_object);
-      object_init(data_object);
-      add_property_long(data_object, "code", event->data.finished.status);
-      if (event->data.finished.details == NULL) {
-        add_property_null(data_object, "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(data_object, "details", detail_string, true);
-      }
-      add_property_zval(data_object, "metadata",
-                        grpc_call_create_metadata_array(
-                            event->data.finished.metadata_count,
-                            event->data.finished.metadata_elements));
-      add_property_zval(event_object, "data", data_object);
-      break;
-    case GRPC_SERVER_RPC_NEW:
-      MAKE_STD_ZVAL(data_object);
-      object_init(data_object);
-      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(data_object, "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(data_object, "host", host_string, false);
-      add_property_zval(
-          data_object, "absolute_timeout",
-          grpc_php_wrap_timeval(event->data.server_rpc_new.deadline));
-      add_property_zval(data_object, "metadata",
-                        grpc_call_create_metadata_array(
-                            event->data.server_rpc_new.metadata_count,
-                            event->data.server_rpc_new.metadata_elements));
-      add_property_zval(event_object, "data", data_object);
-      break;
-    default:
-      add_property_null(event_object, "data");
-      break;
-  }
-  grpc_event_finish(event);
-  return event_object;
-}
diff --git a/src/php/ext/grpc/event.h b/src/php/ext/grpc/event.h
deleted file mode 100755
index ef5846a..0000000
--- a/src/php/ext/grpc/event.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#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"
-
-/* Create a new Event object that wraps an existing grpc_event struct */
-zval *grpc_php_convert_event(grpc_event *event);
-
-#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
index 67e366c..1f9edfe 100644
--- a/src/php/ext/grpc/php_grpc.c
+++ b/src/php/ext/grpc/php_grpc.c
@@ -34,8 +34,6 @@
 #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"
@@ -127,27 +125,12 @@
   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\\FINISH_ACCEPTED", GRPC_FINISH_ACCEPTED,
-                         CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\WRITE_ACCEPTED", GRPC_WRITE_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,
@@ -181,10 +164,27 @@
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_DATA_LOSS", GRPC_STATUS_DATA_LOSS,
                          CONST_CS);
 
+  /* Register op type constants */
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_INITIAL_METADATA",
+                         GRPC_OP_SEND_INITIAL_METADATA, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_MESSAGE",
+                         GRPC_OP_SEND_MESSAGE, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_CLOSE_FROM_CLIENT",
+                         GRPC_OP_SEND_CLOSE_FROM_CLIENT, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_STATUS_FROM_SERVER",
+                         GRPC_OP_SEND_STATUS_FROM_SERVER, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_INITIAL_METADATA",
+                         GRPC_OP_RECV_INITIAL_METADATA, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_MESSAGE",
+                         GRPC_OP_RECV_MESSAGE, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_STATUS_ON_CLIENT",
+                         GRPC_OP_RECV_STATUS_ON_CLIENT, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_CLOSE_ON_SERVER",
+                         GRPC_OP_RECV_CLOSE_ON_SERVER, 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_timeval(TSRMLS_C);
   grpc_init_credentials(TSRMLS_C);
   grpc_init_server_credentials(TSRMLS_C);
diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c
index a5cfd95..86b2995 100644
--- a/src/php/ext/grpc/server.c
+++ b/src/php/ext/grpc/server.c
@@ -52,15 +52,27 @@
 #include "grpc/grpc_security.h"
 
 #include "server.h"
-#include "completion_queue.h"
 #include "channel.h"
 #include "server_credentials.h"
+#include "timeval.h"
 
 zend_class_entry *grpc_ce_server;
 
 /* 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;
+  grpc_event *event;
+  if (server->queue != NULL) {
+    grpc_completion_queue_shutdown(server->queue);
+    event = grpc_completion_queue_next(server->queue, gpr_inf_future);
+    while (event != NULL) {
+      if (event->type == GRPC_QUEUE_SHUTDOWN) {
+        break;
+      }
+      event = grpc_completion_queue_next(server->queue, gpr_inf_future);
+    }
+    grpc_completion_queue_destroy(server->queue);
+  }
   if (server->wrapped != NULL) {
     grpc_server_shutdown(server->wrapped);
     grpc_server_destroy(server->wrapped);
@@ -95,26 +107,22 @@
 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;
-  /* "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) {
+  /* "|a" == 1 optional array */
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &args_array) ==
+      FAILURE) {
     zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "Server expects a CompletionQueue and an array",
+                         "Server expects 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);
+  server->queue = grpc_completion_queue_create();
   if (args_array == NULL) {
-    server->wrapped = grpc_server_create(queue->wrapped, NULL);
+    server->wrapped = grpc_server_create(server->queue, NULL);
   } else {
     php_grpc_read_args_array(args_array, &args);
-    server->wrapped = grpc_server_create(queue->wrapped, &args);
+    server->wrapped = grpc_server_create(server->queue, &args);
     efree(args.args);
   }
 }
@@ -129,16 +137,40 @@
   grpc_call_error error_code;
   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_call *call;
+  grpc_call_details details;
+  grpc_metadata_array metadata;
+  zval *result;
+  grpc_event *event;
+  MAKE_STD_ZVAL(result);
+  object_init(result);
+  grpc_call_details_init(&details);
+  grpc_metadata_array_init(&metadata);
+  error_code = grpc_server_request_call(server->wrapped, &call, &details,
+                                        &metadata, server->queue, NULL);
+  if (error_code != GRPC_CALL_OK) {
+    zend_throw_exception(spl_ce_LogicException, "request_call failed",
+                         (long)error_code TSRMLS_CC);
+    goto cleanup;
   }
-  error_code = grpc_server_request_call_old(server->wrapped, (void *)tag_new);
-  MAYBE_THROW_CALL_ERROR(request_call, error_code);
+  event = grpc_completion_queue_pluck(server->queue, NULL, gpr_inf_future);
+  if (event->data.op_complete != GRPC_OP_OK) {
+    zend_throw_exception(spl_ce_LogicException,
+                         "Failed to request a call for some reason",
+                         1 TSRMLS_CC);
+    goto cleanup;
+  }
+  add_property_zval(result, "call", grpc_php_wrap_call(call, server->queue,
+                                                       true));
+  add_property_string(result, "method", details.method, true);
+  add_property_string(result, "host", details.host, true);
+  add_property_zval(result, "absolute_deadline",
+                    grpc_php_wrap_timeval(details.deadline));
+  add_property_zval(result, "metadata", grpc_parse_metadata_array(&metadata));
+cleanup:
+  grpc_call_details_destroy(&details);
+  grpc_metadata_array_destroy(&metadata);
+  RETURN_DESTROY_ZVAL(result);
 }
 
 /**
@@ -168,7 +200,7 @@
   int addr_len;
   zval *creds_obj;
   /* "sO" == 1 string, 1 object */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &addr, &addr_len,
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO", &addr, &addr_len,
                             &creds_obj, grpc_ce_server_credentials) ==
       FAILURE) {
     zend_throw_exception(
diff --git a/src/php/ext/grpc/server.h b/src/php/ext/grpc/server.h
index b55689c..ebb8d25 100755
--- a/src/php/ext/grpc/server.h
+++ b/src/php/ext/grpc/server.h
@@ -53,6 +53,7 @@
   zend_object std;
 
   grpc_server *wrapped;
+  grpc_completion_queue *queue;
 } wrapped_grpc_server;
 
 /* Initializes the Server class */
diff --git a/src/php/lib/Grpc/ActiveCall.php b/src/php/lib/Grpc/ActiveCall.php
index f0d0d55..af4dca5 100755
--- a/src/php/lib/Grpc/ActiveCall.php
+++ b/src/php/lib/Grpc/ActiveCall.php
@@ -38,9 +38,7 @@
  * Represents an active call that allows sending and recieving binary data
  */
 class ActiveCall {
-  private $completion_queue;
   private $call;
-  private $flags;
   private $metadata;
 
   /**
@@ -48,24 +46,15 @@
    * @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();
+                              $metadata = array()) {
     $this->call = new Call($channel, $method, Timeval::inf_future());
-    $this->call->add_metadata($metadata, 0);
-    $this->flags = $flags;
 
-    // Invoke the call.
-    $this->call->invoke($this->completion_queue,
-                        CLIENT_METADATA_READ,
-                        FINISHED, 0);
-    $metadata_event = $this->completion_queue->pluck(CLIENT_METADATA_READ,
-                                                     Timeval::inf_future());
-    $this->metadata = $metadata_event->data;
+    $event = $this->call->start_batch([OP_SEND_INITIAL_METADATA => $metadata]);
+
+    $this->metadata = $event->metadata;
   }
 
   /**
@@ -87,8 +76,7 @@
    * @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());
+    $read_event = $this->call->start_batch([OP_RECV_MESSAGE => true]);
     return $read_event->data;
   }
 
@@ -98,16 +86,14 @@
    * @param ByteBuffer $data The data to write
    */
   public function write($data) {
-    $this->call->start_write($data, WRITE_ACCEPTED, $this->flags);
-    $this->completion_queue->pluck(WRITE_ACCEPTED, Timeval::inf_future());
+    $this->call->start_batch([OP_SEND_MESSAGE => $data]);
   }
 
   /**
    * 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());
+    $this->call->start_batch([OP_SEND_CLOSE_FROM_CLIENT => true]);
   }
 
   /**
@@ -116,8 +102,7 @@
    *     and array $metadata members
    */
   public function getStatus() {
-    $status_event = $this->completion_queue->pluck(FINISHED,
-                                                   Timeval::inf_future());
+    $status_event = $this->call->start_batch([RECV_STATUS_ON_CLIENT => true]);
     return $status_event->data;
   }
 }
diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php
index 8bb0927..d361ce0 100755
--- a/src/php/tests/unit_tests/CallTest.php
+++ b/src/php/tests/unit_tests/CallTest.php
@@ -36,65 +36,47 @@
   static $port;
 
   public static function setUpBeforeClass() {
-    $cq = new Grpc\CompletionQueue();
-    self::$server = new Grpc\Server($cq, []);
+    self::$server = new Grpc\Server([]);
     self::$port = self::$server->add_http2_port('0.0.0.0:0');
   }
 
   public function setUp() {
-    $this->cq = new Grpc\CompletionQueue();
     $this->channel = new Grpc\Channel('localhost:' . self::$port, []);
     $this->call = new Grpc\Call($this->channel,
                                 '/foo',
                                 Grpc\Timeval::inf_future());
   }
 
-  /**
-   * @expectedException LogicException
-   * @expectedExceptionCode Grpc\CALL_ERROR_INVALID_FLAGS
-   * @expectedExceptionMessage invoke
-   */
-  public function testInvokeRejectsBadFlags() {
-    $this->call->invoke($this->cq, 0, 0, 0xDEADBEEF);
-  }
-
-  /**
-   * @expectedException LogicException
-   * @expectedExceptionCode Grpc\CALL_ERROR_NOT_ON_CLIENT
-   * @expectedExceptionMessage server_accept
-   */
-  public function testServerAcceptFailsCorrectly() {
-    $this->call->server_accept($this->cq, 0);
-  }
-
-  /* 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);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => []
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 
   public function testAddSingleMetadata() {
-    $this->call->add_metadata(['key' => ['value']], 0);
-    /* Dummy assert: Checks that the previous call completed without error */
-    $this->assertTrue(true);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => ['key' => ['value']]
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 
   public function testAddMultiValueMetadata() {
-    $this->call->add_metadata(['key' => ['value1', 'value2']], 0);
-    /* Dummy assert: Checks that the previous call completed without error */
-    $this->assertTrue(true);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => ['key' => ['value1', 'value2']]
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 
   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);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => ['key1' => ['value1'],
+                                          'key2' => ['value2', 'value3']]
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 }
diff --git a/src/php/tests/unit_tests/CompletionQueueTest.php b/src/php/tests/unit_tests/CompletionQueueTest.php
deleted file mode 100755
index 76ee61d..0000000
--- a/src/php/tests/unit_tests/CompletionQueueTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-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);
-  }
-}
diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php
index 0cbc506..3e165b7 100755
--- a/src/php/tests/unit_tests/EndToEndTest.php
+++ b/src/php/tests/unit_tests/EndToEndTest.php
@@ -33,18 +33,15 @@
  */
 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 = new Grpc\Server([]);
     $port = $this->server->add_http2_port('0.0.0.0:0');
     $this->channel = new Grpc\Channel('localhost:' . $port, []);
+    $this->server->start();
   }
 
   public function tearDown() {
     unset($this->channel);
     unset($this->server);
-    unset($this->client_queue);
-    unset($this->server_queue);
   }
 
   public function testSimpleRequestBody() {
@@ -53,55 +50,45 @@
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
-    $server_tag = 2;
 
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true
+                                       ]);
 
-    // check that a server rpc new was received
-    $this->server->start();
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
+    $this->assertSame([], $event->metadata);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $server_call->server_end_initial_metadata();
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true
+                                        ]);
 
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertFalse($event->cancelled);
 
-    // the server sends the status
-    $server_call->start_write_status(Grpc\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true
+                                 ]);
 
-    // the client gets CLIENT_METADATA_READ
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
-
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+    $this->assertSame([], $event->metadata);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
-
     unset($call);
     unset($server_call);
   }
@@ -115,79 +102,52 @@
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
 
-    $server_tag = 2;
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
+        Grpc\OP_SEND_MESSAGE => $req_text
+                                       ]);
 
-    // the client writes
-    $call->start_write($req_text, $tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+    $this->assertTrue($event->send_message);
 
-    // check that a server rpc new was received
-    $this->server->start();
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $server_call->server_end_initial_metadata();
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_MESSAGE => $reply_text,
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+                                        ]);
 
-    // start the server read
-    $server_call->start_read($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($req_text, $event->data);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertTrue($event->send_message);
+    $this->assertFalse($event->cancelled);
+    $this->assertSame($req_text, $event->message);
 
-    // the server replies
-    $server_call->start_write($reply_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true,
+                                       ]);
 
-    // the client reads the metadata
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
-
-    // the client reads the reply
-    $call->start_read($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($reply_text, $event->data);
-
-    // the client sends writes done
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the server sends the status
-    $server_call->start_write_status(GRPC\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+    $this->assertSame([], $event->metadata);
+    $this->assertSame($reply_text, $event->message);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-
     unset($call);
     unset($server_call);
   }
diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php
index 896afea..2d62fe9 100755
--- a/src/php/tests/unit_tests/SecureEndToEndTest.php
+++ b/src/php/tests/unit_tests/SecureEndToEndTest.php
@@ -33,17 +33,16 @@
  */
 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);
+    $this->server = new Grpc\Server();
     $port = $this->server->add_secure_http2_port('0.0.0.0:0',
                                                  $server_credentials);
+    $this->server->start();
     $this->channel = new Grpc\Channel(
         'localhost:' . $port,
         [
@@ -55,70 +54,58 @@
   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;
-    $call->invoke($this->client_queue, $tag, $tag);
-    $server_tag = 2;
 
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true
+                                       ]);
 
-    // check that a server rpc new was received
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
+    $this->assertSame([], $event->metadata);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $server_call->server_end_initial_metadata();
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true
+                                        ]);
 
-    // the server sends the status
-    $server_call->start_write_status(Grpc\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertFalse($event->cancelled);
 
-    // the client gets CLIENT_METADATA_READ
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true
+                                 ]);
 
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+    $this->assertSame([], $event->metadata);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->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';
@@ -127,78 +114,52 @@
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
 
-    $server_tag = 2;
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
+        Grpc\OP_SEND_MESSAGE => $req_text
+                                       ]);
 
-    // the client writes
-    $call->start_write($req_text, $tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+    $this->assertTrue($event->send_message);
 
-    // check that a server rpc new was received
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
 
-    $server_call->server_end_initial_metadata();
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_MESSAGE => $reply_text,
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+                                        ]);
 
-    // start the server read
-    $server_call->start_read($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($req_text, $event->data);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertTrue($event->send_message);
+    $this->assertFalse($event->cancelled);
+    $this->assertSame($req_text, $event->message);
 
-    // the server replies
-    $server_call->start_write($reply_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true,
+                                       ]);
 
-    // the client reads the metadata
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
-
-    // the client reads the reply
-    $call->start_read($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($reply_text, $event->data);
-
-    // the client sends writes done
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the server sends the status
-    $server_call->start_write_status(GRPC\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+    $this->assertSame([], $event->metadata);
+    $this->assertSame($reply_text, $event->message);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-
     unset($call);
     unset($server_call);
   }