2023年1月

Goridge 是 SpiralsCout 开源的高性能的 PHP 到 Golang 编解码器库。该库可以帮助你在 PHP 调用 Go 的服务方法。

Goridge 入门

使用方法:

我们先来编写简单的 Go 程序,目的就是提供一个 rpc 接口给 PHP 进行访问。

GO111MODULE=on go get github.com/roadrunner-server/goridge/v3

package main

import (
    "fmt"
    "net"
    "net/rpc"

    goridgeRpc "github.com/roadrunner-server/goridge/v3/pkg/rpc"
)

type App struct{}

func (s *App) Hi(name string, r *string) error {
    *r = fmt.Sprintf("Hello, %s!", name)
    return nil
}

func main() {
    ln, err := net.Listen("tcp", ":6001")
    if err != nil {
        panic(err)
    }

    _ = rpc.Register(new(App))

    for {
        conn, err := ln.Accept()
        if err != nil {
            continue
        }
        _ = conn
        go rpc.ServeCodec(goridgeRpc.NewCodec(conn))
    }
}

Hi 函数用来接受一个 name 入参。

# 进入工程陌路
cd project
# 安装 spiral/goridge sdk
composer install spiral/goridge
# 编写 index.php
vim index.php

<?php

use Spiral\Goridge\RPC\RPC;
use Spiral\Goridge\SocketRelay;

include 'vendor/autoload.php';

$rpc1 = new RPC(new SocketRelay("127.0.0.1", 6001));
echo $rpc1->call("App.Hi", "Goridge RPC");

2023-03-08T12:31:55.png

PHP 访问 Go 原生 RPC 的方法

在 2019 年,我们的团队开始引入 Golang,原因是想通过 Golang 良好的并发性来提升我们的应用访问速度,但是正常情况下公司是不会给研发团队大片的时间去进行重构,这个时候我们就需要 PHP 和 Go 进行通讯。

不过那个时候我们并没有使用 Goridge,而是直接用 PHP 访问 Go RPC。现在我们来模拟一下当时的场景,还是用 Goridge 的那个例子,其实实现思路大体上是一致的,不过我们并没有写自己的编解码,而是利用了 jsonrpc。

package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type App struct{}

func (s *App) Hi(name string, r *string) error {
    *r = fmt.Sprintf("Hello, %s!", name)
    return nil
}

