0%

这个指南描述了如何使用 protocol buffer 语言来组织你的 protocol buffer 数据,包括 .proto 文件语法和如何从你的 .proto 文件生成数据访问类。

定义一个消息类型

首先我们来看一个简单的例子。假设你要定义一个请求消息格式,每个搜索请求有一个查询字符串,你想要查询特定页数的数据,并且指定每一页的大小。这里是定义消息类型的 .proto 文件。

1
2
3
4
5
6
7
syntax = "proto3";

message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
  • 第一行表示我们使用的是 proto3 的语法;如果不指定版本,protobuf 编译器会当作 proto2 处理。这行必须处于文件开头。

  • SearchRequest 消息类型定义了三个字段,每个字段有一个名字和类型。

指定字段类型

在上面的例子中,所有字段都是标量类型:两个整数(page_number 和 result_per_page) 和一个字符串(query)。然而,你也可以组合不同类型的字段到一个字段中,作为一个新的消息类型。

分配字段编号

正如你所见,消息中每个字段都有一个唯一的数字。这些字段数字被用来作为二进制消息中的唯一标识,同时一旦消息类型被使用就不应该再修改这个唯一标识。请注意,范围为 1 到 15 的字段号需要一个字节来编码, 包括字段号和字段的类型。16 到 2047 之间的字段编号占用两个字节。因此,应该为经常出现的消息元素保留数字 1 到 15。请记住为将来可能添加的频繁出现的元素流出一些空间。

指定字段规则

消息字段可以是以下格式之一:

  • 单数:格式正确的消息可以包含零个或一个此字段(但不能超过一个)。这是 proto3 语法的默认字段规则。

  • repeated:在格式正确的消息中,此字段可以重复任意次(包括零次)。重复值的顺序将保留。

在 proto3 中,repeated 标量数字类型的字段 packed 默认情况下使用编码。

添加更多消息类型

可以在一个 .proto 文件中定义多种消息类型。如果要定义多个相关消息,这很有用,例如,如果要定义你的 SearchResponse 消息类型相对应的回复消息格式,可以将其添加到相同的消息中 .proto:

1
2
3
4
5
6
7
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}

message SearchResponse {}

添加注释

要将注释添加到 .proto 文件中,请使用 C/C++ 样式 // 和 /* */ 语法。

1
2
3
4
5
6
7
8
/* SearchRequest represents a search query, with pagination options to
indicate which results to include in the response. */

message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want ?
int32 result_per_page = 3; // Number of results to return per page.
}

保留字段

如果你完全删除一个字段或将其注释掉来更新消息类型,则将来的用户在自己对该类型进行更新时可以重用该字段号。如果他们以后加载相同版本的旧版本,可能会导致严重的问题 .proto, 包括数据损坏,隐私错误等。确保不会发生这种情况的一种方法是,将已删除字段的字段编号(和/或名称,也可能导致 JSON 序列化的问题)指定为 reserved。如果将来有任何用户尝试 使用这些字段标识符,则 proto buf 编译器会报错。

1
2
3
4
message Foo {
rserved 2, 15, 9 to 11;
reserved "foo", "bar";
}

请注意,你不能在同 reversed 一条语句中混用字段名称和字段编号。

你产生了什么 .proto ?

编译器会以你选择的语言生成代码,你将需要使用文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,并从输入流中解析消息。

  • 对于 C++,编译器从每个 .proto 生成一个 .h 和 .cc 文件,并为文件中描述的每种消息类型提供一个类。

  • 对于 Java,编译器将生成一个 .java 文件,其中包含每种消息类型的类以及 Builder 用于创建消息实例的特殊类。

  • Python 稍有不同 - Python 编译器会生成一个模块,其中包含每种消息类型的静态描述符,.proto 然后将该模块与元类一起使用,以在运行时创建必要的 Python 数据访问类。

  • 对于 Go,编译器 .pb.go 将为文件中的每种消息类型生成一个具有相应类型的文件。

  • 对于 Ruby,编译器将 .rb 使用包含你的消息类型的 Ruby 模块生成文件。

默认值

解析消息时,如果编码的消息不包含特定的单数元素,则已解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的。

  • 对于字符串,默认值为空字符串

  • 对于字节,默认值为空字节

  • 对于布尔值,默认值为 false

  • 对于数字类型,默认值为 0

  • 对于枚举,默认值为第一个定义的枚举值,必须为 0

  • 对于消息字段,未设置该字段。它的确切值取决于语言。

重复字段的默认值为空。

请注意,对于标量消息字段,一旦解析了一条消息,就无法告诉该字段是显式设置为默认值(例如,是否将 boolean 设置为 false)还是根本没有设置:你应该牢记这一点定义消息类型时。

