How to Speak Go When You Think in PHP
A practical translation guide for PHP devs learning to speak fluent Go.
At first glance, Go feels deceptively simple. There are fewer language features, less magic, and almost no opinionated structure. But once you start actually building with it - especially coming from the dynamic and highly abstracted world of PHP - it becomes clear that Go forces you to unlearn quite a few habits.
I’ve caught myself trying to reach for things that don’t exist in Go: default parameters, associative arrays that double as everything, magic methods, and even basic things like string interpolation. The first few days felt like landing in a new country where the signs look familiar, but no one speaks your language. That’s when I realised: I don’t just need documentation - I need translation.
So I started putting together a side-by-side reference to make sense of it all. It’s not just for me - if you’re a PHP developer stepping into Go, this guide should save you hours of head-scratching and Stack Overflow diving. Think of it like a travel phrasebook: "How do I say foreach
in Go?" "How do I define a class?" "Wait - there are no classes?"
Here’s the full breakdown, complete with examples, patterns, and those sneaky gotchas that’ll trip you up if you’re not looking.
1. Dependency Management
PHP:
Composer with
composer.json
.
Go:
Go modules with
go.mod
andgo.sum
.Commands:
go mod init yourmodule
go get some/package
CopyEdit
go mod init yourmodule go get some/package
2. Structs vs Classes
Go doesn’t have classes. struct
is your data container. Think of it as the Go equivalent of a simple class without the OOP overhead.
PHP:
class User {
public string $name;
public int $age;
}
Go:
type User struct {
Name string
Age int
}
3. Methods on Structs
Go methods are defined outside the struct, and the (u *User)
bit is the receiver — it attaches the method to the struct.
PHP:
class User {
public string $name;
public function sayHello(): void {
echo "Hi, I'm $this->name\n";
}
}
Go:
func (u *User) SayHello() {
fmt.Println("Hi, I'm", u.Name)
}
4. Interfaces and Implementation
In Go, you don’t declare that something implements an interface. If it has the right methods, it automatically satisfies the interface.
PHP:
interface Greeter {
public function sayHello(): void;
}
class User implements Greeter {
public function sayHello(): void {
echo "Hello";
}
}
Go:
type Greeter interface {
SayHello()
}
type User struct {
Name string
}
func (u *User) SayHello() {
fmt.Println("Hello")
}
5. Constructors
Go doesn’t have constructors built in. You just write a function that returns a pointer to your struct.
PHP:
class User {
public function __construct(public string $name) {}
}
Go:
func NewUser(name string) *User {
return &User{Name: name}
}
6. Inheritance vs Embedding
Go doesn’t support inheritance. You compose using embedding. The embedded User
struct gives Admin
access to its fields and methods directly.
PHP:
class Admin extends User {
public string $role;
}
Go:
type Admin struct {
User
Role string
}
7. Visibility: Capital Letters Matter
In Go, capitalised names are exported (public). Lowercase names are package-private. No keywords like public
or private
.
PHP:
class User {
private string $password;
public string $name;
}
Go:
type User struct {
name string // private
Password string // public
}
8. Error Handling
Go doesn’t do exceptions. You return error
values and check them manually. It’s explicit, repetitive, and intentional.
PHP:
try {
riskyFunction();
} catch (Exception $e) {
echo $e->getMessage();
}
Go:
if err := riskyFunction(); err != nil {
fmt.Println(err)
}
9. Async: Goroutines vs PHP's Limitations
PHP:
You’re reaching for tools like Swoole, ReactPHP, or background queues for concurrency.
Go:
go doSomething()
Just slap go
in front of a function call and boom — it's async. For coordination, Go uses channels:
ch := make(chan string)
go func() {
ch <- "hello"
}()
fmt.Println(<-ch)
10. Project Structure
PHP devs are used to src/
, vendor/
, public/
, etc.
In Go:
Typically flat or grouped by domain, not MVC.
Emphasise package-level organisation, not class-level.
/cmd
/internal
/pkg
11. Arrays vs Slices vs Maps
Arrays are fixed-size in Go. Most of the time, you’ll use slices. Maps are similar to PHP’s associative arrays, but explicitly typed.
PHP:
$items = ["apple", "banana"];
$prices = ["apple" => 1.2, "banana" => 0.5];
Go:
items := []string{"apple", "banana"}
prices := map[string]float64{
"apple": 1.2,
"banana": 0.5,
}
12. Looping
range
is Go’s version of foreach
. _
ignores the index/key if you don’t need it.
PHP:
foreach ($names as $name) {
echo $name;
}
Go:
for _, name := range names {
fmt.Println(name)
}
13. Appending to Arrays
Go slices don’t auto-resize like PHP arrays — you use append()
to expand them.
PHP:
$names[] = "Charlie";
Go:
names = append(names, "Charlie")
14. String Interpolation
Go doesn’t have $var
interpolation — use fmt.Printf
or fmt.Sprintf
.
PHP:
$name = "Alice";
echo \"Hello, $name\";
Go:
name := "Alice"
fmt.Printf("Hello, %s\n", name)
15. Nil vs Null
Go has nil
, but only for pointers, slices, maps, channels, interfaces, and functions. Basic types like int
, string
, and bool
always have zero-values (0
, ""
, false
).
PHP:
$var = null;
Go:
var thing *Something // defaults to nil
16. Optional Parameters / Defaults
No default params in Go. You handle that logic yourself inside the function.
PHP:
function greet($name = "World") {
echo "Hello, $name";
}
Go:
func greet(name string) {
if name == "" {
name = "World"
}
fmt.Println("Hello,", name)
}
17. Variable Declaration
Go has static typing, so you can let it infer types with :=
or be explicit with var
.
PHP:
$name = "Bob";
Go:
name := "Bob" // shorthand
var name string = "Bob" // explicit
18. Ternary Operator
Feels verbose at first, but Go prefers explicitness over clever syntax.
PHP:
$msg = $isLoggedIn ? "Welcome" : "Login";
Go:
var msg string
if isLoggedIn {
msg = "Welcome"
} else {
msg = "Login"
}
19. Global Variables / Superglobals
No magic access to global state. You extract everything you need manually.
PHP:
$_GET, $_POST, $_SERVER, $GLOBALS
Go:
func handler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
}
20. Sessions and Cookies
Session handling isn’t built-in. You handle cookies manually or bring in a library like gorilla/sessions
.
PHP:
session_start();
$_SESSION["user_id"] = 123;
Go:
http.SetCookie(w, &http.Cookie{Name: "user_id", Value: "123"})
cookie, err := r.Cookie("user_id")
21. Autoloading / Namespaces
If you move a file, you update the import. It’s static and simple.
PHP:
namespace App\Controllers;
spl_autoload_register(...);
Go:
import "yourapp/controllers"
22. Anonymous Functions / Closures
Closures exist in Go, including capturing outer variables. Just stricter with types.
PHP:
$greet = function($name) {
return \"Hello $name\";
};
Go:
greet := func(name string) string {
return "Hello " + name
}
23. Named Parameters / Associative Arrays as Config
Go doesn’t have named parameters — you define a struct and pass it in.
PHP:
config([
'host' => 'localhost',
'port' => 3306,
])
Go:
cfg := Config{
Host: "localhost",
Port: 3306,
}
24. Default Values on Unset Fields
No null coalescing operator — you check the presence of keys explicitly.
PHP:
$foo = $data['foo'] ?? 'default';
Go:
val, ok := data["foo"]
if !ok {
val = "default"
}
25. Environment Variables
You’ll use os
for env access, or a library like godotenv
to load from .env
files.
PHP:
$_ENV['DB_HOST']
Go:
os.Getenv("DB_HOST")
26. String Functions / Helpers
PHP has tons of built-ins like strtoupper
, explode
, substr
, etc.
Go keeps things split into packages, no global helpers — everything is grouped logically.
Go:
import "strings"
strings.ToUpper("hello")
strings.Split("a,b,c", ",")
27. Date & Time
No null coalescing operator — you check the presence of keys explicitly.
PHP:
$now = new DateTime();
Go:
now := time.Now()
# Go’s time package is rich, but also more verbose. Formatting is via layout strings like:
now.Format("2006-01-02 15:04:05")
(This date string is the format key, not an example — weird at first, but you get used to it.)
28. JSON Encoding / Decoding
Go requires you to define a struct for decoding — no dynamic arrays or object access unless you use map[string]interface{}
.
PHP:
json_encode($array);
json_decode($json, true);
Go:
json.Marshal(data)
json.Unmarshal(body, &targetStruct)
Final Thoughts
Go is simple, but it forces a different mindset. No magic, no frameworks, just you and the code. If you’re coming from PHP, expect to write more boilerplate at first, but you’ll appreciate the clarity. The language won’t hold your hand, but it won’t stab you in the back either.
Want more comparisons or ready to build something small in Go to lock it in? Hit me up or leave a comment.