PHP转GO

最近在转语言,想从一名 PHPer 转成 Gopher,感觉我长时间搞 PHP,很多思想转不过来(究其原因还是太菜了) 今天就说说两者之间相似之处,亦或是不同的地方,说的对与不对完全是自己的理解 那么首先从依赖管理说起吧,我们知道 PHP 的依赖管理是 Composer。而 Go 目前大致差不多是 go mod 了,至于以前的 vendor 或者其他就不学也罢吧。 这里仅仅是说下两者的区别,至于更多高级用法,还是自己去看各自的依赖管理吧,这两个东西,拿出来讲 我觉得内容也不少,也是一个很重要的部分。有时间的话就再写一篇文章出来….

依赖管理

安装

在 PHP 中我们安装一个组件是 composer require xxx/xxx , 例如我们安装 超哥的 EasyWecaht

composer require w7corp/easywechat

而在 Go 中 我们使用 go get -u xxx, 例如安装 GORM

go get gorm.io/gorm

更新

使用第三方包,假设需要更新,在 PHP 中我们可以使用 composer update xxx

composer update w7corp/easywechat

Go 的话还是 go get 指定一个 -u 参数即可

go get -u gorm.io/gorm

理论上更新的话需要慎重一些,下版本更新无所谓

清理缓存

在装这些依赖包的时候有时候会遇到各种稀奇古怪的问题,怎么办呢,问题有很多,但是很多时候清除一下缓存就好使了。 在 PHP 中 清除 composer 缓存,使用 clear-cache

composer clear-cache

Go 的话

go clean --modcache

自动加载更新依赖

PHP

composer dump-autoload

Go go mod tidy 会自动更新当前 module 的依赖关系

go mod tidy

代理、镜像

众所周知,我们在安装这些依赖的时候很大原因安装不上就是因为有堵墙。所以需要配置镜像,以便于加速下载 PHP 设置镜像,目前最好用的无非就是阿里云了,虽然不定时抽风。另外还有 华为、腾讯、安畅等镜像,这些我只有在阿里云抽风的情况下做个备选方案。

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

Go 也有一些镜像,比如七牛、阿里等

启用 Go Modules 功能

go env -w GO111MODULE=on

配置 GOPROXY 环境变量,以下三选一

#七牛 CDN
go env -w  GOPROXY=https://goproxy.cn,direct
#阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
#官方
go env -w  GOPROXY=https://goproxy.io,direct

数组

在 PHP 中 数组即 Array,可以存储几乎任意类型,即使他们直接数据类型都不相同也无妨 例如:

$array = [1,2,3,4,5,'A','B','c',"hello 你好",34.56,true];
print_r($array);

而在 Go 语言中数组则有很大的不同,首先必须申明数组长度、其次必须指明类型,只能赋值实现申明好的类型。例如:

arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)

而如果你多添加了元素这在 Go 中是不允许的。以下方式不允许,且编译都不会通过

arr := [5]int{1, 2, 3, 4, 5, 6}
fmt.Println(arr)
// array index 5 out of bounds [0:5]

如果给定的元素不够事先申明的长度,那么默认会是当前类型的零值,例如 int 的零值是 0

arr := [5]int{1, 2, 3, 4}
fmt.Println(arr)
// [1 2 3 4 0]

当然如果你赋值的是其他类型 ,这在 Go 中也是行不通的

arr := [5]int{1, 2, "string", 3, 4}
fmt.Println(arr)
// cannot use "string" (type untyped string) as type int in array literal

在 Go 这一强类型中,必须事先申明类型,那么申明了类型就代表以后只允许这一类型的赋值。其他类型赋值则不允许。如果事先没申明 那么采用简短申明 ,Go 则会进行类型推断,同理也是 后续也只能赋值这一种类型

值类型 & 引用类型

在 PHP 中值类型包括:bool、int、string、float、以及 复杂数据类型 array 都是值类型 PHP 的对象则为引用类型: 例如:

<?php

class Person {
    public $name;

    public function getName()
    {
        return $this->name;
    }

    public function setName($name): void
    {
        $this->name = $name;
    }
}

$p1 = new Person();
$p1->setName("php");

$p2 = $p1;
$p2->setName("go");
var_dump($p1);
var_dump($p2);

var_dump($p1 === $p2);

可以猜想一下以上内容会输出啥?

object(Person)#1 (1) {
  ["name"]=>
  string(2) "go"
}
object(Person)#1 (1) {
  ["name"]=>
  string(2) "go"
}
bool(true)

