diff --git a/src/php/bin/run_gen_code_test.sh b/src/php/bin/run_gen_code_test.sh
index 3f176fb5e4aad2d81195a07f680bf55d1e4a5d73..79abbe6cf8c25aac9b8db3b5f77845f7f475361b 100755
--- a/src/php/bin/run_gen_code_test.sh
+++ b/src/php/bin/run_gen_code_test.sh
@@ -32,3 +32,6 @@ 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
+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/GeneratedCodeWithCallbackTest.php
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index fc83dace20552b7838487a1b3688e2d210068136..cb2537fe98d4308d6fb9554b4ce649208eaf238f 100755
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -41,7 +41,24 @@ class BaseStub {
 
   private $channel;
 
+  // a callback function
+  private $update_metadata;
+
+  /**
+   * @param $hostname string
+   * @param $opts array
+   *  - 'update_metadata': (optional) a callback function which takes in a
+   * metadata array, and returns an updated metadata array
+   */
   public function __construct($hostname, $opts) {
+    $this->update_metadata = null;
+    if (isset($opts['update_metadata'])) {
+      if (is_callable($opts['update_metadata'])) {
+        $this->update_metadata = $opts['update_metadata'];
+      }
+      unset($opts['update_metadata']);
+    }
+         
     $this->channel = new Channel($hostname, $opts);
   }
 
@@ -69,7 +86,12 @@ class BaseStub {
                                  callable $deserialize,
                                  $metadata = array()) {
     $call = new UnaryCall($this->channel, $method, $deserialize);
-    $call->start($argument, $metadata);
+    $actual_metadata = $metadata;
+    if (is_callable($this->update_metadata)) {
+      $actual_metadata = call_user_func($this->update_metadata,
+                                        $actual_metadata);
+    }
+    $call->start($argument, $actual_metadata);
     return $call;
   }
 
@@ -89,7 +111,12 @@ class BaseStub {
                                        callable $deserialize,
                                        $metadata = array()) {
     $call = new ClientStreamingCall($this->channel, $method, $deserialize);
-    $call->start($arguments, $metadata);
+    $actual_metadata = $metadata;
+    if (is_callable($this->update_metadata)) {
+      $actual_metadata = call_user_func($this->update_metadata,
+                                        $actual_metadata);
+    }
+    $call->start($arguments, $actual_metadata);
     return $call;
   }
 
@@ -108,7 +135,12 @@ class BaseStub {
                                        callable $deserialize,
                                        $metadata = array()) {
     $call = new ServerStreamingCall($this->channel, $method, $deserialize);
-    $call->start($argument, $metadata);
+    $actual_metadata = $metadata;
+    if (is_callable($this->update_metadata)) {
+      $actual_metadata = call_user_func($this->update_metadata,
+                                        $actual_metadata);
+    }
+    $call->start($argument, $actual_metadata);
     return $call;
   }
 
@@ -124,7 +156,12 @@ class BaseStub {
                                callable $deserialize,
                                $metadata = array()) {
     $call = new BidiStreamingCall($this->channel, $method, $deserialize);
-    $call->start($metadata);
+    $actual_metadata = $metadata;
+    if (is_callable($this->update_metadata)) {
+      $actual_metadata = call_user_func($this->update_metadata,
+                                        $actual_metadata);
+    }
+    $call->start($actual_metadata);
     return $call;
   }
 }
diff --git a/src/php/tests/generated_code/AbstractGeneratedCodeTest.php b/src/php/tests/generated_code/AbstractGeneratedCodeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2d2352b19965134da56a3f3a3d1e0951dcc0d808
--- /dev/null
+++ b/src/php/tests/generated_code/AbstractGeneratedCodeTest.php
@@ -0,0 +1,97 @@
+<?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.
+ *
+ */
+require_once realpath(dirname(__FILE__) . '/../../vendor/autoload.php');
+require 'DrSlump/Protobuf.php';
+\DrSlump\Protobuf::autoload();
+require 'math.php';
+abstract class AbstractGeneratedCodeTest 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 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->assertSame(1, $response->getQuotient());
+    $this->assertSame(3, $response->getRemainder());
+    $this->assertSame(\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->assertSame([1, 1, 2, 3, 5, 8, 13], $values);
+    $status = $call->getStatus();
+    $this->assertSame(\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->assertSame(21, $response->getNum());
+    $this->assertSame(\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->assertSame($i, $response->getQuotient());
+      $this->assertSame(1, $response->getRemainder());
+    }
+    $call->writesDone();
+    $status = $call->getStatus();
+    $this->assertSame(\Grpc\STATUS_OK, $status->code);
+  }
+}
diff --git a/src/php/tests/generated_code/GeneratedCodeTest.php b/src/php/tests/generated_code/GeneratedCodeTest.php
index 927d24ca63086c4eaa961619030e974b3cfdfa77..1e4742fc8009c5a599d8dded588b7d6bd76c10b6 100755
--- a/src/php/tests/generated_code/GeneratedCodeTest.php
+++ b/src/php/tests/generated_code/GeneratedCodeTest.php
@@ -31,71 +31,11 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  */
-require_once realpath(dirname(__FILE__) . '/../../vendor/autoload.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;
+require 'AbstractGeneratedCodeTest.php';
+
+class GeneratedCodeTest extends AbstractGeneratedCodeTest {
   public static function setUpBeforeClass() {
     self::$client = new math\MathClient(new Grpc\BaseStub(
         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->assertSame(1, $response->getQuotient());
-    $this->assertSame(3, $response->getRemainder());
-    $this->assertSame(\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->assertSame([1, 1, 2, 3, 5, 8, 13], $values);
-    $status = $call->getStatus();
-    $this->assertSame(\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->assertSame(21, $response->getNum());
-    $this->assertSame(\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->assertSame($i, $response->getQuotient());
-      $this->assertSame(1, $response->getRemainder());
-    }
-    $call->writesDone();
-    $status = $call->getStatus();
-    $this->assertSame(\Grpc\STATUS_OK, $status->code);
-  }
 }
diff --git a/src/php/tests/generated_code/GeneratedCodeWithCallbackTest.php b/src/php/tests/generated_code/GeneratedCodeWithCallbackTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ec95375e913075ae925041959680cd20d6e93be
--- /dev/null
+++ b/src/php/tests/generated_code/GeneratedCodeWithCallbackTest.php
@@ -0,0 +1,48 @@
+<?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.
+ *
+ */
+require 'AbstractGeneratedCodeTest.php';
+
+class GeneratedCodeWithCallbackTest extends AbstractGeneratedCodeTest {
+  public static function setUpBeforeClass() {
+    self::$client = new math\MathClient(new Grpc\BaseStub(
+        getenv('GRPC_TEST_HOST'), ['update_metadata' =>
+                                   function($a_hash,
+                                            $opts = array(),
+                                            $client = array()) {
+                                     $a_copy = $a_hash;
+                                     $a_copy['foo'] = ['bar'];
+                                     return $a_copy;
+                                   }]));
+  }
+}