Module 1

Core PHP

The complete language foundation — types, OOP, closures, match expressions, enums, generators, and fibers.

Types & Variables Arrays OOP Traits Enums Readonly Error Handling Generators Fibers

Types & Variables

01

PHP is dynamically typed but supports strict type declarations. PHP 8.x dramatically improved the type system with union types, intersection types, and never.

TypeExampleNotes
int42, -7, 0xFF64-bit on modern systems
float3.14, 1.2e3IEEE 754 double
string"hello", 'world'Binary-safe, any length
booltrue, falseCase-insensitive
nullnullAbsence of value
int|float 8.0Union typeAccept multiple types
A&B 8.1Intersection typeMust satisfy both
never 8.1exit(), throwFunction never returns
<?php
declare(strict_types=1); // Enforces all type declarations

// Union types (8.0+)
function format(int|float $n): string {
    return number_format($n, 2);
}

// Nullable type — either User or null
function find(int $id): ?User { /* ... */ }

// Intersection types (8.1+): must be Countable AND Traversable
function process(Countable&Traversable $col): void { /* ... */ }

// First-class callable syntax (8.1+)
$fn      = strlen(...);         // Closure from built-in
$lengths = array_map(strlen(...), $strings);

Arrays

// List (sequential)
$fruits = ['apple', 'banana', 'cherry'];

// Associative (hash map)
$user = [
    'name' => 'Alice',
    'age'  => 30,
    'role' => 'admin',
];

// Spread & unpack
$a = [1, 2, 3];
$b = [0, ...$a, 4]; // [0,1,2,3,4]

// Destructuring
[$first, , $third] = $fruits;
['name' => $name]  = $user;
// Functional pipeline
$result = array_reduce(
    array_filter(
        array_map(
            fn($x) => $x * 2,
            range(1, 10)
        ),
        fn($x) => $x > 10
    ),
    fn($carry, $item) => $carry + $item,
    0
); // 60

// usort with spaceship operator
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);

Strings

// PHP 8.0 str_* functions (replace strpos boilerplate)
str_contains($haystack, 'needle');    // bool
str_starts_with($url, 'https://');    // bool
str_ends_with($file, '.php');         // bool

// Heredoc — evaluates variables
$msg = <<

Control Flow

02

match Expression 8.0

match uses strict === comparison, returns a value, and throws UnhandledMatchError when no arm matches.

// Old: switch (loose ==, no return)
switch ($status) {
    case 1:
        $label = 'Active';
        break;
    case 2:
        $label = 'Inactive';
        break;
    default:
        $label = 'Unknown';
}
// New: match (strict ===, returns value)
$label = match($status) {
    1       => 'Active',
    2       => 'Inactive',
    3, 4    => 'Pending',   // multiple arms
    default => 'Unknown',
};

// Null-safe operator (8.0+)
$country = $user?->getAddress()?->getCountry();
Tip: The null-safe operator ?-> short-circuits the entire chain on null — none of the subsequent method calls execute.
λ

Functions & Closures

03

Closures

// Closure captures variables via `use`
$multiplier = 3;
$triple = function(int $n) use ($multiplier): int {
    return $n * $multiplier;
};

// Capture by reference — mutates outer scope
$count = 0;
$inc = function() use (&$count) { $count++; };
$inc(); $inc(); // $count === 2

// Closure::bind — attach a closure to a private class context
class Foo { private int $secret = 42; }
$reader = Closure::bind(function() {
    return $this->secret;
}, new Foo(), Foo::class);
$reader(); // 42

Arrow Functions 7.4

// Arrow functions implicitly capture outer scope — no `use` needed
$tax     = 0.1;
$withTax = array_map(fn($price) => $price * (1 + $tax), $prices);

// Named arguments (8.0+) — skip positional order
array_slice($arr, offset: 2, length: 5, preserve_keys: true);

// First-class callable syntax (8.1+)
$fn      = strlen(...);         // Closure from built-in
$lengths = array_map(strlen(...), $strings);

Object-Oriented PHP

04
interface
Serializable
abstract class
BaseModel
class
User
class
Post

Constructor Property Promotion 8.0

class User
{
    // Constructor property promotion — no boilerplate property declarations
    public function __construct(
        public readonly int    $id,
        public          string $name,
        private         string $email,
        public          Role   $role = Role::User,
    ) {}

    // Fluent interface using clone
    public function withName(string $name): static
    {
        $clone       = clone $this;
        $clone->name = $name;
        return $clone;
    }

    public function __toString(): string
    {
        return "{$this->name} <{$this->email}>";
    }
}

Interfaces

interface Repository
{
    public function find(int $id): ?Model;
    public function findAll(): array;
    public function save(Model $model): void;
    public function delete(int $id): bool;
}