其实如果用 PHPStorm 最后的 $p1 === $p2 已经给出警告了,Condition is always ’true’ because ‘$p2’ is evaluated at this point 那么,如何解决此类型的问题呢?如果想得到对象的一个副本(将复制旧变量的所有属性), 在 PHP 中我们可以使用 clone

<?php

class Person {
    public $name;

    public function getName()
    {
        return $this->name;
    }

    public function setName($name): void
    {
        $this->name = $name;
    }
}

$p1 = new Person();
$p1->setName("php");

$p2 = clone $p1;
$p2->setName("go");
var_dump($p1);
var_dump($p2);

var_dump($p1 === $p2);

同样的内容,我们把 $p2 = $p1; 改成 $p2 = clone $p1; 即可解决此类问题,输出会变成以下内容

object(Person)#1 (1) {
  ["name"]=>
  string(3) "php"
}
object(Person)#2 (1) {
  ["name"]=>
  string(2) "go"
}
bool(false)

Go 语言值类型包括 int 系列、float 系列、bool、string、数组、结构体(struct) 引用类型包括:指针、slice 切片、map、管道 chan、interface 上面给出了,Go 语言值类型、引用类型,其实在 Go 语言面向对象的概念微乎其微,但是我们可以采用 struct 结构体来对标 PHP 中的 class

package main

import "fmt"

type Person struct {
    Name string
}

func main() {
    p1 := Person{}
    p1.Name = "John"
    p2 := p1
    p2.Name = "Jane"
    fmt.Printf("p1:%+v p2:%+v\n", p1, p2)
}

猜想一下 以上内容会出啥?

p1:{Name:John} p2:{Name:Jane}

如果将它 变成引用类型怎么办呢?答案是使用指针。

package main

import "fmt"

type Person struct {
    Name string
}

func main() {
    p1 := Person{}
    p1.Name = "John"
    p2 := &p1
    p2.Name = "Jane"
    fmt.Printf("p1:%+v p2:%+v\n", p1, p2)
}

只更改了 p2 := p1 为 p2 := &p1, 加了一个 & 符号,那么就会输出如下内容

p1:{Name:Jane} p2:&{Name:Jane}

把上面的例子简单改造一下,改造成否和面向对象的方式,比如我们更改 Name 只能通过 setName,获取 Name 是 getName

package main

import "fmt"

type Person struct {
    Name string
}

func (p Person) getName() string {
    return p.Name
}
func (p *Person) setName(name string) {
    p.Name = name
}

func main() {
    p1 := Person{}
    p1.setName("John")

    p2 := p1
    p2.setName("Jane")
    fmt.Println(p1.getName())
    fmt.Println(p2.getName())
}

同理如果 将 p1 赋值 给 p2 还是使用指针 p2 := &p1

接口 Interface

PHP 如果实现接口则需要定义一个 接口 类,然后子类中使用关键字 implements 来实现该接口的 所有方法。注意是 所有,例如我们有一个接口,暂定为 A,该接口规定了两个方法,foo() 以及 bar()

interface A{
    public function foo($foo);
    public function bar($bar);
}

继续用上面的 Person 类来实现这个接口

class Person implements A {

    public $name;
    public function getName()
    {
        return $this->name;
    }
    public function setName($name): void
    {
        $this->name = $name;
    }
}

而如果我们使用了 implements A ,但是并没有实现其中的两个方法,则在运行的时候 PHP 会报出致命性错误

PHP Fatal error: Class Person contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (A::foo, A::bar) Fatal error: Class Person contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (A::foo, A::bar)

可以看到必须同时实现两个方法

<?php

interface A{
    public function foo($foo);
    public function bar($bar);
}

class Person implements A {

    public $name;
    public function getName()
    {
        return $this->name;
    }
    public function setName($name): void
    {
        $this->name = $name;
    }
    public function foo($foo)
    {
        // TODO: Implement foo() method.
        return $foo;
    }
    public function bar($bar)
    {
        // TODO: Implement bar() method.
        return $bar;
    }
}

$p1 = new Person();
$p1->setName("php");
echo $p1->foo("php"),$p1->bar(123);

这两个方法接收任意类型的数据类型,但是在 Go 中必须指明类型,但是 PHP 7 ,我们同样可以强制参数为具体的类型,例如 foo(int $foo) , 表示只允许接收 int 类型,同样如果给定的不是 int 运行时则会报错 而 在 Go 语言中 则不需要 使用什么 implements 等关键字了,在 Go 语言中接口是隐式的,对于一个接口类型,我们并不需要知道它实现了哪个接口,只要它实现了接口中的所有方法即可

