From PHP to Go: PHP2Go

Child with a red helmet on a blue trike
Image credits: Christian ter Maat, via Unsplash

For advanced PHP developers, stepping into Go is often more attractive than Node.JS. But the Go library seems to be missing so much useful stuff found in the PHP library. How can one switch faster ?

Migrating away from PHP

Converting one’s development practice from PHP to Go is not an obvious rewrite with “just” a new syntax : more often than not, it will imply completely rethinking the organization of one’s programs, when realizing that many things held as obvious were really a consequence of the former language runtime types and organization optimized for minimal parsing (think autoloading with PSR-4, or the myriad of different tasks served by PHP so-called arrays). When porting code to Go, all of these PHP underpinnings upon which code depends need to be replaced, making the first conversions harder to deliver.

This is where PHP2Go comes in.

The PHP2Go library

To ease these first migrations and allow one to deliver code fast even when just starting, the github.com/syyongx/php2go has been available since Go 1.10, and offers over 140 functions designed as Go equivalents for the PHP functions by the same name.

It even includes a Ternary function to convert PHP ?: ternary expressions, which Go chose to exclude. Here is how it is used, starting with the replaced PHP code:

1
2
3
4
5
6
7
8
9
<?php
// PHP Version
// https://3v4l.org/4ZpV6

$pwd = getcwd();
$root = strlen($pwd) === 1
  ? "Root"
  : "Elsewhere";
echo $root;

As the Go conversion below shows, all it takes is importing — possibly using a dot import — the package, and use the PHP2Go functions instead of the original. Because exported identifiers in Go need to start by an uppercase letter, the conversion implies uppercasing the first letter of PHP functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Go Version with php2go
// https://play.golang.org/p/Ob_pgHw92t-

package main

import (
	"fmt"

	. "github.com/syyongx/php2go"
)

func main() {
	// Returns an error, unlike PHP
	pwd, _ := Getcwd()

	// Argument and result are interface{} values...
	root := Ternary(Strlen(pwd) == 1,
		"Root",
		"Elsewhere",
	)

	// ...so type assertions may be needed...
	println(root)          // (0x134fa0,0x19af18)
	println(root.(string)) // Root

	// ...although not always.
	fmt.Println(root) // Root
}

Limitations

Because the languages differ so much, some limitations need to be accounted for, as this example shows :

  • The function signatures may differ: in this fragment Getcwd() honors the specifics of its underlying Go implementation os.Getwd(), returning an error value along with the result, instead of keeping the exact PHP signature, to stay closer to the Go style error handling. Don’t just ignore them as is done in this tiny demo.
  • Because PHP runtime functions are typically loosely types, their Go counterparts rely on interface{} values to simulate the dynamic typing available in PHP. This causes the result of some functions like Ternary not to be immediately usable in some cases, requiring a v.(<type>) type assertion as in the example ; or a switch v.(type) type switch when the result type is not known in advance.

As the example show, the resulting code is more verbose and not idiomatic, and as such will look alien to most Go developers. To complete the comparison, here is a native Go version for the same tiny example: it is not really less simple, and any gopher will be familiar with it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Native Go version
// https://play.golang.org/p/w3DcSD5RXNw

package main

import "os"

func main() {
	pwd, _ := os.Getwd()

	var root string
	if len(pwd) == 1 {
		root = "Root"
	} else {
		root = "Elsewhere"
	}

	println(root) // Root
}

Practical use

Some functions are significantly more useful, especially those operating the myriad operations on PHP arrays, which have not direct equivalent in Go, and will often be the most complex part of any conversion from PHP to Go.

The php2go package includes many of these, taking []interface{} arguments:

PHP 7.4php2go v0.9.4
array_change_case
array_chunkArrayChunk
array_columnArrayColumn
array_combineArrayCombine
array_count_valuesArrayFill
array_diff_assoc
array_diff_key
array_diff_uassoc
array_diff_ukey
array_diff
array_fill_keys
array_fill
array_filter
array_flipArrayFlip
array_intersect_assoc
array_intersect_key
array_intersect_uassoc
array_intersect_ukey
array_intersect
array_key_existsArrayKeyExists
array_key_first
array_key_last
array_keysArrayKeys
array_map
array_merge_recursive
array_mergeArrayMerge
array_multisort
array_padArrayPad
array_popArrayPop
array_product
array_pushArrayPush
array_randArrayRand
array_reduce
array_replace_recursive
array_replace
array_reverseArrayReverse
array_search
array_shiftArrayShift
array_sliceArraySlice
array_splice
array_sumArrayUnshift
array_udiff_assoc
array_udiff_uassoc
array_udiff
array_uintersect_assoc
array_uintersect_uassoc
array_uintersect
array_unique
array_unshift
array_valuesArrayValues
array_walk_recursive
array_walk

Conclusion

The php2go module is a useful tool allowing experimented PHP developers who are just beginning Go to deliver their first projects in record time, making the switch from one technology to the other easier on management.

For most needs, though, it shows how difficult moving from one language to another without changing work habits can be:

  • PHP is designed on top of dynamic typing and its “anything goes” arrays, which provide both arrays, maps, queues, and stacks, and preserve the insertion order when used as maps, whereas nothing equivalent exists in the Go runtime.
  • Go is statically typed, so converting a PHP project to Go will most often mean entirely rethinking the data model instead of attempting to shortcut the needed redesign by using a porting library like php2go.

In the end, someday, the technical debt needs to be repaid, so better do it as soon as possible.