func main() {
    rpc := rpc.NewServer()
    app := new(App)
    rpc.Register(app)
    ln, err := net.Listen("tcp", ":6001")
    defer ln.Close()
    if err != nil {
        panic(err)
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            panic(err)
        }
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

在 go 中我,我们使用 jsonrpc 作为解码器,这样一来就是可以通过 socket 传递 json 的方式进行访问 go 的 rpc 接口了。

2023-03-08T13:16:34.png

如果对 go rpc 不是特别了解的话,可以通过 https://colobu.com/2016/09/18/go-net-rpc-guide/ 这篇文章了解一下,该文的作者是 rpcx 的作者鸟窝,是 Golang 界的一位大牛。

目前企业大多数都是使用微服务架构作为解决方案,在这里我们使用 yaf + yar 来作为基础实现一套企业级的微服务解决方案。

Yaf 是鸟哥开发的基于 C 语言扩展的高性能框架,而 yar 是一款 rpc 解决方案。

安装 yaf

最简单的安装方式:

# 利用 pecl 安装 yaf
➜  ~ sudo pecl install yaf
# 验证是否安装成功
➜  ~ php -m | grep yaf
yaf

yaf 的 hello world

在正式开始之前,我们先来解决的代码提示,这是因为 yaf 作为 C 语言编写的扩展,我们的 phpstrom 无法给出代码提醒,这就降低了我们的研发效率,所以我们要先解决代码提示的问题。

➜  ~ git clone https://github.com/elad-yosifon/php-yaf-doc.git

然后打开 phpstrom,选择外部库,然后添加刚才下载的 php-yaf-doc**

2023-03-04T21:05:48.png

2023-03-04T21:06:03.png

这个时候我们再编写代码的时候就可以用到代码提醒了,这个时候我们按照国际惯例,开始编写 hello world程序。

我们先来建立标准目录,鸟哥在文档中给了我们很好的案例。

- .htaccess // Rewrite rules
+ public
  | - index.php // Application entry
  | + css
  | + js
  | + img
+ conf
  | - application.ini // Configure 
- application/
  - Bootstrap.php   // Bootstrap
  + controllers
     - Index.php // Default controller
  + views    
     |+ index   
        - index.phtml // View template for default controller
  + library // libraries
  + models  // Models
  + plugins // Plugins

2023-03-04T21:06:23.png

首先我们来看 public 下的 index。public 是公共目录,其中 index.php就是我们的入口文件。

define('APPLICATION_PATH', dirname(__FILE__, 2));
try {
    $app = new Yaf_Application(APPLICATION_PATH . "/conf/application.ini");
    $app->bootstrap() //call bootstrap methods defined in Bootstrap.php
    ->run();
} catch (Yaf_Exception_StartupError|Yaf_Exception_TypeError $e) {

}

首先是定义了应用目录,然后初始化 Yaf_Application,这里的异常我们先不处理。

这里,我们需要先创建 application.ini 这 Bootstrap.php。

[product]
;CONSTANTS is supported
application.directory = APPLICATION_PATH "/application/"
<?php
class Bootstrap extends Yaf_Bootstrap_Abstract
{
}

之后就是 IndexController了。

<?php

class IndexController extends Yaf_Controller_Abstract
{
    public function indexAction()
    {
        $this->getView()->assign('context', 'hello world');
    }
}

与其他框架一样,Yaf 也是一款 MVC 框架,我们可以直接利用 assign 像页面复制。

2023-03-04T21:06:40.png

这里我们需要注意目录结构,index 代表的是 IndexController,index.phtml才是 indexAction。我们通过PHP 内置的服务器来运行我们的应用。

➜  yaf-micro php -S localhost:8080 -t public

2023-03-04T21:07:00.png

yaf 的配置

yaf 框架的配置主要分为运行时配置和应用配置。

运行时配置

运行时配置是配置在 php.ini配置文件中的。

配置名称默认值含义
yaf.library全局路径,Yaf_loader会从这个目录检索全局库
yaf.action_prefer0如果路径信息中只有一部分,当设置为0时,会把这部分作为controller,如果设置为1,会把这部分作为action
yaf.lowcase_path0在类自动加载过程中是否将所有的路径转换为小写
yaf.use_spl_autoload0当设置为0时,Yaf_Loader::autoload()将一直返回true,设置为1时,如果Yaf_Loader找不到类时,将返回false,由其他自动加载方法处理
yaf.forward_limit5Yaf_Controller_Abstract::forward()的最大次数,防止递归次数过多,设置最大保护机制
yaf.name_suffix1当设置为1时,Yaf_Loader将根据类后缀名判断是否是一个MVC类,若设置为0,将根据类前缀名判断
yaf.name_separator当该值不为空时,Yaf_Loader将根据这个值来鉴定类后缀和值,如:当值为"_"时,Yaf_Loader将把Index_Controller作为控制器类,IndexController作为普通类
yaf.cache_config0当设置为1时,同时使用ini配置文件作为Yaf_Application()的参数时,ini配置文件的编译结果将被缓存到php进程中,Yaf根据ini文件的mtime判断配置是否更新,如果更新,Yaf将重新加载ini配置,注意,yaf使用ini文件路径作为缓存key,所以在ini文件路径中使用绝对路径,否则,如果两个应用程序使用相同的ini配置相对路径,可能会出现一些冲突。
yaf.environproduct用于yaf获取ini配置文件的config部分,如果该值为“product”,那么yaf将使用ini配置文件(Yaf_Application第一个参数)中名为“product”的部分作为Yaf_Application的最终配置。
yaf.use_namespace0当设置为1时,Yaf所有的类都将以命名空间的方式使用

预定义常量

常量名称含义
YAF_VERSIONyaf框架版本
YAF_ENVIRONyaf框架环境
YAF_ERR_STARTUP_FAILED错误码
YAF_ERR_ROUTE_FAILED错误码
YAF_ERR_DISPATCH_FAILED错误码
YAF_ERR_AUTOLOAD_FAILED错误码
YAF_ERR_NOTFOUND_MODULE错误码
YAF_ERR_NOTFOUND_CONTROLLER错误码
YAF_ERR_NOTFOUND_ACTION错误码
YAF_ERR_NOTFOUND_VIEW错误码
YAF_ERR_CALL_FAILED错误码
YAF_ERR_TYPE_ERROR错误码
  • get_defined_constants()方法获取yaf预定义的常量

应用配置

应用配置是在工程目录conf 目录下的 application.ini 中进行配置。

配置名称默认值含义
application.directory应用程序的目录,包含"controllers", "views", "models", "plugins"等子目录
application.extphpPHP脚本的扩展名,Yaf_Loader自动加载类的时候需要用到它
application.view.extphtml视图模板扩展名
application.modulesindex注册的模块列表,以逗号分隔,用于路由处理,特别是当PATH_INFO超过三段的时候
application.libraryapplication.directory . "/library"本地类库的目录
application.library.directoryapplication.directory . "/library"本地类库的目录,application.library的别名
application.library.namespace""逗号分隔的本地类库命名空间前缀,Yaf2.1.6以后加入
application.bootstrapapplication.directory . "/Bootstrap" . application.extBootstrap类脚本文件的绝对路径
application.baseUri""路由处理中需要忽略的路径前缀。举个例子,请求"/prefix/controller/action"时。如果你将application.baseUri设置为"/prefix",那么只有"/controller/action"会被当做路由路径。通常不需要设置此值。
application.dispatcher.defaultRoute默认路由,如果未指定,静态路由会被当做是默认路由
application.dispatcher.throwException1开启此项,Yaf会在发生错误的地方抛出异常。
application.dispatcher.catchException0开启此项,如果有未捕获的异常,Yaf将会把它定向到Error controller, Error Action。
application.dispatcher.defaultModuleindex开启此项,如果有未捕获的异常,Yaf将会把它定向到Error controller, Error Action。
application.dispatcher.defaultControllerindex开启此项,如果有未捕获的异常,Yaf将会把它定向到Error controller, Error Action。
application.dispatcher.defaultActionindex开启此项,如果有未捕获的异常,Yaf将会把它定向到Error controller, Error Action。
application.system在application.ini中设置Yaf运行时配置