package main

import "fmt"

type A interface {
    foo(foo string) string
    bar(bar int) int
}
type Person struct {
    Name string
}

func (p Person) foo(foo string) string {
    return fmt.Sprintf("%s %s", foo, p.Name)
}

func (p Person) bar(bar int) int {
    return bar + 1
}

func main() {

    var a A
    a = Person{"Bob"}
    fmt.Println(a.foo("Hello"))
    fmt.Println(a.bar(1))
}

结构体初始化

PHP 有些钩子函数,例如:__construct 用与初始化

<?php

class Person{

    public $name;

    public function __construct($name)
    {
        $this->name = $name;
    }
}

$p1 = new Person("John");
var_dump($p1);

在 Go 则没有这类方法,大家差不多都是定义一个 func 函数,函数名为 NewXXX 开头。例如:

package main

import "fmt"

type Person struct {
    Name string
}

func NewPerson(name string) *Person {
    return &Person{Name: name}
}

func main() {
  person := NewPerson("John")
  fmt.Println(person.Name)
}

异常处理

PHP 中处理异常我们可以采用 try catch 进行捕获

<?php

class Test{

    public function inverse($a,$b) {
        if (!$b) {
            throw new Exception('Division by zero.');
        }
        return $a /$b;
    }
}

$t = new Test();
try {
    var_dump($t->inverse(4, 5));
    var_dump($t->inverse(1, 0));
} catch (Exception $e) {
    var_dump($e->getMessage());
}
var_dump("hello");

则会输出

float(0.8)
string(17) "Division by zero."
string(5) "hello"

可以看到,$t->inverse (1, 0) ,我们捕获了异常,输出了错误,但是并不影响后续的执行 但是在 Go 中并没有异常处理的方式。错误只能我们进行手动处理,大概可以看到 满屏的 if err != nil 例如:

package main

import "fmt"

func inverse(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("b cannot be zero")
    }

    return a / b, nil
}

func main() {
    i, err := inverse(6, 3)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
    fmt.Println(i)
}

还有就是 我们的程序可能引发 panic, 一旦遇到 panic,我们的程序可能就会立马终止了,这时我们可以使用 defer + recover() 进行捕获。

func inverse(a, b int) (int, error) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Panic recover, Err =", err)
        }
    }()
    return a / b, nil
}

func main() {
    fmt.Println(inverse(10, 5))
    fmt.Println(inverse(10, 0))
    fmt.Println(inverse(10, 2))
    fmt.Println("Over")
}

输出的内容如下:

2 <nil>
Panic recover, Err = runtime error: integer divide by zero
0 <nil>
5 <nil>
Over

JSON 处理

在 PHP 中几乎所有的东西 都是 array 一把锁,md 在 Go 中又分数组、切片、map 等等….. 在 PHP 中 array 和 json 相互转换是一件非常 easy 的事,我们可以使用 json_encode 以及 json_decode 例如 将数组转为 json

<?php

$array = ["name" => "hedeqiang","age"=>18];
$json = json_encode($array);
var_dump($json);     // string(29) "{"name":"hedeqiang","age":18}"

将 json 转为 数组

<?php

$array = ["name" => "hedeqiang","age"=>18];
$json = json_encode($array);
var_dump(json_decode($json,true));

即可转为数组:

array(2) {
  ["name"]=>
  string(9) "hedeqiang"
  ["age"]=>
  int(18)
}

转为对象呢,将 json_decode 第二个参数设置为 true

<?php

$array = ["name" => "hedeqiang","age"=>18];
$json = json_encode($array);
var_dump(json_decode($json));

即可转为对象:

object(stdClass)#1 (2) {
  ["name"]=>
  string(9) "hedeqiang"
  ["age"]=>
  int(18)
}

而在 Go 中似乎就没这么方便了,需要配合 struct 再结合 json 包的 Marshal 以及 Unmarshal 来进行处理 结构体序列化成 json:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    p := Person{Name: "John", Age: 30}
    b, _ := json.Marshal(p)
    fmt.Println(string(b))
}

json 转化为 结构体

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    jsonString := `{"name":"hedeqiang","age":18}`
    var person Person
    err := json.Unmarshal([]byte(jsonString), &person)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%+v\n", person)
}

当然 json 包很强大,不仅局限于 结构体 好了 ,这次就先到这吧,感觉还有很多,等着有时间再介绍吧……(不知道下次啥时候..)