Are you thinking of moving from PHP to Go? Read this first!

Are you thinking of moving from PHP to Go? Read this first!

Are you a PHP developer contemplating a switch to Go? You're not alone. As technology evolves, many developers find themselves reevaluating their choice of programming languages to keep up with industry trends, improve performance, and enhance their skill sets. PHP has been a reliable workhorse for web development for decades, powering a vast number of websites and applications. However, with the rise of Go, known for its simplicity, efficiency, and powerful concurrency features, it’s no wonder you might be curious about making the transition.

In this post, we'll dive deep into what it means to move from PHP to Go. We'll explore the key differences between these two languages, and provide practical steps to help you navigate this journey. So, before you take the plunge, read on to discover everything you need to know about transitioning from PHP to Go.

History of Go

Go, also known as Golang, is a statically typed, compiled programming language designed by Google for simplicity and efficiency.

  • Origins:

    • Developed by Google: Created by Robert Griesemer, Rob Pike, and Ken Thompson.

    • First Released: Publicly announced in 2009, with version 1.0 released in March 2012.

  • Design Philosophy:

    • Simplicity and Clarity: Designed to be easy to understand, avoiding unnecessary complexity.

    • Efficiency: Emphasizes fast compilation and execution.

    • Concurrency: Features goroutines and channels for efficient concurrent task management.

  • Key Milestones:

    • 2015: Go 1.5 introduced a major runtime overhaul and self-hosted compiler toolchain.

    • 2017: Go 1.9 added type aliases and improved concurrent map access.

    • 2021: Go 1.17 brought performance enhancements and new module features.

Go continues to evolve, supported by an active community and driven by the need for efficient, scalable, and maintainable software.

Key differences between Go and PHP

When to Use Go

Go is ideal for high-performance, concurrent, and scalable applications such as microservices, real-time systems, and distributed systems. Its efficient handling of concurrent processes, faster execution times, and maintainable code make it suitable for backend services, network servers, and command-line tools.

When to Use PHP

PHP is perfect for server-side scripting and building dynamic websites and CMS-driven projects. Its ease of use, extensive documentation, and large community support make it ideal for beginners and small to medium-sized projects.

Side-by-side comparison

PHPGo
Executioninterpreted, runtime is neededcompiled, self-contained
Concurrencyshared nothing architectureconcurrency is the default
Error handlingexceptions, mixed returns, errorserror struct returned, very verbose
Memory managementgarbage collectedgarbage collected
Testing3rd party librariespart of the standard library
Benchmarking3rd party librariespart of the standard library
Frameworkslarge ecosystemfewer frameworks but steadily growing
Return valuesone or no value returnedno void type, multiple return values are normal, often accompanied by an “error” type
Code formattingfreestyle, automation via 3rd party libraries, or IDE, no synchronization across application and vendor codehard-baked into the language, the base format is mandatory for the code to compile
Package managementvia 3rd party tools: composer/packagistbuilt in the language core

Go basics

💡
Here are the important basics, but there are more. Check out the linked learning resources for all the details.

Variable declaration for primitives

 // standard declaration
var sum int

// type inferred
var a = "a string"

// multiple variables at once
var b, c int64 = 1, 2
var (
    d = 42
    e = "another string"
)

// shorthand
f := "apple"

Arrays, Slices and Maps

Arrays

// template
var arrayName [size]Type

// example
var num [3]int

// declaration and initialization
numbers := [5]int{1, 2, 3}
fmt.Println(numbers) // [1 2 3 0 0]
  • fixed capacity and order

  • keys are 0 indexed integers without gaps

  • passed by reference

Slices

// template
var sliceName []Type

// example
var ages []int

// explicit declaration
threeValues := make([]int, 3, 10) // 3 is the length, 10 is the capacity

// shorthand declaration
numbers := []int{1, 2, 3}
fmt.Println(numbers) // [1 2 3]
  • size is dynamic

  • order is fixed

  • slice is a reference to an underlying array

  • more flexibility and convenience in comparison to arrays

Maps

// template
var mapName map[KeyType]ValueType

// explicit declaration
ages := make(map[string]int)

// shorthand declaration
ages := map[string]int{
    "alice": 27,
    "bob":   28,
}
  • grows and shrinks dynamically

  • backed by hash-map

  • passed by reference

  • random order when iterating over

  • cannot be compared with the equality operator

Structs