枚举

定义消息类型时,你可能希望其字段之一仅具有一个预定义的值列表之一。例如,假设你想为每个 SearchRequest 添加一个 corpus 字段,这个字段可以是 UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS。你可以简单地添加一个枚举类型到 SearchRequest 中。

在如下例子中,我们添加了一个名为 Corpus 的枚举类型,包含了所有可能的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}

正如你所看到的,枚举类型字段 Corpus 的第一个常量是 0,每个枚举类型必须包含一个映射为零的常量作为其第一个元素。这是因为:

  • 必须有一个零值,以便我们可以使用 0 作为数字默认值

  • 零值必须是第一个元素,以便与 proto2 语义兼容,其中第一个枚举值始终是默认值。

你可以通过将相同的值分配给不同枚举常量来定义别名。为此,你需要将 allow_alias 选项设置为 true,否则协议别名会在找到别名时生成一条错误消息。

1
2
3
4
5
6
7
8
9
10
11
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

枚举常量必须在 32 位整数范围内。

保留值

如果通过完全删除枚举条目或将其注释掉来更新枚举类型,则将来的用户在自己对类型进行更新时可以重用数值。如果他们以后加载相同版本的旧版本,可能会导致严重的问题, 包括数据损坏,隐私错误等。确保不会发生这种情况的一种方法是,将删除的条目的数字值指定为 reversed。如果将来有任何用户尝试使用这些标识符, 则 proto buf 编译器会报错,你可以使用 max 关键字指定保留的数值范围达到最大可能值。

1
2
3
4
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}

请注意,你不能在同 reversed 一条语句中混合使用字段名和数字值。

使用其他消息类型

你可以使用其他消息类型作为字段类型。例如,假设你想包括 Result 每个消息的 SearchResponse 消息 - 要做到这一点,你可以定义一个 Result 在同一个消息类型 .proto,然后指定类型的字段为 SearchResponse。

1
2
3
4
5
6
7
8
9
message SearchResponse {
repeated Result results = 1;
}

message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}

导入定义

在上面的示例中,Result 消息类型与以下文件定义在同一文件中 SearchResponse - 如果要用作字段的消息类型已在另一个 .proto 文件中定义,该怎么办?

你可以 .proto 通过导入其他文件来使用它们的定义。要导入另外一个 .proto 的定义,请在文件顶部添加一个 import 语句;

1
import "myproject/other_protos.proro"

默认情况下,你只能使用直接导入 .proto 文件中的定义。但是,有时你可能需要将 .proto 文件移到新位置。

1
2
// new.proto
// All definitions are moved here
1
2
3
4
// old.proto
// This is the proto that all clients are importing
import public "new.proto";
import "other.proto"
1
2
3
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

协议编译器使用 -I/--proto_path 标志在协议编译器命令行上指定的一组目录中搜索导入的文件。

嵌套类型

你可以在其他消息类型内定义和使用消息类型,如以下示例所示 - 在此处,Result 消息是在消息内定义的 SearchResponse:

1
2
3
4
5
6
7
8
message SearcResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}

如果要在其父消息类型之外重用此消息类型,则将其称为:Parent.Type

1
2
3
message SomeOtherMessag {
SearchResponse.Result result = 1;
}

