PHP客户端调用由Go服务端GRPC接口
一.安装和配置
1.本地windows下安装php的gprc和protobuf扩展:
gRPC扩展:(php7.4.3nts)
https://pecl.php.net/package/gRPC/1.43.0/windows
protobuf扩展:
https://pecl.php.net/package/protobuf/3.24.4/windows
将下载的dll文件放到对应php版本的ext文件下:D:\phpstudy_pro\Extensions\php\php7.4.3nts
再修改对应php版本的php.ini文件添加:
extension="D:/phpstudy_pro/Extensions/php/php7.4.3nts/php_grpc.dll"
extension=D:/phpstudy_pro/Extensions/php/php7.4.3nts/php_protobuf.dll
重启nginx然后在pathinfo()查看或者执行下面的命令看是否有grpc和protobuf扩展
2.composer安装包grpc和protobuf:
composer require google/protobuf:3.24.4
composer require grpc/grpc:^1.42.0 (由于提示1.43.0不存在 则按照1.42.x系列最新版)
3.本地使用grpc_php_plugin插件 可以生成对应proto的php文件
可以通过git clone https://github.com/lifenglsf/grpc_for_windows.git 下载到本地,然后查看文件夹下对应系统的grpc_php_plugin.exe文件
或者在https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0下面下载对应的版本
(我的是在grpc_for_windows/x64/grpc_php_plugin.exe)
二.指定proto文件生成对应的php文件
1.将go的grpc文件复制到laravel框架:
/protos/food :里面有common.proto和user.proto如下,新增命名空间


