Merge pull request #15960 from mehrdada/upmerge-v1.13.x

Upmerge v1.13.x into master
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index 5ae6931..ecb419a 100644
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -28,6 +28,7 @@
     private $hostname;
     private $hostname_override;
     private $channel;
+    private $call_invoker;
 
     // a callback function
     private $update_metadata;
@@ -58,6 +59,15 @@
         if (!empty($opts['grpc.ssl_target_name_override'])) {
             $this->hostname_override = $opts['grpc.ssl_target_name_override'];
         }
+        if (isset($opts['grpc_call_invoker'])) {
+            $this->call_invoker = $opts['grpc_call_invoker'];
+            unset($opts['grpc_call_invoker']);
+            $channel_opts = $this->updateOpts($opts);
+            // If the grpc_call_invoker is defined, use the channel created by the call invoker.
+            $this->channel = $this->call_invoker->createChannelFactory($hostname, $channel_opts);
+            return;
+        }
+        $this->call_invoker = new DefaultCallInvoker();
         if ($channel) {
             if (!is_a($channel, 'Grpc\Channel') &&
                 !is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
@@ -72,15 +82,7 @@
         $this->channel = static::getDefaultChannel($hostname, $opts);
     }
 
-    /**
-     * Creates and returns the default Channel
-     *
-     * @param array $opts Channel constructor options
-     *
-     * @return Channel The channel
-     */
-    public static function getDefaultChannel($hostname, array $opts)
-    {
+    private static function updateOpts($opts) {
         $package_config = json_decode(
             file_get_contents(dirname(__FILE__).'/../../composer.json'),
             true
@@ -97,6 +99,19 @@
                 'required. Please see one of the '.
                 'ChannelCredentials::create methods');
         }
+        return $opts;
+    }
+
+    /**
+     * Creates and returns the default Channel
+     *
+     * @param array $opts Channel constructor options
+     *
+     * @return Channel The channel
+     */
+    public static function getDefaultChannel($hostname, array $opts)
+    {
+        $channel_opts = self::updateOpts($opts);
         return new Channel($hostname, $opts);
     }
 
@@ -239,7 +254,7 @@
                          $deserialize,
                          array $metadata = [],
                          array $options = []) use ($channel) {
-            $call = new UnaryCall(
+            $call = $this->call_invoker->UnaryCall(
                 $channel,
                 $method,
                 $deserialize,
@@ -275,7 +290,7 @@
                          $deserialize,
                          array $metadata = [],
                          array $options = []) use ($channel) {
-            $call = new ClientStreamingCall(
+            $call = $this->call_invoker->ClientStreamingCall(
                 $channel,
                 $method,
                 $deserialize,
@@ -312,7 +327,7 @@
                          $deserialize,
                          array $metadata = [],
                          array $options = []) use ($channel) {
-            $call = new ServerStreamingCall(
+            $call = $this->call_invoker->ServerStreamingCall(
                 $channel,
                 $method,
                 $deserialize,
@@ -348,7 +363,7 @@
                          $deserialize,
                          array $metadata = [],
                          array $options = []) use ($channel) {
-            $call = new BidiStreamingCall(
+            $call = $this->call_invoker->BidiStreamingCall(
                 $channel,
                 $method,
                 $deserialize,
diff --git a/src/php/lib/Grpc/CallInvoker.php b/src/php/lib/Grpc/CallInvoker.php
new file mode 100644
index 0000000..a1b4553
--- /dev/null
+++ b/src/php/lib/Grpc/CallInvoker.php
@@ -0,0 +1,33 @@
+<?php
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+namespace Grpc;
+
+/**
+ * CallInvoker is used to pass the self defined channel into the stub,
+ * while intercept each RPC with the channel accessible.
+ * THIS IS AN EXPERIMENTAL API.
+ */
+interface CallInvoker
+{
+    public function createChannelFactory($hostname, $opts);
+    public function UnaryCall($channel, $method, $deserialize, $options);
+    public function ClientStreamingCall($channel, $method, $deserialize, $options);
+    public function ServerStreamingCall($channel, $method, $deserialize, $options);
+    public function BidiStreamingCall($channel, $method, $deserialize, $options);
+}
diff --git a/src/php/lib/Grpc/DefaultCallInvoker.php b/src/php/lib/Grpc/DefaultCallInvoker.php
new file mode 100644
index 0000000..e5b1e13
--- /dev/null
+++ b/src/php/lib/Grpc/DefaultCallInvoker.php
@@ -0,0 +1,47 @@
+<?php
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+namespace Grpc;
+
+/**
+ * Default call invoker in the gRPC stub.
+ * THIS IS AN EXPERIMENTAL API.
+ */
+class DefaultCallInvoker implements CallInvoker
+{
+    public function createChannelFactory($hostname, $opts) {
+        return new Channel($hostname, $opts);
+    }
+
+    public function UnaryCall($channel, $method, $deserialize, $options) {
+        return new UnaryCall($channel, $method, $deserialize, $options);
+    }
+
+    public function ClientStreamingCall($channel, $method, $deserialize, $options) {
+        return new ClientStreamingCall($channel, $method, $deserialize, $options);
+    }
+
+    public function ServerStreamingCall($channel, $method, $deserialize, $options) {
+        return new ServerStreamingCall($channel, $method, $deserialize, $options);
+    }
+
+    public function BidiStreamingCall($channel, $method, $deserialize, $options) {
+        return new BidiStreamingCall($channel, $method, $deserialize, $options);
+    }
+}
+
diff --git a/src/php/tests/unit_tests/CallInvokerTest.php b/src/php/tests/unit_tests/CallInvokerTest.php
new file mode 100644
index 0000000..00b5b99
--- /dev/null
+++ b/src/php/tests/unit_tests/CallInvokerTest.php
@@ -0,0 +1,227 @@
+<?php
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+/**
+ * Interface exported by the server.
+ */
+require_once(dirname(__FILE__).'/../../lib/Grpc/BaseStub.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/AbstractCall.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/UnaryCall.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/ClientStreamingCall.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/Interceptor.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/CallInvoker.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/DefaultCallInvoker.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/Internal/InterceptorChannel.php');
+
+class CallInvokerSimpleRequest
+{
+    private $data;
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+    public function setData($data)
+    {
+        $this->data = $data;
+    }
+    public function serializeToString()
+    {
+        return $this->data;
+    }
+}
+
+class CallInvokerClient extends Grpc\BaseStub
+{
+
+  /**
+   * @param string $hostname hostname
+   * @param array $opts channel options
+   * @param Channel|InterceptorChannel $channel (optional) re-use channel object
+   */
+  public function __construct($hostname, $opts, $channel = null)
+  {
+    parent::__construct($hostname, $opts, $channel);
+  }
+
+  /**
+   * A simple RPC.
+   * @param SimpleRequest $argument input argument
+   * @param array $metadata metadata
+   * @param array $options call options
+   */
+  public function UnaryCall(
+    CallInvokerSimpleRequest $argument,
+    $metadata = [],
+    $options = []
+  ) {
+    return $this->_simpleRequest(
+      '/dummy_method',
+      $argument,
+      [],
+      $metadata,
+      $options
+    );
+  }
+}
+
+class CallInvokerUpdateChannel implements \Grpc\CallInvoker
+{
+    private $channel;
+
+    public function getChannel() {
+        return $this->channel;
+    }
+
+    public function createChannelFactory($hostname, $opts) {
+        $this->channel = new \Grpc\Channel('localhost:50050', $opts);
+        return $this->channel;
+    }
+
+    public function UnaryCall($channel, $method, $deserialize, $options) {
+        return new UnaryCall($channel, $method, $deserialize, $options);
+    }
+
+    public function ClientStreamingCall($channel, $method, $deserialize, $options) {
+        return new ClientStreamingCall($channel, $method, $deserialize, $options);
+    }
+
+    public function ServerStreamingCall($channel, $method, $deserialize, $options) {
+        return new ServerStreamingCall($channel, $method, $deserialize, $options);
+    }
+
+    public function BidiStreamingCall($channel, $method, $deserialize, $options) {
+        return new BidiStreamingCall($channel, $method, $deserialize, $options);
+    }
+}
+
+
+class CallInvokerChangeRequest implements \Grpc\CallInvoker
+{
+    private $channel;
+
+    public function getChannel() {
+        return $this->channel;
+    }
+    public function createChannelFactory($hostname, $opts) {
+        $this->channel = new \Grpc\Channel($hostname, $opts);
+        return $this->channel;
+    }
+
+    public function UnaryCall($channel, $method, $deserialize, $options) {
+        return new CallInvokerChangeRequestCall($channel, $method, $deserialize, $options);
+    }
+
+    public function ClientStreamingCall($channel, $method, $deserialize, $options) {
+        return new ClientStreamingCall($channel, $method, $deserialize, $options);
+    }
+
+    public function ServerStreamingCall($channel, $method, $deserialize, $options) {
+        return new ServerStreamingCall($channel, $method, $deserialize, $options);
+    }
+
+    public function BidiStreamingCall($channel, $method, $deserialize, $options) {
+        return new BidiStreamingCall($channel, $method, $deserialize, $options);
+    }
+}
+
+class CallInvokerChangeRequestCall
+{
+    private $call;
+
+    public function __construct($channel, $method, $deserialize, $options)
+    {
+        $this->call = new \Grpc\UnaryCall($channel, $method, $deserialize, $options);
+    }
+
+    public function start($argument, $metadata, $options) {
+        $argument->setData('intercepted_unary_request');
+        $this->call->start($argument, $metadata, $options);
+    }
+
+    public function wait()
+    {
+        return $this->call->wait();
+    }
+}
+
+class CallInvokerTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->server = new Grpc\Server([]);
+        $this->port = $this->server->addHttp2Port('0.0.0.0:0');
+        $this->server->start();
+    }
+
+    public function tearDown()
+    {
+        unset($this->server);
+    }
+
+    public function testCreateDefaultCallInvoker()
+    {
+        $call_invoker = new \Grpc\DefaultCallInvoker();
+    }
+
+    public function testCreateCallInvoker()
+    {
+        $call_invoker = new CallInvokerUpdateChannel();
+    }
+
+    public function testCallInvokerAccessChannel()
+    {
+        $call_invoker = new CallInvokerUpdateChannel();
+        $stub = new \Grpc\BaseStub('localhost:50051',
+          ['credentials' => \Grpc\ChannelCredentials::createInsecure(),
+            'grpc_call_invoker' => $call_invoker]);
+        $this->assertEquals($call_invoker->getChannel()->getTarget(), 'localhost:50050');
+        $call_invoker->getChannel()->close();
+    }
+
+    public function testClientChangeRequestCallInvoker()
+    {
+        $req_text = 'client_request';
+        $call_invoker = new CallInvokerChangeRequest();
+        $client = new CallInvokerClient('localhost:'.$this->port, [
+            'force_new' => true,
+            'credentials' => Grpc\ChannelCredentials::createInsecure(),
+            'grpc_call_invoker' => $call_invoker,
+        ]);
+
+        $req = new CallInvokerSimpleRequest($req_text);
+        $unary_call = $client->UnaryCall($req);
+
+        $event = $this->server->requestCall();
+        $this->assertSame('/dummy_method', $event->method);
+        $server_call = $event->call;
+        $event = $server_call->startBatch([
+            Grpc\OP_SEND_INITIAL_METADATA => [],
+            Grpc\OP_SEND_STATUS_FROM_SERVER => [
+                'metadata' => [],
+                'code' => Grpc\STATUS_OK,
+                'details' => '',
+            ],
+            Grpc\OP_RECV_MESSAGE => true,
+            Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+        ]);
+        $this->assertSame('intercepted_unary_request', $event->message);
+        $call_invoker->getChannel()->close();
+        unset($unary_call);
+        unset($server_call);
+    }
+}
diff --git a/src/php/tests/unit_tests/InterceptorTest.php b/src/php/tests/unit_tests/InterceptorTest.php
index d759ceb..0ad49fc 100644
--- a/src/php/tests/unit_tests/InterceptorTest.php
+++ b/src/php/tests/unit_tests/InterceptorTest.php
@@ -24,6 +24,7 @@
 require_once(dirname(__FILE__).'/../../lib/Grpc/UnaryCall.php');
 require_once(dirname(__FILE__).'/../../lib/Grpc/ClientStreamingCall.php');
 require_once(dirname(__FILE__).'/../../lib/Grpc/Interceptor.php');
+require_once(dirname(__FILE__).'/../../lib/Grpc/CallInvoker.php');
 require_once(dirname(__FILE__).'/../../lib/Grpc/Internal/InterceptorChannel.php');
 
 class SimpleRequest