一.安装和配置
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文件内容如下:
php
#!/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文件内容如下:
php
#!/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服务端接口
php
$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配置详见