Twenty hands-on lessons that translate what you already know from Go
into PHP. Each lesson shows a familiar Go idiom, gives you a small
problem to solve in PHP, and checks your answer by running it on Wandbox
(internet connection required). Your progress is saved locally, and a
reference solution is one click away if you get stuck.
Go ⇄ PHP cheat sheet
A comprehensive quick reference pulled together from all 20 lessons — every idiom mapped between the two languages, in one place.
| Concept | Go | PHP |
| Declare + assign | x := 1 | $x = 1; (assignment is declaration) |
| Change type later | compile error | allowed — variables are dynamic |
| Numbers | int, int64, float64, … | int and float |
| Typed signature | func f(x int) string | function f(int $x): string |
| Formatting | fmt.Sprintf("%s is %d", n, a) | "$n is $a" in double quotes |
| Failure | return val, err | throw new Exception(...) (lesson 16) |
| Naming | camelCase | snake_case functions |
| Concept | Go | PHP |
| Format into a string | fmt.Sprintf("%s is %d", n, a) | "$n is $a" or "{$n} is {$a}" |
| Concatenate | a + b | $a . $b |
| Uppercase | strings.ToUpper(s) | strtoupper($s) |
| Substring test | strings.Contains(h, n) | str_contains($h, $n) |
| Length | len(s) | strlen($s) |
| Join | strings.Join(parts, ", ") | implode(", ", $parts) |
| Split | strings.Split(s, ",") | explode(",", $s) |
| Concept | Go | PHP |
| Literal | []int{1, 2, 3} | [1, 2, 3] |
| Append | s = append(s, x) | $s[] = $x; |
| Length | len(s) | count($s) |
| Membership | slices.Contains(s, x) | in_array($x, $s, true) |
| Sum | for range loop | array_sum($s) |
| Index | s[0] | $s[0] |
| Semantics | slice header, shared backing array | value: copies on assign/pass (copy-on-write) |
| Concept | Go | PHP |
| Literal | map[string]int{"a": 1} | ['a' => 1] |
| Read with default | v, ok := m[k] + if | $m[$k] ?? $default |
| Key exists? | _, ok := m[k] | array_key_exists($k, $m) |
| Set | m[k] = v | $m[$k] = $v; |
| Delete | delete(m, k) | unset($m[$k]); |
| Iterate | for k, v := range m | foreach ($m as $k => $v) |
| Iteration order | randomized on purpose | insertion order, guaranteed |
| All keys / values | manual loop | array_keys($m) / array_values($m) |
| Concept | Go | PHP |
| Signature | func f(x int) string | function f(int $x): string |
| Default value | — (write a wrapper) | string $g = "Hello" |
| Optional / nullable | *string + nil check | ?string $x = null |
| Either-or type | any + type switch | int|string + is_int() |
| Readable call sites | functional-options pattern | f(name: "Ada") |
| No return value | omit the type | : void |
| Multiple returns | (int, error) | return an array, or throw (Lesson 16) |
| Concept | Go | PHP |
| The absent value | nil (behavior varies by kind) + zero values | null, one value for everything |
| Nilable type | *Address | ?Address |
| Map lookup with default | v, ok := m[k] + if | $m[$k] ?? $def |
| Deep access | nil check per hop, or panic | $u?->address?->city — null anywhere → null |
| Null check | u == nil | $u === null; isset($x) = declared and not null |
| Assign-if-absent | if _, ok := m[k]; !ok { m[k] = d } | $m[$k] ??= $d; |
| Concept | Go | PHP |
| Equality | == (compile-time type-checked) | === — use it always; == juggles |
| Inequality | != | !== |
| Mixed-type compare | won't compile | "1" == 1 is true; "1" === 1 is false |
| Condition type | must be bool | anything — coerced via truthiness |
| Falsy values | only false | false, 0, 0.0, "", "0", [], null |
| Three-way compare | cmp.Compare(a, b) | $a <=> $b |
| Concept | Go | PHP |
| if | if x > 0 { } | if ($x > 0) { } — parens required |
| Value switch | switch day { case ...: } | match ($day) { ... => ... } (skip legacy switch) |
| Condition chain | switch { case a > b: } | match (true) { $a > $b => ... } |
| Grouped cases | case "sat", "sun": | "sat", "sun" => |
| Comparison | strict (typed) | match: strict ===; switch: loose == |
| Statement or expression? | statement | match is an expression — assign it, return it |
| Unhandled value | runs nothing | throws UnhandledMatchError |
| Concept | Go | PHP |
| Define a type | type T struct {...} + external methods | class T { ... } — everything inside |
| Construct | NewT(...) (convention) | new T(...) → __construct (language feature) |
| Fields + constructor | struct block + assignment in NewT | promoted: __construct(private int $x = 0) |
| Receiver | func (a *T) M() — named, explicit | $this, implicit in signature, mandatory in body |
| Member access | a.field (auto-deref) | $a->field (also auto-deref) |
| Visibility | Exported / unexported case | public / private / protected |
| Copy semantics | value receivers copy; pointers don't | objects are handles — never copied on assignment or call |
| Concept | Go | PHP |
| Interface | implicit satisfaction | interface Shape {} + explicit implements Shape |
| Inheritance | none — embed instead | extends (single parent only) |
| Contract + shared code | interface + embedded helper type | abstract class — both in one |
| Required method | interface method | abstract public function area(): float; |
| Constructor chaining | — | parent::__construct(...) |
| Type test | type switch / assertion | $x instanceof Shape |
| Multiple contracts | satisfy many interfaces freely | implements A, B — many interfaces, one extends |
| Concept | Go | PHP |
| Declare | type S int + iota const block | enum S: string { case A = 'a'; } |
| To raw value | hand-written String() | $s->value |
| Case name | — | $s->name (e.g. "Pending") |
| From raw value | hand-written map/switch | S::from($raw) (throws) / S::tryFrom($raw) (null) |
| All values | manual slice | S::cases() |
| Exhaustiveness | linters, maybe | match throws on an unhandled case |
| Invalid values | OrderStatus(42) — legal | cannot be constructed |
| Concept | Go | PHP |
| Share method code | embed a struct | trait T { ... } + use T; |
| How methods arrive | promoted, still the inner type's | flattened in, as if written in the class |
| Call back into host | not possible via embedding | $this->method() — dynamic dispatch |
| Multiple sources | embed several structs | use A, B; |
| Name clash | ambiguous selector error | use A, B { A::f insteadof B; } |
| Require host methods | — | abstract public function name(): string; in the trait |
| State | embedded struct's fields | traits can declare properties too |
| Concept | Go | PHP |
| Transform | for range + append | array_map($fn, $arr) |
| Select | for range + if + append | array_filter($arr, $fn) |
| Fold | accumulator loop | array_reduce($arr, $fn, $init) |
| Sum | accumulator loop | array_sum($arr) |
| Inline callback | func(p Product) string { … } | fn($p) => $p['name'] |
| Argument order | n/a | map: callback first; filter: array first |
| Keys after filter | slices reindex by nature | preserved — call array_values() |
| Sort with comparator | slices.SortFunc | usort($arr, $fn) (in place, by reference) |
| Concept | Go | PHP |
| Multiple assignment | a, b := f() | [$a, $b] = f(); |
| Swap | a, b = b, a | [$a, $b] = [$b, $a]; |
| Skip a value | _, b := f() | [, $b] = f(); |
| Pick named fields | — | ['name' => $n] = $user; |
| Variadic parameter | func f(nums ...int) | function f(int ...$nums) |
| Spread at call site | f(nums...) (dots after) | f(...$nums) (dots before) |
| Concatenate | append(a, b...) | [...$a, ...$b] |
| Destructure in a loop | for _, p := range pairs | foreach ($pairs as [$x, $y]) |
| Concept | Go | PHP |
| Anonymous function | func(x int) int { ... } | function (int $x) { ... } |
| Capture a variable | implicit, by reference | use ($n) — explicit, by value (snapshot) |
| Capture by reference | the only mode | use (&$n) — opt-in per variable |
| Short single expression | — | fn($x) => $x * $n (auto-captures by value) |
| Function-typed parameter | f func(int) int | callable $f / Closure $f |
| Function-typed return | func() int | Closure |
| Named function as a value | strings.ToUpper | strtoupper(...) (first-class callable) or 'strtoupper' |
| Concept | Go | PHP |
| Signal failure | return 0, err | throw new SomeException("msg"); |
| Propagate upward | if err != nil { return 0, err } everywhere | automatic — do nothing |
| Handle by type | errors.As(err, &target) | catch (SomeException $e) — subclasses match too |
| Custom error | type with Error() string | class MyError extends Exception {} |
| Guaranteed cleanup | defer | finally { ... } |
| The message | err.Error() | $e->getMessage() |
| Catch anything | recover() | catch (Throwable $e) |
| Concept | Go | PHP |
| Constant | const DefaultName = "guest" | const DEFAULT_NAME = "guest"; in a class |
| Access it | pkg.DefaultName | Config::DEFAULT_NAME |
| Shared state | var count int | private static int $count = 0; |
| Call without instance | pkg.Increment() | Counter::increment() |
| Self-reference | unqualified name in package | self::$count, self::CONST |
| Write-once field | — (convention only) | public readonly string $name |
| Violation caught | never | Error thrown at runtime |
| Concept | Go 1.23 | PHP |
| Define an iterator | func(yield func(int) bool) | any function containing yield |
| Produce a value | if !yield(v) { return } | yield $v; |
| Return type | iter.Seq[int] | Generator |
| Consume | for v := range seq | foreach ($gen as $v) |
| Early stop | break → yield returns false | break — generator stays suspended |
| Collect all | slices.Collect(seq) | iterator_to_array($gen) |
| Array-or-sequence param | two signatures (or convert) | iterable accepts both |
| Concept | Go | PHP |
| Serialize | json.Marshal(v) | json_encode($v) |
| Parse | json.Unmarshal(raw, &v) | json_decode($raw, true) |
| Field naming | json:"snake_name" tags | array keys are used verbatim |
| Missing field | zero value | key absent — read with ?? $default |
| Bad input | err != nil | null silently, or JSON_THROW_ON_ERROR → JsonException |
| Pretty output | json.MarshalIndent | json_encode($v, JSON_PRETTY_PRINT) |
| Objects instead of arrays | — | omit the true → stdClass |
| Concept | Go | PHP |
| Copying a collection of objects | []*Item copy shares the pointees | array copy shares the objects |
| Shallow copy of one object | copied := *it | $copy = clone $it; |
| Maybe-absent result | *Item, check nil | ?Item, check null (or chain with ?->) |
| Sum a field | for loop + accumulator | array_reduce($items, fn(...), 0) |
| Transform | for loop building a new slice | array_map(fn($it) => ..., $items) |
| Select + reindex | for loop with append | array_values(array_filter(...)) |
| Deep-copy hook | write it by hand | __clone() method, called on clone |