// A class can implement multiple interfaces
class UserRepository implements Repository, Countable
{
    public function count(): int { return count($this->items); }
    // ... implement Repository methods
}

Traits

What are traits? Traits are a code-reuse mechanism — compiler-assisted copy-paste. They solve the single-inheritance limitation without the complexity of multiple inheritance.
trait Timestampable
{
    private ?DateTime $createdAt = null;
    private ?DateTime $updatedAt = null;

    public function touch(): void
    {
        $this->updatedAt  = new DateTime();
        $this->createdAt ??= new DateTime(); // only set once
    }
}

trait SoftDeletes
{
    private ?DateTime $deletedAt = null;
    public function delete(): void  { $this->deletedAt = new DateTime(); }
    public function restore(): void { $this->deletedAt = null; }
    public function isDeleted(): bool { return $this->deletedAt !== null; }
}

class Post
{
    use Timestampable, SoftDeletes; // compose both traits
}

Enums 8.1

// Pure enum — no backing value
enum Status
{
    case Active;
    case Inactive;
    case Banned;

    public function label(): string
    {
        return match($this) {
            Status::Active   => 'Active',
            Status::Inactive => 'Inactive',
            Status::Banned   => 'Banned',
        };
    }
}

// Backed enum — has a scalar value (int or string)
enum Role: string
{
    case Admin = 'admin';
    case User  = 'user';
    case Guest = 'guest';
}

$role = Role::from('admin');        // throws on invalid value
$role = Role::tryFrom('unknown');   // returns null
$val  = Role::Admin->value;         // 'admin'

Readonly Classes 8.2

// readonly class — ALL properties are readonly, great for value objects
readonly class Money
{
    public function __construct(
        public float  $amount,
        public string $currency,
    ) {}

    public function add(Money $other): static
    {
        // Cannot mutate — must return a new instance
        return new static($this->amount + $other->amount, $this->currency);
    }
}

$price         = new Money(9.99, 'USD');
$price->amount = 5.00; // Fatal error: Cannot modify readonly property

Error Handling

05
// Exception hierarchy
class AppException extends RuntimeException {}
class NotFoundException extends AppException {
    public function __construct(string $resource, int $id) {
        parent::__construct("$resource #$id not found", 404);
    }
}

// Multi-catch (7.1+) — catch multiple types in one block
try {
    $user = $repo->findOrFail($id);
} catch (NotFoundException | AccessDeniedException $e) {
    return response()->error($e->getMessage(), $e->getCode());
} catch (Throwable $e) {
    // Catches exceptions AND PHP engine errors (TypeError, etc.)
    logger()->error('Unexpected', ['exception' => $e]);
    throw $e;
} finally {
    // Always runs — clean up resources here
    $db->close();
}
Always catch Throwable (not just Exception) when you need to intercept PHP engine errors like TypeError, ParseError, and Error.

Generators

06

Generators produce values lazily — only one value is in memory at a time, making them ideal for large datasets or infinite sequences.

// Without generator — loads ALL rows ❌
function getAllUsers(): array
{
    return DB::query('SELECT * FROM users')
             ->fetchAll(); // 1M rows → OOM
}
// Generator — yields one at a time ✅
function streamUsers(): Generator
{
    $stmt = DB::query('SELECT * FROM users');
    while ($row = $stmt->fetch()) {
        yield $row;
    }
}

foreach (streamUsers() as $user) {
    process($user); // constant memory
}
// yield from — delegate to sub-generator
function flatten(array $items): Generator
{
    foreach ($items as $item) {
        if (is_array($item)) {
            yield from flatten($item); // recurse
        } else {
            yield $item;
        }
    }
}

// send() — two-way communication with generator
function accumulator(): Generator
{
    $total = 0;
    while (true) {
        $n = yield $total;
        if ($n === null) break;
        $total += $n;
    }
}
$gen = accumulator(); $gen->current(); // init
$gen->send(10); $gen->send(5);         // total = 15

Fibers 8.1

07

Fibers are stackful coroutines — they can pause execution at any point in the call stack, unlike generators which only pause at the top level.

$fiber = new Fiber(function(): void {
    $value = Fiber::suspend('first');    // pauses here, yields 'first'
    echo "Resumed with: $value\n";      // prints "hello"

    $value2 = Fiber::suspend('second'); // pauses again
    echo "Resumed again: $value2\n";
});

$yielded  = $fiber->start();            // 'first'
$yielded2 = $fiber->resume('hello');    // 'second', prints "Resumed with: hello"
$fiber->resume('world');                // prints "Resumed again: world"
Real-world use: Fibers are the foundation that async libraries like ReactPHP and Amp v3 use to implement cooperative multitasking. You rarely use Fiber directly — you interact through an event loop abstraction.