type BankAccount struct {
    IBAN     int    
    password string    
}

a := BankAccount{}
b := BankAccount{}

fmt.Println(a == b) // true
fmt.Println(&a == &b) // false
  • structs are not objects you know in php

  • just a collection of properties to represent the state

  • there is no parent/child relationship

  • structs are composed (composition over inheritance)

  • structs are compared by comparing each property

Visibility

type BankAccount struct {
    IBAN     int    // exported (public)
    password string    // unexported (private)
}
  • No extra keywords for public and private

  • Visibility is defined by the lower and upper casing of the property name

  • The same applies to variables, constants, methods, and types

Struct methods

type BankAccount struct {
    IBAN     int    
    password string    
}

func (b BankAccount) HasLongPassword() bool {
    return len(b.password) > 12
}

b := BankAccount{
    password: "Lorem ipsum dolor sit amet",
}
fmt.Println(b.HasLongPassword()) // true

Interfaces

type error interface {
    Error() string
}

type Reader interface {
    Read(p []byte) (n int, err error) // read into a buffer
}
type Writer interface {
    Write(p []byte) (n int, err error) // write into a buffer
}
  • interfaces are implemented implicitly

  • no "implements" keyword

  • this decouples the definition of an interface from its implementation

  • typically very small (1-2 methods)

Iterating

data := []int{3, 2, 1}
var i int

// initialization ; end condition ; post loop action
for i = 0; i < len(data); i++ {
    fmt.Printf("loop 1: key %d has value %d\n", i, data[i])
}

i = 0
// similar to while(condition === true)
for i < len(data) {
    fmt.Printf("loop 2: key %d has value %d\n", i, data[i])
    i++
}

//via range keyword
data = []int{3, 2, 1}

for k, v := range data {
    fmt.Printf("key %d has value %d\n", k, v)
}

Error handling

func Copy(dst Writer, src Reader) (written int64, err error) {
    ...
}

func PipeStdinToStdout() (int, error) {
    bytesWritten, err := io.Copy(os.Stdout, os.Stdin)
    if err != nil {
        return 0, fmt.Errorf("streaming stdin to stdout failed: %v", err)
    }

    return int(bytesWritten), nil
}
  • doesn’t have exceptions

  • doesn’t “throw” errors/warnings

  • doesn’t have mixed returns

  • but has multiple returns instead

  • errors are explicitly handled and
    passed to the caller

Go routines/concurrency

  • prefix any function call with the go keyword to

  • run it in a non-blocking fashion

  • no promises, no async-await, no callbacks

    → code looks and feels synchronous

  • concurrency needs to be controlled

Wait groups

wg := &sync.WaitGroup{}

for range data {
    wg.Add(1) // add a "unit of work"
    go func() {
       dowork()
       wg.Done() // signal one unit of work is done
    }()
}

wg.Wait() // block until all goroutines are finished
  • allows you to wait for a collection of goroutines (concurrent functions) to finish

  • internally implemented as a counter

  • when synchronization needs to happen, Wait() must be invoked

Channels

radio := make(chan string)
send := func(ch chan string, msg string) {
    ch <- msg 
}

go send(radio, "hello world") // non blocking

fmt.Println(<-radio) // blocking
  • channels enable communication between go routines

  • communication happens when a sender and a receiver are present at the same time

  • sending and receiving is a blocking operation

Write & execute your first "Hello World"

Install Go

Follow the required steps for your OS here: https://go.dev/doc/install.

Programm code "Hello World"

Create a file called main.go in a folder of your choice and past the following content.

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World")
}

Running and compiling your program

go run main.go
> Hello World

go build -o godemo main.go
./godemo
> Hello World

file godemo
> godemo: Mach-O 64-bit executable arm64

GOOS=windows GOARCH=amd64 go build -o godemo.exe main.go
file godemo.exe
> godemo.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
💡
With the environment variables GOOS and GOARCH you can compile your Go program into any OS.

Learning resources

Summary

In summary, Go and PHP each have their strengths and ideal use cases. Go excels in performance, concurrency, and scalability, making it perfect for complex, high-demand applications. PHP, with its ease of use and strong community support, is well-suited for web development and content management systems. If you’re considering a switch from PHP to Go, carefully evaluate your project requirements and team capabilities. Ready to dive deeper into Go? Explore our recommended resources and start experimenting with Go today to experience its benefits firsthand. Share your journey or ask questions in the comments below!