你可以根据深度嵌套消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message Outer {
message MiddleAA {
message Inner {
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB {
message Inner {
int32 ival = 1;
bool booly = 2;
}
}
}

更新消息类型

如果现有的消息类型不再满足你的所有需求,但是你仍然希望使用以旧格式创建的代码,请不要担心!在不破坏任何现有代码的情况下更新消息类型非常简单。只要记住以下规则:

  • 不要更改任何现有字段的字段编号。

  • 如果添加新字段,则仍可以使用新生成的代码来解析使用 "旧" 消息格式通过代码序列化的任何消息。你应该记住这些元素的默认值,以便新代码可以与旧代码生成的消息正确交互。 同样,由新代码创建的消息可以由旧代码解析:旧的二进制文件在解析时只会忽略新字段。

  • 只要在更新的消息类型中不再使用字段号,就可以删除字段。你可能想要重命名该字段,或者添加前缀 "OBSOLETE_",或将字段编号保留,以便将来的用户不会意外地重用该编号。

  • int32,uint32,int64,uint64 和 bool 都是兼容的。

未知字段

未知字段是格式正确的 proto buf 序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件使用新字段解析新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。

最初,proto3 消息在解析过程中总是丢弃未知字段,但是在版本 3.5 中,我们重新引入了保留未知字段以匹配 proto2 行为的功能。

源码

::merge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Merge the collection with the given items.
*
* @param \ArrayAccess|array $items
* @return static
*/
public function merge($items)
{
$dictionary = $this->getDictionary();

foreach ($items as $item) {
$dictionary[$item->getKey()] = $item;
}

return new static(array_values($dictionary));
}

模型集合合并的时候,如果存在相同主键值的模型,那么合并的结果中,只会有相同主键值模型的最后一个。相同主键值的模型会被忽略掉。

不同模型相同表关联的传统查询方式

对于某些场景可能要利用到这个特性。

比如有模型集合 A,里面有关联 a, a.b, a.c,我们需要加载这些关联很简单,按以下的写法写就行。

1
2
3
4
5
6
7
$a = app(A::class)->hydrate(xx);

$a->load([
'a',
'a.b',
'a.c'
]);

现在我们有另外一个模型集合 B,也存在这些关联 a, a.b, a.c,一般情况下,我们会和集合 A 的 load 操作分开。

但是这个时候就产生了重复的查询了。比如上面,就产生了 3 个重复的查询。

去除重复查询

一种方法是,把 A 集合和 B 集合中的 a_id 拿出来合并去数据库查询。但是这样操作起来相对麻烦,查询出来还要手动组装回去。

另外一种方法是,合并两个集合 A 和 B 得到一个新的 Eloquent 集合。然后使用 Eloquent Collection 的 load 方法一次性加载关联数据。这样就可以避免很多重复查询。

1
2
3
4
5
6
7
$c = $a->merge($b);

$c->load([
'a',
'a.b',
'a.c'
]);

为什么行得通?

因为我们操作的是模型,一个个对象,在 load 操作执行的时候会设置对应对象的属性,而新集合持有的对象和原集合的对象是一样的,所以最终的结果就是:集合 A 和集合 B 里面的对象都获取到了关联数据。

这和 merge 有什么关系?

现在有另外一种场景,我们有一个集合 A 和一个 a_id 数组,但是我们也想一次性查询出集合 A 的关联和 a_id 数组的关联数据,避免重复查询。

我的做法是,根据 a_id 数组建立另一个模型 B 的集合,其外键 a_id 的值分别为 a_id 数组里的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$b = collect();

foreach($a_ids as $a_id) {
$model = app(B::class);
$model->a_id = $a_id;
$b->push($model);
}

$c = $a->merge($b);

$c->load([
'a',
'a.b',
'a.c'
]);

我们想要的结果是,也是没有重复查询。但是事与愿违,我们发现产生了一些重复的查询,这是为什么呢?

通过打印 $c,发现里面只有一个 B 模型的对象,其他的都没有了。这就是本文要探讨的问题,因为这些 B 模型对象没有设置主键,其主键都是 null,最终只有一个成功合并进去,即使这些模型的 a_id 属性不一样。

破解之道

现在我们知道了原因,也就不难解决了,因为这个模型 B 其实不是我们需要的数据,我们需要的只是其查询出来的关联数据,我们就给它们不一样的主键值就行了。

1
2
3
4
5
6
7
8
$key = 0;

foreach($a_ids as $a_id) {
$model = app(B::class);
$model->a_id = $a_id;
$mode->{$model->getKeyName()} = $key++; // 设置不一样的主键值
$b->push($model);
}

最后发现,没有了重复的查询。

如何拿出这些关联?

1
2
3
$a = $b->pluck('a');
$ab = $b->pluck('a.b');
$ac = $b->pluck('a.c');

有时候我们操作一个模型的时候,其关联模型已从其他地方查询出来,现在想要给这个模型赋予这个关联,以防止在访问这个关联的时候再去查询数据库。

例子

我们现在有一个查询出来的关联 sku, 现在需要把它赋给 $cart 模型对象,这样我们使用 $cart->sku 来访问 sku 属性, 的时候就不会再去查询数据库了。

我们试试这样

1
2
3
4
5
6
7
8
9
10
11
12
<?php

require_once __DIR__ . '/bootstrap/app.php';

enable_mysql_log();

$cart = app(\Modules\Order\Models\Cart::class); // jenssegers mongodb Model
$sku = app(\Modules\Product\Models\Sku::class)->first();

$cart->sku = $sku;

dump($cart->sku); // null, 这里拿不到 sku,当然这里是因为 cart 没有设置关联键的值,如果有设置的话,会进行数据库查询

这说明实际上,我们在使用 $cart->sku 的时候实际上并不能获取到 $cart->sku = $sku; 这里设置的 sku

源码剖析

::getAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @inheritdoc
*/
public function getAttribute($key)
{
if (!$key) {
return;
}

// Dot notation support.
if (Str::contains($key, '.') && Arr::has($this->attributes, $key)) {
return $this->getAttributeValue($key);
}

// This checks for embedded relation support.
if (method_exists($this, $key) && !method_exists(self::class, $key)) {
return $this->getRelationValue($key);
}

return parent::getAttribute($key);
}

我们获取模型属性的时候会先调用这个方法,我们发现,jenssegers mongodb Model 覆盖了 Laravel Model 的 getAttribute 方法,因为在 Cart 模型里面定义了 sku 关联,因此这里实际上返回的是:

1
return $this->getRelationValue($key);

接下来再看 getRelationValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Get a relationship.
*
* @param string $key
* @return mixed
*/
public function getRelationValue($key)
{
// If the key already exists in the relationships array, it just means the
// relationship has already been loaded, so we'll just return it out of
// here because there is no need to query within the relations twice.
if ($this->relationLoaded($key)) { // 这个条件会不成立
return $this->relations[$key];
}

// If the "attribute" exists as a method on the model, we will just assume
// it is a relationship and will load and return results from the query
// and hydrate the relationship's value on the "relationships" array.
if (method_exists($this, $key)) {
return $this->getRelationshipFromMethod($key);
}
}

在这个方法中 $this->relationLoaded($key) 这个条件不成立,因此导致了实际运行了 $this->getRelationshipFromMethod($key);,这就导致了再次查询数据库。

解决方法

翻看源码,发现这个条件实际上是 array_key_exists($key, $this->relations);,但是很遗憾,我们使用手动设置关联的方式的时候,Cart 模型实际上把 sku 当作一个普通的属性,而不是 relation,因此在 $cartrelations 属性中并没有 sku

唯一的方法就是通过调用 setRelation 来显式设置模型关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Set the specific relationship in the model.
*
* @param string $relation
* @param mixed $value
* @return $this
*/
public function setRelation($relation, $value)
{
$this->relations[$relation] = $value;

return $this;
}

Laravel 的 Model 有没有这个问题

没有。

Laravel 的 Model 会先去模型的 attributes 里查看是否存在对应的属性,最后才看关联有没有这个关联。

Golang 1.11 推出了 modules 机制来进行依赖管理。

modules 简单使用方式

在 1.12 版本之前,使用 Go modules 之前需要环境变量 GO111MODULE:

  • GO111MODULE=off: 不使用 modules 功能

  • GO111MODULE=on: 使用 modules 功能,不会去 GOPATH 下面查找依赖包

  • GO111MODULE=auto: Golang 自己检测是不是使用 modules 功能

在 GOPATH 之外创建一个项目 mod-demo,包含一个 main.go,内容如下:

1
2
3
4
5
6
7
8
9
package main

import (
"github.com/astaxie/beego"
)

func main() {
beego.Run()
}

初始化

初始化很简单,在项目根目录执行命令 go mod init mod-demo,然后会生成一个 go.mod 文件如下。

1
2
3
4
5
6
7
8
➜ mod-demo $ go mod init mod-demo
go: creating new go.mod: module .
➜ mod-demo $ ls
go.mod main.go
➜ mod-demo $ cat go.mod
module .

go 1.12

这里比较关键的就是这个 go.mod 文件,这个文件标识了我们的项目的依赖的 package 的版本。执行 init 暂时 还没有将所有的依赖管理起来。我们需要将程序 run 起来(比如执行 go run/test),或者 build(执行命令 go build)的时候,才会触发依赖的解析。

比如使用 go run 即可触发 modules 工作。

1
2
3
➜ mod-demo $ go run main.go
go: extracting github.com/astaxie/beego v1.12.0
2019/09/08 23:23:03.507 [I] http server Running on http://:8080

这个时候我们再查看 go.mod 文件:

1
2
3
4
5
6
7
8
9
➜ mod-demo $ catgo.mod
module mod-demo

go 1.12

require (
github.com/astaxie/beego v1.12.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

同时我们发现项目目录下多了一个 go.sum 用来记录每个 package 的版本和哈希值。go.mod 文件正常情况会 包含 module 和 require 模块,初次之外还可以包含 replace 和 exclude 模块。

这些 package 并不是直接存储到 $GOPATH/src,而是存储到 $GOPATH/pkg/mod 下面,不同版本并存的方式。

1
2
➜ mod-demo $ ls $GOPATH/pkg/mod/github.com/astaxie
beego@v1.11.0 beego@v1.11.1 beego@v1.12.0

依赖升级(降级)

可以使用如下命令来查看当前项目依赖的所有的包。

1
go list -m -u all

如果我想升级(降级)某个 package 则只需要 go get 即可,比如:

1
go get package@version

需要注意的是,在 modules 模式开启和关闭的情况下,go get 的使用方式不是完全相同的。在 modules 模式 开启的情况下,可以通过在 package 后面添加 @version 来表名要升级(降级)到某个版本。如果没有指明 version 的情况下,则默认先下载打了 tag 的 release 版本,比如 v0.4.5 或者 v1.2.3;如果没有 release 版本,则 下载最新的 pre release 版本,比如 v0.0.1-pre1。如果还没有则下载最新的 commit。这个地方给我们的 一个启示是如果我们不按规范来命名我们的 package 的 tag,则 modules 是无法管理的。version 的格式 为 v(major).(minor).(patch),更多信息可以参考: https://semver.org/。

比如我们现在想将我们依赖中的 beego 项目的版本改为 v1.11.1,则可以像如下操作。我们发现执行完 go get 之后,go.mod 中的项目的版本也相应的改变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜ mod-demo $ go get github.com/astaxie/beego@v1.11.1
go: finding github.com/astaxie/beego v1.11.1
go: downloading github.com/astaxie/beego v1.11.1
go: extracting github.com/astaxie/beego v1.11.1
➜ mod-demo $ cat go.mod
module .

go 1.12

require (
github.com/OwnLocal/goes v1.0.0 // indirect
github.com/astaxie/beego v1.11.1 // indirect
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

在 modules 开启的模式下,go get 还支持 version 模糊查询,比如 >v1.0.0 表示大于 v1.0.0 的可用版本; <v1.12.0 表示小于 v1.12.0 版本下最近可用的版本。version 的比较规则按照 version 的各个字段来展开。

除了指定版本,我们还可以使用如下命名使用最近的可行的版本:

  • go get -u 使用最新的 minor 或者 patch 版本

  • go get -u=patch 使用最新的 patch 版本

vendor

我们知道 Go 1.5 推出了 vendor 机制,go mod 也可以支持 vendor 机制,将依赖包拷贝到 vendor 目录。 但是像一些 test case 里面的依赖包并不会拷贝到 vendor 目录中。

1
go mod vendor

modules 特性

GoProxy

有些 Golang 的 package 在国内是无法直接 go get 的。在之前,我们解决这个问题,一般是通过设置 http_proxy/https_proxy 来解决。GoProxy 相当于官方提供了一种 proxy 的方式让用户来进行包下载。 要使用 GoProxy 只需要设置环境变量 GOPROXY 即可。目前公开的 GOPROXY 有:

  • goproxy.io

  • goproxy.cn

值得注意的是,在最新 release 的 Go 1.13 版本中默认将 GOPROXY 设置为 https://proxy.golang.org,这个对于国内的开发者是无法直接使用的。所以如果升级了 Go 1.13 版本一定要把 GOPROXY 手动改掉。

Replace

replace 主要是为了解决某些包发生改名的问题。

对于另外一种场景有时候也是有用的,比如对于有些 golang.org/x/ 下面的包由于某些历史原因在国内是下载不了的,但是对应的包在 github 上面是有一份拷贝的,这个时候我们就可以将 go.mod 中的包进行 replace 操作。

下面是一个 Beego 项目的 go.mod 的 replace 的示例:

1
2
3
4
replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85

replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d

SubCommand

1
go help mod
  • download: 下载 modules 到本地缓存

  • edit: 提供一种命令行交互修改 go.mod 的方式

  • graph: 将 module 的依赖图在命令行打印出来,其实并不是很直观

  • init: 初始化 modules,会生成一个 go.mod 文件

  • tidy: 清理 go.mod 中的依赖,会添加缺失的依赖,同时移除没有用到的依赖

  • vendor: 将依赖打包拷贝到项目的 vendor 目录下,值得注意的是并不会将 test code 中的依赖打包到 vendor 中。

  • verify: verify 用来检测依赖包自下载之后是否被改动过。

  • why: 解释为什么 package 或者 module 是需要的,但是看上去解释的理由并不是很直观。

下载源码(下面指定了版本)

1
2
3
4
5
sudo rm -rf /tmp/mongo-php-driver /usr/src/mongo-php-driver
git clone -c advice.detachedHead=false -b '1.6.1' --single-branch https://github.com/mongodb/mongo-php-driver.git /tmp/mongo-php-driver
sudo mv /tmp/mongo-php-driver /usr/src/mongo-php-driver
cd /usr/src/mongo-php-driver
git submodule update --init

编译安装

1
phpize && ./configure && make && make install