最近在转语言,想从一名 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 包很强大,不仅局限于 结构体 好了 ,这次就先到这吧,感觉还有很多,等着有时间再介绍吧……(不知道下次啥时候..)