2.执行命令将proto文件生成对应php文件(project下有generator和generate_grpc.sh文件)
(1)这里使用了swoole/grpc里面的./generator 可以自动生成所有proto文件对应的php文件,不用单个proto文件去生成
./generator文件内容如下:
#!/usr/bin/env php
<?php
/*+----------------------------------------------------------------------+| Swoole-gRPC |+----------------------------------------------------------------------+| This source file is subject to version 2.0 of the Apache license, || that is bundled with this package in the file LICENSE, and is || available through the world-wide-web at the following url: || http://www.apache.org/licenses/LICENSE-2.0.html || If you did not receive a copy of the Apache2.0 license and are unable|| to obtain it through the world-wide-web, please send a note to || license@swoole.com so we can mail you a copy immediately. |+----------------------------------------------------------------------+| Author: Twosee <twose@qq.com> |+----------------------------------------------------------------------+
*/function scan_dir(string $dir, callable $filter = null): array
{$files = scandir($dir);$files = array_filter($files, function (string $f) {return $f[0] !== '.';});array_walk($files, function (string &$value) use ($dir) {$value = rtrim($dir, '/') . '/' . $value;});return array_values($filter ? array_filter($files, $filter) : $files);
}function generateClient(string $proto_dir)
{#下面的文件名需要修改!!!!!!!!!!!$proto_dir = 'D:\project\udc\app\Grpc\Protos\Food';echo $proto_dir;$source_dirs = scan_dir($proto_dir, function (string $f) use ($proto_dir) {return is_dir($f);});$php_files = [];foreach ($source_dirs as $source_dir) {$php_files[] = scan_dir($source_dir, function (string $f) {return substr($f, -4, 4) === '.php';});}$php_files = array_merge(...$php_files);foreach ($php_files as $php_file) {$file_content = file_get_contents($php_file);$extends_keyword = ' extends \Grpc\BaseStub';if (strpos($file_content, $extends_keyword) !== false) {// $filename = explode('/', $php_file);// $filename = end($filename);// use swoole construct$file_content = str_replace('__construct($hostname, $opts, $channel','__construct($hostname, $opts',$file_content);// fit swoole arguments$file_content = str_replace('$opts = null', '$opts = []', $file_content);// use correct return value$file_content = preg_replace_callback('/(call options)(\n([ ]+?)\*\/\n[ ]+?public function[\s\S]+?(_\w+Request)[\s\S]+?\[\'([^\']+)\', ?\'\w+\'\],)/',function (array $match) {switch ($match[4]) {case '_simpleRequest':return "{$match[1]}\n{$match[3]}* @return {$match[5]}[]|\\Grpc\\StringifyAble[]{$match[2]}";case '_bidiRequest':return "{$match[1]}\n{$match[3]}* @return bool|\\Grpc\\BidiStreamingCall{$match[2]}";case '_serverStreamRequest':return "{$match[1]}\n{$match[3]}* @return bool|\\Grpc\\ServerStreamingCall{$match[2]}";case '_clientStreamRequest':return "{$match[1]}\n{$match[3]}* @return bool|\\Grpc\\ClientStreamingCall{$match[2]}";}return $match[0];},$file_content);file_put_contents($php_file, $file_content);}}
}function generateFromProto(string $proto_path, string $php_out, string $grpc_out, string $plugin, array $proto_list)
{$plugin_file = explode('=', $plugin)[1] ?? null;if (!$plugin_file) {$plugin = "protoc-gen-grpc={$plugin}";}if (!file_exists($plugin_file)) {exit("Can't find the plugin generator file [{$plugin_file}]");}function realGenerate($proto_path, $php_out, $grpc_out, $plugin, array $proto_list){foreach ($proto_list as $key => $proto_file) {if (is_dir($proto_file)) {$proto_deep_list = scan_dir($proto_file, function (string $f) {return substr($f, -6, 6) === '.proto';});realGenerate($proto_path, $php_out, $grpc_out, $plugin, $proto_deep_list);} else {`protoc --proto_path={$proto_path} --php_out={$php_out} --grpc_out={$grpc_out} --plugin={$plugin} {$proto_file}`;}}}realGenerate($proto_path, $php_out, $grpc_out, $plugin, $proto_list);
}function get_command(&$command, &$options, &$params): void
{global $argv;$arguments = $argv;$command = '';//命令$options = [];//选项$params = []; //参数array_shift($arguments);if (isset($arguments[0]) && substr($arguments[0], 0, 1) !== '-') {$command = array_shift($arguments); //指定第一个参数为命令}foreach ($arguments as $i => $v) {if (empty($v)) {continue;} elseif (substr($v, 0, 2) === '--') {$now = substr($v, 2);$now = explode('=', $now);$options[trim(array_shift($now))] = trim(implode('=', $now));} else {$params[] = $v;}}
}(function () {get_command($command, $options, $params);if (empty($command)) {$needle_params = ['proto_path' => null,'php_out' => null,'grpc_out' => null,'plugin' => __DIR__ . './../../grpc/bins/opt/grpc_php_plugin'];$proto_path = $php_out = $grpc_out = $plugin = '';foreach ($needle_params as $param_name => $param_default_value) {if (empty($options[$param_name])) {if ($param_default_value === null) {exit("{$param_name} is missing!");} else {$options[$param_name] = $param_default_value;}} else {if ($param_name === 'php_out') {$needle_params['grpc_out'] = $options[$param_name];}}$$param_name = $options[$param_name];}generateFromProto($proto_path, $php_out, $grpc_out, $plugin, $params);
// generateClient($php_out);}
})();
(2)generate_grpc.sh文件内容如下:
#!/usr/bin/env bash# 指定food的proto文件生成php文件# 生成代码
# 1.protoc-gen-grpc需要指向本地grpc_php_plugin.exe所在位置
# 2.指定要生成的proto文件
./generator \
--proto_path=./protos \
--php_out=./ \
--grpc_out=./ \
--plugin=protoc-gen-grpc=../grpc_for_windows/x64/grpc_php_plugin.exe \
./protos/food
#上述文件名(/protos/food)需要对应修改!!!!!!!!!!!
echo "gRPC 代码已重新生成到 app/Grpc下"
sleep(2)#双击该文件执行或执行 ./generate_grpc.sh ../grpc_for_windows/x64/grpc_php_plugin.exe
#即可生成对应proto的php文件
(3)生成:
双击generate_grpc.sh文件
或执行 ./generate_grpc.sh ../grpc_for_windows/x64/grpc_php_plugin.exe
即可生成对应proto的php文件
# 这里是在laravel框架.env同级目录下执行
# --php_out php代码输出路径,里面包含request,response,client代码
# --grpc_out GPBMetadata输出路径,用于保存.proto的二进制元数据
# --plugin 生成代码插件的类型与插件的绝对路径路径
三.调用go服务端接口
$metaData = ['Sign' => [$sign],'TimeStamp' => [(string)$requestTime],];$request = new CommonRequest();$request->setData($paramData);$hostname = '127.0.0.1:8081';$opts = ['credentials' => ChannelCredentials::createInsecure()];$client = new UserClient($hostname,$opts);list($response, $status) = $client->UserInfo($request, $metaData)->wait();if ($status->code !== \Grpc\STATUS_OK) {Log::error("gRPC调用失败: 状态码={$status->code}, 错误详情={$status->details}");}if($response!=null){$responseData = json_decode($response->serializeToJsonString(), true);}
四.go配置详见
