01
📖 Chapter 1 · PHP

What is PHP?

PHP (Hypertext Preprocessor) is the server-side language powering over 77% of the web. From WordPress to Laravel to Facebook's early days — PHP is battle-tested, fast, and more modern than you think.

5 min
🎯 Beginner
📅 PHP 8.4

How PHP Works

PHP runs on the server. When a visitor requests a URL, PHP executes on the server, generates HTML, and sends it to the browser. The browser never sees your PHP code — only the result.

request-flow.txt
Browser → HTTP Request → PHP Server → Executes .php file
                                           ↓
Browser ← HTML Response ← PHP generates HTML output

Why PHP in 2026?

JIT Compiler

PHP 8.x has a Just-in-Time compiler making it dramatically faster than PHP 7

🔒

Type Safety

Full type system: union types, enums, readonly, intersection types

🌍

Ecosystem

Laravel, Symfony, Composer — world-class frameworks & tooling

💼

Jobs

Consistently top 5 most in-demand server-side language globally

🧪

Modern Testing

PHPUnit, Pest — first-class testing ecosystem

🚀

Easy Deploy

Any shared host, VPS, Docker, or cloud platform supports PHP

PHP Version Timeline

VersionKey FeatureStatus
PHP 8.0JIT, Match expression, Named args, Union typesEOL
PHP 8.1Enums, Fibers, Readonly properties, Intersection typesSecurity only
PHP 8.2Readonly classes, Disjunctive Normal Form types✅ Active
PHP 8.3Typed class constants, json_validate(), Override attribute✅ Active
PHP 8.4Property Hooks, Asymmetric Visibility, Lazy Objects✅ Active
PHP 8.5Pipe Operator |>, URI Extension, Clone With, #[\NoDiscard]✅ Latest

⚡ PHP is Alive — The Numbers Don't Lie

Some people say PHP is dying. The data says the opposite. PHP remains one of the most actively developed, widely deployed, and economically significant languages in the world.

🌐

77% of the Web

Over three quarters of all websites with a known server-side language run PHP — including Wikipedia, WordPress, and Facebook's HHVM roots

🚀

Annual Releases

PHP ships a major version every November. 8.5 dropped Nov 2025 with the Pipe Operator. PHP 9.0 is actively planned

💰

Laravel is Booming

Laravel is among the most popular web frameworks globally by GitHub stars and job postings in 2026

JIT + 10x Faster

PHP 8.x with OPcache + JIT is 2–3× faster than PHP 7 and 10× faster than PHP 5. Performance is no longer a concern

🔒

Modern Type System

Enums, readonly, intersection types, property hooks, generics via Psalm/PHPStan — PHP's type system rivals TypeScript

📦

Rich Ecosystem

Packagist hosts 400,000+ packages. Composer installs have reached billions per month

🎯 This Course Covers

PHP fundamentals → OOP → PHP 8.4 & 8.5 → Error handling → Namespaces → Magic methods → Generators → Regex → Composer → Testing → MySQL from scratch → JOINs, Indexes, Transactions → Full CRUD with PDO. 42 lessons, zero fluff.

02
⚙️ Chapter 2 · PHP

Install PHP 8.5

Get PHP 8.5 — the latest stable release — running on your machine in minutes. We cover macOS, Windows (WSL2), and Docker — the three recommended setups for 2026.

8 min
🎯 Beginner
📅 PHP 8.5 — Nov 2025

macOS — Laravel Herd (Recommended)

Laravel Herd is the fastest way to get PHP + Nginx on macOS. Download from herd.laravel.com.

terminal
# After Herd installs, verify PHP version
php --version
# PHP 8.5.x (cli)

# Switch PHP versions via Herd
herd php85
herd php84

# Start built-in dev server (no Nginx needed for learning)
php -S localhost:8000

Windows — WSL2 + Ubuntu

PowerShell (Admin)
# Enable WSL2
wsl --install

# Then in Ubuntu terminal:
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.5 php8.5-cli php8.5-fpm php8.5-mysql php8.5-curl php8.5-mbstring
php --version

Docker (All Platforms)

docker-compose.yml
services:
  app:
    image: php:8.5-fpm
    volumes: ["./src:/var/www/html"]
    ports: ["9000:9000"]
  nginx:
    image: nginx:alpine
    ports: ["8080:80"]
  mysql:
    image: mysql:8.4
    environment: {MYSQL_ROOT_PASSWORD: secret, MYSQL_DATABASE: myapp}
    ports: ["3306:3306"]
💡 Recommended Editor

VS Code + PHP Intelephense extension. It gives autocomplete, type inference, go-to-definition, and error detection in real-time.

03
👋 Chapter 3 · PHP

Hello World

Write and run your first PHP file. Learn how PHP tags work, how PHP embeds in HTML, and when to use the short echo tag.

hello.php
<?php

// Output text
echo "Hello, World!";

// Short echo tag — use in HTML templates
// <?= "Hello" ?>

// print() works too (returns 1, slightly slower)
print("Hello!");

// Multiple values
echo "Hello", " ", "World";  // no concatenation needed

PHP Inside HTML

index.php
<!DOCTYPE html>
<html>
<body>
  <h1><?= "Welcome!" ?></h1>
  <p>Today: <?= date('l, F j Y') ?></p>
</body>
</html>
💡 No closing tag for pure PHP files

For class and config files (not HTML templates), use <?php at the top and omit the closing ?> tag. This prevents accidental whitespace before headers are sent.

User.php (pure PHP file)
<?php
declare(strict_types=1);

// No closing ?> tag — intentional!
class User
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}
04
📦 Chapter 4 · PHP

Variables & Types

PHP's type system in 2026 is comprehensive. Learn scalar types, type declarations, union types, intersection types, and why strict_types matters.

variables.php
<?php
declare(strict_types=1);

// Scalar types
$name    = "Alice";      // string
$age     = 30;           // int
$score   = 98.5;        // float
$active  = true;        // bool
$nothing = null;        // null

// Inspect types
var_dump($age);          // int(30)
gettype($score);        // "double"

// Constants
const MAX_RETRIES = 3;
define('APP_ENV', 'production');

// Typed function (PHP 7+ strict types)
function greet(string $name, int $age): string
{
    return "Hello {$name}, you are {$age}!";
}

// Union types (PHP 8.0+)
function format(string|int $val): string|float { /*...*/ }

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

// Intersection types (PHP 8.1+) — must implement both
function process(Countable&Iterator $col): void { /*...*/ }
⚠️ Always use strict_types

Add declare(strict_types=1); to every PHP file. Without it, PHP silently coerces "3abc" into 3 — hiding bugs that crash in production.

✦ Quick Check
What does ?string mean as a PHP type?
05
🔤 Chapter 5 · PHP

Strings

String literals, interpolation, heredoc/nowdoc, and PHP 8's modern string functions like str_contains(), str_starts_with(), and str_ends_with().

strings.php
$name = "World";

// Double-quoted: interpolates $variables and escape sequences
echo "Hello, {$name}!";        // Hello, World!
echo "Tab:\tNewline:\n";

// Single-quoted: literal string, no interpolation (faster)
echo 'Hello, $name!';           // Hello, $name!

// Heredoc (interpolates variables)
$html = <<<HTML
    <p>Hello, {$name}!</p>
HTML;

// PHP 8+ string functions
str_contains("Hello World", "World");   // true
str_starts_with("Hello", "He");          // true
str_ends_with("Hello", "lo");           // true

// Classic functions
strlen("hello");        // 5
strtolower("HELLO");    // hello
strtoupper("hello");    // HELLO
trim("  hello  ");      // "hello"
str_replace("World", "PHP", "Hello World"); // Hello PHP
explode(",", "a,b,c");  // ["a","b","c"]
implode("-", ["a","b"]); // "a-b"
substr("Hello", 1, 3);   // "ell"
sprintf("%.2f", 3.14159); // "3.14"
06
🗂️ Chapter 6 · PHP

Arrays

PHP arrays are lists, hashmaps, stacks, and queues all in one. Learn array syntax, destructuring, and the modern functional array functions with arrow functions.

arrays.php
// Indexed array
$fruits = ['apple', 'banana', 'cherry'];
echo $fruits[0];           // apple

// Associative (map/hashmap)
$user = [
    'name'  => 'Alice',
    'email' => '[email protected]',
    'age'   => 28,
];

// Destructuring (PHP 7.1+)
['name' => $name, 'age' => $age] = $user;

// Spread operator
$merged = [...$fruits, 'date', 'elderberry'];

// Modern functional array functions with arrow functions
$nums = [1, 2, 3, 4, 5, 6];

$doubled = array_map(fn($n) => $n * 2, $nums);
// [2, 4, 6, 8, 10, 12]

$evens   = array_filter($nums, fn($n) => $n % 2 === 0);
// [2, 4, 6]

$sum     = array_reduce($nums, fn($c, $i) => $c + $i, 0);
// 21

// PHP 8.4: array_find, array_any, array_all
$found   = array_find($nums, fn($n) => $n > 4);   // 5
$hasLarge = array_any($nums, fn($n) => $n > 5);  // true

// Sorting
sort($fruits);             // in-place, returns void
$sorted = array_values(array_unique($nums));

// array_column — extract column from 2D array
$users = [['id'=>1,'name'=>'Alice'], ['id'=>2,'name'=>'Bob']];
$names = array_column($users, 'name'); // ['Alice','Bob']
07
🔀 Chapter 7 · PHP

Control Flow

PHP 8's match expression, null coalescing ??, nullsafe ?->, and all the loop types — the complete control flow toolkit.

control-flow.php
// if / elseif / else
if ($score >= 90) {
    echo "A";
} elseif ($score >= 80) {
    echo "B";
} else {
    echo "C or below";
}

// match — strict, exhaustive, returns value (PHP 8+)
$label = match($statusCode) {
    200       => 'OK',
    201       => 'Created',
    301, 302  => 'Redirect',
    404       => 'Not Found',
    default   => 'Unknown',
};

// Null coalescing — value or fallback
$name = $_GET['name'] ?? 'Guest';

// Nullsafe operator — short-circuits on null
$city = $user?->getAddress()?->getCity();

// Loops
for ($i = 0; $i < 10; $i++) { /*...*/ }

foreach ($users as $user) {
    echo $user['name'];
}

foreach ($map as $key => $value) {
    echo "{$key}: {$value}";
}

while ($row = $stmt->fetch()) {
    // process DB rows
}

// break and continue work as expected
08
⚡ Chapter 8 · PHP

Functions

Named functions, arrow functions, closures, first-class callables, named arguments, and variadic params — the complete PHP functions guide.

functions.php
// Typed function with default parameter
function greet(string $name, string $greeting = "Hello"): string
{
    return "{$greeting}, {$name}!";
}

// Named arguments (PHP 8.0+) — order doesn't matter
greet(greeting: 'Hi', name: 'Bob');  // Hi, Bob!

// Variadic functions
function sum(int ...$nums): int
{
    return array_sum($nums);
}
sum(1, 2, 3, 4);  // 10

// Arrow function — captures outer scope automatically
$multiplier = 3;
$triple = fn(int $n): int => $n * $multiplier;
$triple(5);  // 15

// Classic closure — must use() to capture
$add = function(int $x) use ($multiplier): int {
    return $x + $multiplier;
};

// First-class callables (PHP 8.1+)
$strlen = strlen(...);
array_map($strlen, ['hello', 'world']); // [5, 5]

// never return type — function always throws/exits
function abort(int $code): never
{
    throw new RuntimeException("Aborted: {$code}");
}
09
🏗️ Chapter 9 · PHP

Classes & Objects

Modern PHP OOP with constructor promotion, readonly properties, and PHP 8.4 property hooks and asymmetric visibility.

Product.php
<?php
declare(strict_types=1);

class Product
{
    // Constructor promotion (PHP 8.0+) — one line per property
    public function __construct(
        public readonly string $name,
        public readonly float  $price,
        private int $stock = 0,
    ) {}

    // Asymmetric visibility (PHP 8.4)
    public private(set) int $views = 0;

    // Property hook (PHP 8.4) — computed property
    public string $slug {
        get => strtolower(str_replace(' ', '-', $this->name));
    }

    public function isInStock(): bool
    {
        return $this->stock > 0;
    }

    public function addStock(int $qty): static
    {
        $this->stock += $qty;
        return $this;  // fluent interface
    }
}

$p = new Product('Laptop', 999.99);
$p->addStock(10);
echo $p->slug;      // "laptop"
echo $p->views;     // 0 (readable)
$p->views = 5;     // ❌ Error: private(set)
10
🧬 Chapter 10 · PHP

Inheritance & Interfaces

extends, abstract classes, interfaces, traits, and the #[Override] attribute from PHP 8.3 that prevents silent method override bugs.

oop-advanced.php
// Abstract class
abstract class Shape
{
    abstract public function area(): float;

    public function describe(): string
    {
        return sprintf("Area: %.2f", $this->area());
    }
}

class Circle extends Shape
{
    public function __construct(private float $radius) {}

    // PHP 8.3: #[Override] — error if parent doesn't have this method
    #[Override]
    public function area(): float
    {
        return M_PI * $this->radius ** 2;
    }
}

// Interface
interface Serializable
{
    public function toJson(): string;
    public static function fromJson(string $json): static;
}

// Trait — reusable code snippets
trait Timestampable
{
    private ?DateTime $createdAt = null;

    public function touch(): void
    {
        $this->createdAt ??= new DateTime();
    }
}

class Post extends BaseModel implements Serializable
{
    use Timestampable;
    // ...implements interface methods...
}
11
✨ Chapter 11 · PHP

Match, Fibers & Enums

Three of the most impactful PHP 8.x features: the exhaustive match expression, backed enum types, and PHP Fibers for cooperative concurrency.

modern.php
// ── ENUMS (PHP 8.1+) ──────────────────────────

// Backed enum — each case has a value
enum Status: string
{
    case Active    = 'active';
    case Inactive  = 'inactive';
    case Suspended = 'suspended';

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

Status::from('active');      // Status::Active
Status::tryFrom('unknown');  // null (safe)

// Use in type signatures
function activate(Status $s): void { /*...*/ }

// ── READONLY CLASS (PHP 8.2+) ──────────────────
readonly class Money
{
    public function __construct(
        public int    $amount,   // in cents
        public string $currency,
    ) {}

    public function add(Money $other): static
    {
        return new static($this->amount + $other->amount, $this->currency);
    }
}

// ── FIBERS (PHP 8.1+) — cooperative multitasking ──
$fiber = new Fiber(function(): void {
    $val = Fiber::suspend('first yield');
    echo "Resumed with: {$val}";
});

$yielded = $fiber->start();    // "first yield"
$fiber->resume('hello');       // "Resumed with: hello"
12
🚀 Chapter 12 · PHP 8.4

PHP 8.4 Features

Released November 2024. Property Hooks, Asymmetric Visibility, Lazy Objects, and new array functions — the biggest PHP release since 8.1.

📦 PHP 8.4.x — Released Nov 2024
php84-features.php
// ── 1. PROPERTY HOOKS ─────────────────────────
class User
{
    public string $email {
        set(string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new ValueError("Invalid email");
            }
            $this->email = strtolower($value);
        }
    }

    public string $displayName {
        // Virtual — no backing storage, computed on read
        get => ucwords($this->firstName . ' ' . $this->lastName);
    }
}

// ── 2. ASYMMETRIC VISIBILITY ──────────────────
class Counter
{
    public private(set) int $count = 0;
    // Outside: can read. Cannot write. Inside: can do both.

    public function increment(): void { $this->count++; }
}

// ── 3. NEW ARRAY FUNCTIONS ────────────────────
$arr = [3, 1, 4, 1, 5, 9, 2, 6];

array_find($arr, fn($v) => $v > 4);      // 5
array_find_key($arr, fn($v) => $v > 4);  // 4
array_any($arr, fn($v) => $v > 8);       // true
array_all($arr, fn($v) => $v > 0);       // true

// ── 4. LAZY OBJECTS (Ghost Proxy) ─────────────
$ref = new ReflectionClass(HeavyService::class);
$lazy = $ref->newLazyGhost(function(HeavyService $obj) {
    $obj->__construct();  // only called on first property access
});

// ── 5. HTML5 ENCODING (new in 8.4) ────────────
htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5);
✨ PHP 8.4 Property Hooks

Property hooks are inspired by C# and Kotlin. They allow validation, transformation, and computed properties directly on class properties — no separate getters/setters needed.

13
⚡ Chapter 13 · PHP 8.5

PHP 8.5 Features

Released November 20, 2025. The Pipe Operator, URI Extension, Clone With, #[\NoDiscard], and more — a landmark release for developer ergonomics.

📅 Released Nov 20, 2025
🔒 Active until Dec 2027
Latest Stable
⚡ PHP 8.5.x — Released Nov 20, 2025 · Latest Stable

1. Pipe Operator |>

The biggest new syntax in 8.5. The pipe operator chains callables left-to-right, eliminating deeply nested function calls. Each callable receives the output of the previous one as its first argument.

pipe-operator.php
// Before PHP 8.5 — deeply nested, read right-to-left
$result = strtolower(
    str_replace(['.', '/'], '',
        str_replace(' ', '-',
            trim($input)
        )
    )
);

// PHP 8.5 — pipe operator, reads left-to-right
$result = $input
    |> trim(...)
    |> fn(string $s) => str_replace(' ', '-', $s)
    |> fn(string $s) => str_replace(['.', '/'], '', $s)
    |> strtolower(...);

// Real-world example: process a request body
$data = file_get_contents('php://input')
    |> json_decode(..., true)
    |> fn($d) => array_filter($d, 'strlen')
    |> array_values(...);

2. URI Extension

PHP 8.5 ships a built-in URI extension for parsing, validating, and manipulating URLs following RFC 3986 and WHATWG URL standards — no more fragile parse_url() arrays. OPcache is now always compiled in.

uri-extension.php
use Uri\Rfc3986\Uri;
use Uri\WhatWg\Url;

// Before — parse_url() returns a messy array
$parts = parse_url('https://php.net/releases/8.5/en.php');
echo $parts['host']; // "php.net"

// PHP 8.5 — immutable URI object, fluent and typed
$uri = new Uri('https://php.net/releases/8.5/en.php');

$uri->getScheme();   // "https"
$uri->getHost();     // "php.net"
$uri->getPath();     // "/releases/8.5/en.php"
$uri->getQuery();    // null

// Immutable modification — returns new URI
$newUri = $uri->withPath('/downloads')
              ->withQuery('tab=stable');
// "https://php.net/downloads?tab=stable"

// WHATWG URL standard (browser-compatible parsing)
$url = new Url('https://example.com/path?key=value#section');
$url->getFragment();  // "section"

3. Clone With (Wither Pattern)

Clone an object and override specific properties in a single expression. This was previously a lot of boilerplate with readonly classes — now it's one clean call.

clone-with.php
final readonly class Book
{
    public function __construct(
        public string $title,
        public string $author,
        public int    $year,
    ) {}
}

$original = new Book('PHP Internals', 'Nikita', 2023);

// Before PHP 8.5 — manual wither methods (lots of boilerplate)
public function withTitle(string $title): static
{
    $clone = clone $this;
    $clone->title = $title;
    return $clone;
}

// PHP 8.5 — clone() with property overrides, zero boilerplate
$updated = clone($original, [
    'title' => 'PHP Mastery 2026',
    'year'  => 2026,
]);
// $updated->author is still 'Nikita'
// $original is unchanged (immutable)

4. #[\NoDiscard] Attribute

Mark functions whose return values must not be ignored. PHP emits a warning if the caller discards the return value — prevents silent bugs like ignoring an error status.

no-discard.php
// Mark function — return value must be used
#[\NoDiscard("some items may fail silently")]
function bulkProcess(array $items): array
{
    // Returns array of failures — ignoring it hides errors
    return processAll($items);
}

bulkProcess($items);           // ⚠️ Warning: return value ignored
$failures = bulkProcess($items); // ✅ OK
(void) bulkProcess($items);    // ✅ Explicit discard — suppresses warning

// Works on methods too
class Builder
{
    #[\NoDiscard]
    public function build(): Product
    {
        return new Product($this->data);
    }
}

$builder->build();              // ⚠️ Warning — you forgot to use the result!
$product = $builder->build();  // ✅ Correct

5. New Array Functions

array_first() and array_last() complement the PHP 7.3 key functions — finally a clean way to grab the first or last value of any array without boilerplate.

array-first-last.php
$users = ['Alice', 'Bob', 'Carol'];

// Before — ugly workarounds
$first = reset($users);          // mutates internal pointer
$last  = end($users);            // mutates internal pointer
$first = $users[array_key_first($users)] ?? null; // verbose

// PHP 8.5 — clean and intention-revealing
$first = array_first($users);    // 'Alice'
$last  = array_last($users);     // 'Carol'

// Works with associative arrays too
$config = ['host' => 'localhost', 'port' => 3306, 'db' => 'myapp'];
array_first($config);             // 'localhost'
array_last($config);              // 'myapp'

// Returns null for empty arrays (safe!)
array_first([]);                   // null

6. Closures in Constant Expressions

Static closures and first-class callables can now be used in constant expressions — useful for attribute parameters and compile-time configuration.

const-closures.php
// PHP 8.5 — closures valid in constant expressions
const TRANSFORM = static fn(string $s) => strtoupper(trim($s));
const STRLEN    = strlen(...);

// Use in attribute parameters
#[Validate(transform: static fn($v) => trim($v))]
class UserDto { /*...*/ }

// Combine with pipe operator
$result = "  Hello World  "
    |> TRANSFORM  // "HELLO WORLD"
    |> STRLEN;    // 11
⚡ PHP 8.5 Summary

Released Nov 20, 2025. Active support until Dec 31, 2027. Security fixes until Dec 31, 2029. OPcache is now always compiled in (enablement still controlled by INI). Install via brew install [email protected] on macOS or the official Docker image php:8.5-fpm.

13
📋 Chapter 13 · PHP

Forms & Validation

Handle HTML forms securely in PHP — reading GET/POST data, validating inputs, and protecting against XSS and CSRF.

contact-form.php
<?php
declare(strict_types=1);

// Read POST data safely
$name  = trim($_POST['name']  ?? '');
$email = trim($_POST['email'] ?? '');
$age   = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);

$errors = [];

// Validate
if (strlen($name) < 2) {
    $errors[] = 'Name must be at least 2 characters';
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = 'Invalid email address';
}

if ($age === false || $age < 0) {
    $errors[] = 'Age must be a positive number';
}

// CSRF protection
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
    http_response_code(403);
    die('Invalid CSRF token');
}

if (empty($errors)) {
    // Safe to process
    // Always escape output!
    echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
}
14
🍪 Chapter 14 · PHP

Sessions & Cookies

PHP sessions for user state, secure cookie configuration, and the recommended session settings for 2026 production apps.

sessions.php
// Secure session config (set before session_start)
ini_set('session.cookie_httponly', '1');   // no JS access
ini_set('session.cookie_secure', '1');    // HTTPS only
ini_set('session.cookie_samesite', 'Lax'); // CSRF protection
ini_set('session.use_strict_mode', '1');  // reject unknown IDs

session_start();

// Store user in session after login
$_SESSION['user_id']   = 42;
$_SESSION['logged_in'] = true;
$_SESSION['csrf']      = bin2hex(random_bytes(32));

// Read session
if ($_SESSION['logged_in'] ?? false) {
    echo "Welcome back!";
}

// Regenerate ID on privilege change (prevents session fixation)
session_regenerate_id(true);

// Destroy session on logout
session_unset();
session_destroy();

// Secure cookies
setcookie('remember_token', $token, [
    'expires'  => time() + 30 * 24 * 3600,
    'path'     => '/',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
15
📡 Chapter 15 · PHP

JSON & REST APIs

Build and consume REST APIs with PHP — json_encode/decode, php84's json_validate(), HTTP clients, and serving JSON responses.

api.php
// Build a simple JSON API endpoint
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

function respond(mixed $data, int $code = 200): never
{
    http_response_code($code);
    echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

// Parse incoming JSON body
$body = file_get_contents('php://input');

// PHP 8.3: json_validate() — check without decoding
if (!json_validate($body)) {
    respond(['error' => 'Invalid JSON'], 400);
}

$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

// Consume an external API with cURL
$ch = curl_init('https://api.example.com/users');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $token],
    CURLOPT_TIMEOUT        => 10,
]);
$response = curl_exec($ch);
$users = json_decode($response, true);

respond(['users' => $users, 'total' => count($users)]);
16
🔐 Chapter 16 · PHP

Security Best Practices

The essential PHP security checklist for 2026 — XSS, SQL injection, CSRF, password hashing, and secure headers.

security.php
// ── XSS: Always escape output ────────────────
function e(string $s): string
{
    return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
echo e($userInput);  // safe!

// ── Passwords: Argon2id ───────────────────────
$hash = password_hash($password, PASSWORD_ARGON2ID);
password_verify($input, $hash);        // bool
password_needs_rehash($hash, PASSWORD_ARGON2ID); // bool

// ── Secure random ─────────────────────────────
$token    = bin2hex(random_bytes(32));   // 64-char hex token
$otp      = random_int(100000, 999999);  // 6-digit OTP

// ── Secure HTTP headers ───────────────────────
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Content-Security-Policy: default-src \'self\'');
header('Referrer-Policy: strict-origin-when-cross-origin');

// ── Rate limiting (simple in-memory example) ──
session_start();
$attempts = $_SESSION['login_attempts'] ?? 0;
if ($attempts >= 5) {
    http_response_code(429);
    die('Too many attempts. Try again later.');
}
⚠️ PHP Security Checklist

strict_types=1 everywhere · ✅ Prepared statements for all SQL · ✅ htmlspecialchars() all output · ✅ Argon2id for passwords · ✅ CSRF tokens on all forms · ✅ Secure session config · ✅ HTTPS everywhere · ✅ Security headers

17
🔢 PHP · Numbers

Numbers & Math

Integers, floats, arbitrary precision with BCMath, and PHP's extensive math function library — everything you need for numeric work.

numbers.php
// Integer literals
$dec  = 255;
$hex  = 0xFF;      // 255
$oct  = 0377;      // 255
$bin  = 0b11111111; // 255
$big  = 1_000_000; // underscore separator (PHP 7.4+)

// Float
$pi   = 3.14159;
$sci  = 1.5e3;    // 1500.0
PHP_INT_MAX;      // 9223372036854775807 on 64-bit
PHP_FLOAT_EPSILON; // smallest distinguishable float

// Math functions
abs(-42);              // 42
round(3.567, 2);      // 3.57
ceil(4.1);             // 5
floor(4.9);            // 4
max(1, 5, 3);          // 5
min(1, 5, 3);          // 1
pow(2, 10);            // 1024
sqrt(144);             // 12.0
intdiv(7, 2);          // 3  (integer division)
fmod(7.5, 2.5);        // 0.0
random_int(1, 100);    // cryptographically secure

// Arbitrary precision — for money, never use float!
bcadd('0.1', '0.2', 10);     // "0.3000000000"
bcmul('12.50', '100', 2);    // "1250.00"
bcdiv('10', '3', 4);          // "3.3333"

// Number formatting
number_format(1234567.891, 2, '.', ','); // "1,234,567.89"
18
➕ PHP · Operators

Operators

All PHP operators: arithmetic, string, comparison (== vs ===), logical, bitwise, spaceship <=>, and the spread ... operator.

operators.php
// Arithmetic
10 + 3   // 13
10 - 3   // 7
10 * 3   // 30
10 / 3   // 3.333...
10 % 3   // 1  (modulo)
2 ** 8   // 256 (exponentiation)

// String
"Hello" . " World"  // "Hello World" (concatenation)
$s .= "!";            // append

// Comparison — ALWAYS prefer strict === and !==
0  == "foo"   // true  ⚠️ loose — dangerous!
0  === "foo"  // false ✅ strict
1  != "1"    // false (loose)
1  !== "1"   // true  (strict)

// Spaceship — returns -1, 0, or 1
1 <=> 2   // -1 (left smaller)
2 <=> 2   // 0  (equal)
3 <=> 2   // 1  (left larger)

// Useful for usort:
usort($items, fn($a, $b) => $a->price <=> $b->price);

// Logical
true  && false  // false  (and)
true  || false  // true   (or)
!true           // false  (not)

// Null coalescing assignment
$config['debug'] ??= false;

// Spread operator
function sum(int ...$ns): int { return array_sum($ns); }
sum(...[1,2,3]);  // 6 — unpack array as args
⚠️ == vs ===

PHP's loose comparison == does type coercion with surprising results. Always use strict === unless you explicitly need type juggling. With strict_types=1, function arguments won't coerce — but comparisons still can.

19
📅 PHP · Date & Time

Date & Time

PHP's DateTime, DateTimeImmutable, DateInterval, and DateTimeZone — the right way to handle dates in 2026 without timestamp arithmetic bugs.

datetime.php
// Always use DateTimeImmutable (mutations return new object)
$now  = new DateTimeImmutable();
$date = new DateTimeImmutable('2026-03-15 14:30:00');
$utc  = new DateTimeImmutable('now', new DateTimeZone('UTC'));

// Format output
$date->format('Y-m-d');       // "2026-03-15"
$date->format('D, d M Y');    // "Sun, 15 Mar 2026"
$date->format('H:i:s');       // "14:30:00"
$date->format('c');           // ISO 8601
$date->format('U');           // Unix timestamp

// Arithmetic — immutable returns new instance
$tomorrow    = $date->modify('+1 day');
$nextMonth   = $date->modify('+1 month');
$inTwoWeeks  = $date->add(new DateInterval('P2W'));

// Difference between two dates
$start = new DateTimeImmutable('2026-01-01');
$end   = new DateTimeImmutable('2026-12-31');
$diff  = $start->diff($end);
echo $diff->days;            // 364
echo $diff->m;              // months

// Parse from string
$date = DateTimeImmutable::createFromFormat('d/m/Y', '15/03/2026');

// Timezone conversion
$ny  = new DateTimeImmutable('now', new DateTimeZone('America/New_York'));
$utc = $ny->setTimezone(new DateTimeZone('UTC'));

// Legacy date() — still widely used
date('Y-m-d');         // today's date
time();                // Unix timestamp
strtotime('+1 week'); // timestamp for next week
💡 Always use DateTimeImmutable

Unlike DateTime, DateTimeImmutable never modifies itself — all methods return a new object. This prevents subtle bugs where passing a date to a function changes it in the caller.

20
🔍 PHP · Regex

Regular Expressions

PHP uses PCRE (Perl Compatible Regular Expressions). Master preg_match, preg_replace, preg_split, and named capture groups for real-world string parsing.

regex.php
// preg_match — test if pattern matches
$email = '[email protected]';
if (preg_match('/^[\w.+-]+@[\w-]+\.[a-z]{2,}$/i', $email)) {
    echo "Valid email";
}

// Capture groups
preg_match('/(\d{4})-(\d{2})-(\d{2})/', '2026-03-15', $m);
echo $m[1]; // "2026" (year)
echo $m[2]; // "03"   (month)

// Named capture groups — much clearer
preg_match('/(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/', '2026-03-15', $m);
echo $m['year'];  // "2026"
echo $m['month']; // "03"

// preg_match_all — all occurrences
preg_match_all('/\d+/', 'Order 123, item 456, qty 7', $matches);
// $matches[0] = ["123", "456", "7"]

// preg_replace — replace matches
$safe = preg_replace('/[^a-zA-Z0-9\-]/', '', $input);
$slug = preg_replace('/\s+/', '-', strtolower($title));

// preg_replace_callback — dynamic replacement
$result = preg_replace_callback('/\{(\w+)\}/', function($m) use ($data) {
    return $data[$m[1]] ?? $m[0];
}, '{name} is {age} years old');

// preg_split — split by pattern
$words = preg_split('/[\s,;]+/', 'one,two; three  four');
// ["one","two","three","four"]
21
📁 PHP · File System

File System

Read, write, and manage files and directories. Covers include/require, file I/O, SPL file classes, and secure file upload handling.

filesystem.php
// ── include / require ─────────────────────────
require      'config.php';       // fatal error if missing
require_once 'functions.php';   // only include once
include      'sidebar.php';     // warning only if missing
include_once 'header.php';     // once + warning

// __DIR__ — safe absolute path (never use relative)
require_once __DIR__ . '/vendor/autoload.php';

// ── Read files ────────────────────────────────
$content = file_get_contents('/path/to/file.txt');
$lines   = file('/path/to/file.txt', FILE_IGNORE_NEW_LINES);

// ── Write files ───────────────────────────────
file_put_contents('/tmp/log.txt', "Log entry\n", FILE_APPEND);

// ── Low-level file handle ─────────────────────
$fp = fopen('/tmp/data.csv', 'r');
while (($row = fgetcsv($fp)) !== false) {
    // process $row array
}
fclose($fp);

// ── Directory operations ──────────────────────
is_file('/path/to/file');
is_dir('/path/to/dir');
file_exists('/path');
mkdir('/tmp/newdir', 0755, true);   // recursive
unlink('/tmp/file.txt');             // delete file
rename('/tmp/old', '/tmp/new');

// ── Secure file upload ────────────────────────
if ($_FILES['photo']['error'] === UPLOAD_ERR_OK) {
    $info = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name']);
    $allowed = ['image/jpeg', 'image/png', 'image/webp'];
    if (in_array($info, $allowed, true)) {
        $dest = __DIR__ . '/uploads/' . bin2hex(random_bytes(16)) . '.jpg';
        move_uploaded_file($_FILES['photo']['tmp_name'], $dest);
    }
}
⚠️ File Upload Security

Never trust $_FILES['file']['type'] — it comes from the browser and can be spoofed. Always detect the real MIME type server-side using finfo_file(), validate extension, and store files outside the webroot or with randomized names.

22
⚠️ PHP · Error Handling

Exceptions & Error Handling

try/catch/finally, custom exception classes, multiple catch blocks, the exception hierarchy, and global exception handlers — PHP error handling done right.

exceptions.php
// ── Basic try / catch / finally ───────────────
try {
    $pdo = new PDO($dsn, $user, $pass);
    $result = riskyOperation();
} catch (PDOException $e) {
    error_log("DB error: " . $e->getMessage());
    throw new RuntimeException("Database unavailable", 0, $e);
} catch (InvalidArgumentException | RangeException $e) {
    // catch multiple exception types in one block
    echo "Bad input: " . $e->getMessage();
} finally {
    // always runs — cleanup resources
    closeConnection();
}

// ── Custom exception hierarchy ────────────────
class AppException extends RuntimeException {}

class ValidationException extends AppException
{
    public function __construct(
        private readonly array $errors,
        string $message = 'Validation failed',
    ) {
        parent::__construct($message);
    }

    public function getErrors(): array { return $this->errors; }
}

// Throw custom exception
if (empty($email)) {
    throw new ValidationException(['email' => 'Required']);
}

// ── PHP 8: throw as expression ─────────────────
$user = findUser($id)
    ?? throw new NotFoundException("User {$id} not found");

// ── Global exception handler ───────────────────
set_exception_handler(function(Throwable $e): never {
    error_log($e->getMessage() . "\n" . $e->getTraceAsString());
    http_response_code(500);
    echo json_encode(['error' => 'Internal Server Error']);
    exit(1);
});

// Exception hierarchy cheat sheet:
// Throwable (interface)
//  ├── Error (PHP internal errors)
//  │    ├── TypeError, ValueError, ParseError...
//  └── Exception (user-land)
//       ├── RuntimeException
//       │    ├── PDOException, OverflowException...
//       └── LogicException
//            ├── InvalidArgumentException...
23
📦 PHP · Namespaces

Namespaces & Autoloading

Namespaces prevent name collisions and map to directory structure via PSR-4. Composer autoloading means you never manually require a class again.

src/App/Models/User.php
<?php
declare(strict_types=1);

// Namespace matches directory: App\Models → src/App/Models/
namespace App\Models;

use App\Contracts\HasTimestamps;
use App\Exceptions\ValidationException;
use InvalidArgumentException;            // global namespace

class User implements HasTimestamps
{
    public function __construct(
        public readonly string $email,
    ) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new ValidationException(['email' => 'Invalid']);
        }
    }
}
composer.json + usage
// composer.json — PSR-4 autoloading config
{
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}

// Run: composer dump-autoload
// Then in your entrypoint:
require __DIR__ . '/vendor/autoload.php';

// Now App\Models\User is auto-loaded — no require needed
use App\Models\User;
$user = new User('[email protected]');

// Alias to avoid conflicts
use App\Models\User as UserModel;
use Other\Package\User as OtherUser;

// Namespace separator: backslash \
// Global functions: prepend \ → \strlen(), \array_map()
24
✨ PHP · OOP

Magic Methods

PHP's magic methods give classes special behaviours — stringify, invoke as function, intercept undefined calls, clone, and serialize. Essential for elegant API design.

magic-methods.php
class MagicDemo
{
    private array $data = [];

    // __toString: object → string representation
    public function __toString(): string
    {
        return json_encode($this->data);
    }

    // __get / __set: intercept property access
    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    // __isset / __unset
    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }

    // __call: intercept undefined method calls
    public function __call(string $name, array $args): mixed
    {
        if (str_starts_with($name, 'findBy')) {
            $field = lcfirst(substr($name, 6));
            return $this->findWhere($field, $args[0]);
        }
        throw new BadMethodCallException("Method {$name} not found");
    }

    // __invoke: call object as a function
    public function __invoke(string $query): array
    {
        return $this->search($query);
    }

    // __clone: run code when object is cloned
    public function __clone(): void
    {
        // deep-clone any nested objects here
        $this->data = array_map(fn($v) => is_object($v) ? clone $v : $v, $this->data);
    }
}

$obj = new MagicDemo();
$obj->name  = 'Alice';       // __set
echo $obj->name;             // __get → "Alice"
echo $obj;                   // __toString
$obj->findByEmail('[email protected]');// __call dynamic finder
$obj('search term');         // __invoke
$clone = clone $obj;        // __clone
25
⚡ PHP · OOP

Static Methods, Properties & Late Static Binding

Static members belong to the class, not instances. Late Static Binding (static::) ensures the correct class is used in inheritance chains — critical for fluent builder patterns.

static-lsb.php
class Registry
{
    // Static property — shared by ALL instances
    private static array $instances = [];

    // Static method — call on class, not instance
    public static function register(string $key, mixed $value): void
    {
        static::$instances[$key] = $value;
    }

    public static function get(string $key): mixed
    {
        return static::$instances[$key] ?? null;
    }
}

// Late Static Binding — factory pattern in base class
class Model
{
    public static function make(array $attrs): static
    {
        // static:: resolves to the CALLING class at runtime
        // self:: would always resolve to Model
        $instance = new static();
        foreach ($attrs as $k => $v) { $instance->$k = $v; }
        return $instance;
    }

    public static function className(): string
    {
        return static::class;  // returns child class name
    }
}

class Post extends Model {}

$post = Post::make(['title' => 'Hello']); // Post instance ✅
Post::className(); // "Post" — not "Model"  ✅

// Singleton pattern
class Config
{
    private static ?static $instance = null;
    private function __construct() {}

    public static function getInstance(): static
    {
        return static::$instance ??= new static();
    }
}
26
⚙️ PHP · Advanced

Generators

Generators produce values on-demand with yield — perfect for processing huge datasets without loading everything into memory at once.

generators.php
// Basic generator — yields values lazily
function fibonacci(): Generator
{
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

$fib = fibonacci();
for ($i = 0; $i < 10; $i++) {
    echo $fib->current() . " ";  // 0 1 1 2 3 5 8 13 21 34
    $fib->next();
}

// Process large CSV without loading it all in memory
function readCsvRows(string $path): Generator
{
    $fp = fopen($path, 'r');
    $headers = fgetcsv($fp);
    while (false !== ($row = fgetcsv($fp))) {
        yield array_combine($headers, $row);
    }
    fclose($fp);
}

foreach (readCsvRows('data.csv') as $row) {
    // process one row at a time — constant memory!
}

// yield key => value
function indexedValues(): Generator
{
    yield 'first'  => 1;
    yield 'second' => 2;
    yield 'third'  => 3;
}

foreach (indexedValues() as $key => $val) {
    echo "{$key}: {$val}\n";
}

// yield from — delegate to another generator
function combined(): Generator
{
    yield from [1, 2, 3];
    yield from fibonacci();
}
💡 Memory Efficiency

A regular function returning array_map over 1 million rows allocates ~100 MB. A generator yields each row one at a time, using only a few KB. Use generators anywhere you'd otherwise return a giant array.

27
📦 PHP · Ecosystem

Composer & Packages

Composer is PHP's dependency manager — the single tool that transformed PHP development. Install packages, manage versions, and set up PSR-4 autoloading in minutes.

terminal + composer.json
# Install Composer globally
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

# Create a new project
composer init
composer require guzzlehttp/guzzle     # HTTP client
composer require vlucas/phpdotenv      # .env files
composer require --dev pestphp/pest   # testing (dev only)

# Update / remove
composer update
composer remove package/name

# Regenerate autoloader
composer dump-autoload --optimize
composer.json
{
  "name": "myapp/api",
  "require": {
    "php": "^8.5",
    "guzzlehttp/guzzle": "^7.0",
    "vlucas/phpdotenv": "^5.6"
  },
  "require-dev": {
    "pestphp/pest": "^2.0"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "scripts": {
    "test": "./vendor/bin/pest",
    "lint": "./vendor/bin/phpstan analyse"
  }
}
Must-know Composer packages
// HTTP Client
composer require guzzlehttp/guzzle
composer require symfony/http-client

// Environment variables
composer require vlucas/phpdotenv

// Database / ORM
composer require doctrine/dbal
composer require illuminate/database   // Laravel Eloquent standalone

// Template engine
composer require twig/twig

// Static analysis
composer require --dev phpstan/phpstan

// Testing
composer require --dev pestphp/pest
composer require --dev phpunit/phpunit

// Code style
composer require --dev laravel/pint
28
🧪 PHP · Testing

Testing with Pest

Pest is the modern PHP testing framework — expressive, elegant, and built on top of PHPUnit. Write readable tests that serve as living documentation.

tests/UserTest.php
use App\Models\User;
use App\Exceptions\ValidationException;

// Pest — readable, expressive tests
test('creates a valid user', function() {
    $user = new User('[email protected]');
    expect($user->email)->toBe('[email protected]');
});

test('throws on invalid email', function() {
    expect(fn() => new User('not-an-email'))
        ->toThrow(ValidationException::class);
});

// Describe blocks for grouping
describe('User model', function() {
    beforeEach(function() {
        $this->user = new User('[email protected]');
    });

    it('has the correct email', function() {
        expect($this->user->email)->toContain('@');
    });
});

// Data providers — test multiple cases
test('validates emails', function(string $email, bool $valid) {
    $result = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    expect($result)->toBe($valid);
})->with([
    ['[email protected]', true],
    ['not-an-email',      false],
    ['@nodomain.com',     false],
]);

# Run tests:
# ./vendor/bin/pest
# ./vendor/bin/pest --coverage
01
🐬 MySQL Chapter 1

What is MySQL?

MySQL is the world's most popular open-source relational database — the data layer behind WordPress, Shopify, GitHub, and billions of applications.

5 min
🎯 Beginner
🐬 MySQL 8.4 / 9.0

Relational Databases in 60 Seconds

A relational database stores data in tables (like spreadsheets). Tables have columns (the schema) and rows (the data). Tables can relate to each other via foreign keys.

idnameemailcreated_at
1Alice[email protected]2026-01-01
2Bob[email protected]2026-01-02
3Carol[email protected]2026-01-15

Blazing Fast

InnoDB engine handles millions of rows with millisecond queries

🔒

ACID Transactions

Data integrity guaranteed even during crashes and failures

🔗

PHP Native

PHP PDO + MySQL is the classic web stack with deep integration

🆓

Free & Open

MySQL Community Edition is completely free and open-source

📊

JSON Support

MySQL 8+ has native JSON column type and JSON functions

🌍

Everywhere

Available on every cloud, VPS, and shared hosting provider

🐬 MySQL vs MariaDB

MariaDB is a community fork of MySQL with identical SQL syntax. All queries in this course work on both. Many Linux distros default to MariaDB — they're interchangeable for learning.

02
🔧 MySQL Chapter 2

Install & Connect

Get MySQL 8.4 running locally with the best tools for 2026 — from Homebrew to Docker to TablePlus.

macOS (Homebrew)

terminal
brew install mysql
brew services start mysql
mysql_secure_installation
mysql -u root -p

Ubuntu / Debian

terminal
sudo apt update && sudo apt install mysql-server
sudo systemctl start mysql
sudo mysql_secure_installation
sudo mysql -u root -p

Docker

docker-compose.yml
services:
  mysql:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppass
    ports: ["3306:3306"]
    volumes: ["mysql_data:/var/lib/mysql"]
volumes:
  mysql_data:

Recommended GUI Tools

ToolPlatformBest For
TablePlusMac/Win/LinuxBest UI in 2026, free tier
phpMyAdminWeb browserQuick access, bundled with XAMPP
DBeaverAll platformsFree, supports all databases
MySQL WorkbenchAll platformsOfficial Oracle tool
03
📊 MySQL Chapter 3

Databases & Tables

Create and manage databases and tables — the fundamental building blocks. Covers CREATE, ALTER, DROP, and the InnoDB engine.

schema.sql
-- Create database with UTF8MB4 (supports emoji)
CREATE DATABASE myapp
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

USE myapp;

-- Create a complete users table
CREATE TABLE users (
    id         INT UNSIGNED     NOT NULL AUTO_INCREMENT,
    name       VARCHAR(100)  NOT NULL,
    email      VARCHAR(255)  NOT NULL,
    password   VARCHAR(255)  NOT NULL,
    role       ENUM('user', 'admin') NOT NULL DEFAULT 'user',
    is_active  TINYINT(1)     NOT NULL DEFAULT 1,
    created_at TIMESTAMP        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP        NOT NULL DEFAULT CURRENT_TIMESTAMP
               ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY  uk_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Modify existing table
ALTER TABLE users ADD COLUMN    bio        TEXT NULL AFTER email;
ALTER TABLE users MODIFY COLUMN name       VARCHAR(200) NOT NULL;
ALTER TABLE users DROP COLUMN   bio;

-- Inspect
DESCRIBE users;
SHOW CREATE TABLE users;
SHOW TABLES;
🐬 Always use utf8mb4

The older utf8 charset in MySQL is actually 3-byte and cannot store emoji or many Unicode characters. Always use utf8mb4 for new tables.

04
🗃️ MySQL Chapter 4

MySQL Data Types

Choosing the right type affects storage size, query performance, and data integrity. Here's the complete 2026 guide to MySQL types.

Numeric Types

TypeStorageRangeUse For
TINYINT1 byte-128 to 127Boolean flags (0/1), tiny counts
INT4 bytes±2.1 billionIDs, general counts
BIGINT8 bytes±9.2 quintillionLarge IDs, Unix timestamps
DECIMAL(p,s)VariableExactMoney & finance — always use this
FLOAT / DOUBLE4/8 bytesApproximateScientific data only — not money!

String Types

TypeMaxUse For
CHAR(n)255 charsFixed-length: country codes, hashes
VARCHAR(n)65,535 charsVariable: names, emails, titles
TEXT65 KBShort articles, descriptions
MEDIUMTEXT16 MBBlog posts, large content
JSON4 GBSemi-structured data (MySQL 8+)

Date & Time

types-example.sql
CREATE TABLE events (
    event_date  DATE,       -- '2026-01-15'
    start_time  TIME,       -- '14:30:00'
    scheduled   DATETIME,   -- '2026-01-15 14:30:00' no TZ
    created_at  TIMESTAMP   DEFAULT CURRENT_TIMESTAMP,
    --         ↑ auto-converts to UTC, changes on updates
    price       DECIMAL(10,2),  -- 99999999.99 max
    metadata    JSON        -- {"key":"value"} native JSON
);
⚠️ Never store money in FLOAT

Binary floating point causes rounding errors: 0.1 + 0.2 ≠ 0.3 in binary. Always use DECIMAL(10,2) for currency.

05
🔍 MySQL Chapter 5

SELECT Queries

The SELECT statement is the most important in SQL. Master filtering with WHERE, sorting with ORDER BY, pagination with LIMIT/OFFSET, and powerful pattern matching.

select.sql
-- Basic SELECT
SELECT id, name, email FROM users;
SELECT name AS full_name, email AS contact FROM users;

-- WHERE with operators
SELECT * FROM users
WHERE is_active = 1
  AND role = 'admin';

-- Comparison, BETWEEN, IN, LIKE
WHERE age BETWEEN 18 AND 65
WHERE role IN ('admin', 'moderator')
WHERE email LIKE '%@gmail.com'
WHERE deleted_at IS NULL

-- ORDER BY + LIMIT (pagination)
SELECT * FROM users
ORDER BY created_at DESC, name ASC
LIMIT 10 OFFSET 20;  -- page 3 (10/page)

-- Subquery
SELECT * FROM users
WHERE id IN (
    SELECT user_id FROM orders
    WHERE total > 1000
);

-- Full-text search (requires FULLTEXT index)
WHERE MATCH(title, body) AGAINST('mysql tutorial' IN BOOLEAN MODE);
✦ Quick Check
To get rows 21–30, what LIMIT/OFFSET would you use (10 rows per page)?
06
✏️ MySQL Chapter 6

INSERT / UPDATE / DELETE

Write data to MySQL — inserting single and bulk rows, upserts, updating records, soft deletes, and safety rules that protect your data.

write.sql
-- INSERT single row
INSERT INTO users (name, email, password)
VALUES ('Alice', '[email protected]', 'hashed');

LAST_INSERT_ID();  -- get new auto-increment ID

-- Bulk INSERT (much faster than single inserts)
INSERT INTO users (name, email, password) VALUES
  ('Bob',   '[email protected]',   'hash2'),
  ('Carol', '[email protected]', 'hash3'),
  ('Dave',  '[email protected]',  'hash4');

-- Upsert: insert or update on duplicate key
INSERT INTO user_stats (user_id, login_count)
VALUES (42, 1)
ON DUPLICATE KEY UPDATE login_count = login_count + 1;

-- UPDATE (ALWAYS include WHERE!)
UPDATE users
SET   name = 'Alice Smith',
      updated_at = NOW()
WHERE id = 1;

-- Soft delete (preferred — keeps data history)
UPDATE users
SET   deleted_at = NOW()
WHERE id = 42;

-- Hard delete
DELETE FROM users WHERE id = 42;

-- TRUNCATE: delete all rows, keep structure
TRUNCATE TABLE temp_logs;
⚠️ Always WHERE on UPDATE/DELETE

Omitting WHERE from an UPDATE or DELETE affects every row in the table. Always test with a SELECT using the same WHERE clause first.

07
🔗 MySQL Chapter 7

JOINs

JOINs combine data from multiple tables. They are the heart of relational databases — master JOINs and you master SQL.

TypeReturns
INNER JOINOnly rows that match in both tables
LEFT JOINAll rows from left + matching from right (NULL if no match)
RIGHT JOINAll rows from right + matching from left
joins.sql
-- INNER JOIN: posts with their author (no orphans)
SELECT
    p.id, p.title,
    u.name AS author
FROM posts p
INNER JOIN users u ON p.user_id = u.id;

-- LEFT JOIN: ALL users, even those with no posts
SELECT
    u.name,
    COUNT(p.id) AS post_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id, u.name;

-- Multi-table JOIN
SELECT
    p.title,
    u.name  AS author,
    c.name  AS category
FROM       posts       p
INNER JOIN users       u  ON p.user_id     = u.id
INNER JOIN categories  c  ON p.category_id = c.id
WHERE p.published_at IS NOT NULL
ORDER BY p.published_at DESC
LIMIT 10;

-- Self JOIN: employees and their managers
SELECT
    e.name AS employee,
    m.name AS manager
FROM      employees e
LEFT JOIN employees m ON e.manager_id = m.id;
08
📈 MySQL Chapter 8

Aggregate & GROUP BY

COUNT, SUM, AVG, MIN, MAX — turn raw rows into analytics. Combine with GROUP BY and HAVING to build dashboards and reports.

aggregate.sql
-- Aggregate functions
SELECT
    COUNT(*)            AS total_users,
    COUNT(deleted_at)   AS deleted,      -- NULLs excluded!
    MIN(created_at)     AS first_signup,
    MAX(created_at)     AS latest_signup
FROM users;

-- SUM + AVG for orders
SELECT
    SUM(amount)         AS total_revenue,
    AVG(amount)         AS avg_order_value,
    MAX(amount)         AS largest_order
FROM orders
WHERE status = 'completed';

-- GROUP BY: stats per category
SELECT
    role,
    COUNT(*) AS count
FROM users
GROUP BY role
ORDER BY count DESC;

-- HAVING: filter groups (WHERE works on rows, HAVING on groups)
SELECT
    user_id,
    COUNT(*) AS orders,
    SUM(amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING total_spent > 1000
ORDER BY total_spent DESC;

-- Monthly signups report
SELECT
    DATE_FORMAT(created_at, '%Y-%m') AS month,
    COUNT(*) AS signups
FROM users
GROUP BY month
ORDER BY month DESC;
ℹ️ WHERE vs HAVING

WHERE filters rows before grouping. HAVING filters groups after GROUP BY. You can use both in the same query for maximum power.

09
⚡ MySQL Chapter 9

Indexes & Performance

Indexes are the biggest lever for MySQL performance. A single index can turn a 10-second query into 1 millisecond.

Without an index, MySQL does a full table scan — reading every row. With an index (a B-tree structure), it jumps directly to matching rows. Add indexes on columns you filter or sort by.

indexes.sql
-- Single column index
CREATE INDEX idx_users_email ON users(email);

-- Unique index (also enforces uniqueness)
CREATE UNIQUE INDEX uk_email ON users(email);

-- Composite index — column order matters!
-- Good for: WHERE user_id = ? AND date > ?
CREATE INDEX idx_posts_user_date ON posts(user_id, published_at);

-- Full-text index for MATCH...AGAINST
CREATE FULLTEXT INDEX ft_posts ON posts(title, body);

-- Inspect indexes
SHOW INDEX FROM users;

-- EXPLAIN: see if your query uses indexes
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';
-- Look for: type=ref/eq_ref (good), type=ALL (bad = full scan)
-- key column shows which index MySQL chose

-- EXPLAIN ANALYZE (MySQL 8.0.18+) — actual execution stats
EXPLAIN ANALYZE SELECT * FROM users WHERE email = '[email protected]';
💡 Index Rule of Thumb

Index columns used in WHERE, JOIN ON, and ORDER BY. Don't over-index — each index slows down writes. For composite indexes, put equality columns first, range columns last.

10
🕸️ MySQL Chapter 10

Relationships & Foreign Keys

Design normalized databases. One-to-many, many-to-many, and one-to-one relationships with enforced referential integrity.

TypeExampleImplementation
One-to-ManyUser → PostsFK in child table
Many-to-ManyPosts ↔ TagsPivot/junction table
One-to-OneUser → ProfileFK with UNIQUE constraint
schema-relations.sql
-- One-to-Many: User has many Posts
CREATE TABLE posts (
    id      INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NOT NULL,
    title   VARCHAR(255) NOT NULL,
    FOREIGN KEY (user_id)
        REFERENCES users(id)
        ON DELETE CASCADE   -- posts deleted with user
        ON UPDATE CASCADE
) ENGINE=InnoDB;

-- Many-to-Many: Posts ↔ Tags (pivot table)
CREATE TABLE post_tag (
    post_id INT UNSIGNED NOT NULL,
    tag_id  INT UNSIGNED NOT NULL,
    PRIMARY KEY (post_id, tag_id),
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
    FOREIGN KEY (tag_id)  REFERENCES tags(id)  ON DELETE CASCADE
) ENGINE=InnoDB;

-- Query M2M: posts with comma-separated tags
SELECT
    p.title,
    GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ') AS tags
FROM      posts    p
LEFT JOIN post_tag pt ON p.id    = pt.post_id
LEFT JOIN tags     t  ON pt.tag_id = t.id
GROUP BY p.id;
11
💳 MySQL Chapter 11

Transactions & ACID

Transactions group SQL statements into atomic units — all succeed or all fail. Essential for financial operations, inventory management, and any multi-step operation.

⚛️

Atomic

All statements succeed or all are rolled back — no partial updates

Consistent

Database moves from one valid state to another valid state

🔒

Isolated

Concurrent transactions don't see each other's uncommitted changes

💾

Durable

Once committed, data survives crashes and power failures

transactions.sql
-- Bank transfer: debit one, credit another
START TRANSACTION;

UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;

COMMIT;    -- persist both changes atomically
ROLLBACK;  -- undo everything since START TRANSACTION

-- SAVEPOINTs for partial rollbacks
START TRANSACTION;
INSERT INTO orders (...) VALUES (...);
SAVEPOINT after_order;
INSERT INTO order_items (...) VALUES (...);
ROLLBACK TO after_order;  -- undo items only
COMMIT;                    -- keep the order
12
🔌 PHP + MySQL

PDO & Prepared Statements

PHP Data Objects (PDO) is the modern database interface. Prepared statements are the only safe way to use user data in SQL queries.

database.php
<?php
declare(strict_types=1);

// Create PDO connection
$pdo = new PDO(
    'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
    'user', 'password',
    [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]
);

// SELECT with prepared statement
$stmt = $pdo->prepare(
    'SELECT id, name, email FROM users WHERE id = :id AND is_active = 1'
);
$stmt->execute(['id' => 42]);
$user  = $stmt->fetch();      // one row as array
$users = $stmt->fetchAll();   // all rows

// INSERT
$stmt = $pdo->prepare(
    'INSERT INTO users (name, email, password) VALUES (:name, :email, :pw)'
);
$stmt->execute([
    'name'  => 'Alice',
    'email' => '[email protected]',
    'pw'    => password_hash($password, PASSWORD_ARGON2ID),
]);
$newId = (int) $pdo->lastInsertId();

// Transaction in PHP
$pdo->beginTransaction();
try {
    $pdo->prepare('UPDATE accounts SET balance = balance - :amt WHERE id = :id')
       ->execute(['amt' => 500, 'id' => 1]);

    $pdo->prepare('UPDATE accounts SET balance = balance + :amt WHERE id = :id')
       ->execute(['amt' => 500, 'id' => 2]);

    $pdo->commit();
} catch (Throwable $e) {
    $pdo->rollBack();
    throw $e;
}
🔒 SQL Injection Prevention

NEVER concatenate user input into SQL: "SELECT * FROM users WHERE email = '$email'" is vulnerable. Always use prepared statements with :named placeholders.

13
🏆 PHP + MySQL Capstone

Full CRUD Application

Put everything together — a complete, production-ready Repository pattern with typed classes, PHP 8.4, and MySQL. This is real-world PHP + MySQL.

15 min
🎯 Intermediate
Capstone
UserRepository.php
<?php
declare(strict_types=1);

// Value object — readonly class (PHP 8.2+)
readonly class User
{
    public function __construct(
        public int    $id,
        public string $name,
        public string $email,
        public string $createdAt,
    ) {}
}

class UserRepository
{
    public function __construct(
        private readonly PDO $pdo
    ) {}

    /** @return User[] */
    public function findAll(int $limit = 20, int $offset = 0): array
    {
        $stmt = $this->pdo->prepare('
            SELECT id, name, email, created_at FROM users
            WHERE deleted_at IS NULL
            ORDER BY created_at DESC
            LIMIT :limit OFFSET :offset
        ');
        $stmt->bindValue(':limit',  $limit,  PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        return array_map(fn($r) => new User(...$r), $stmt->fetchAll());
    }

    public function findById(int $id): ?User
    {
        $stmt = $this->pdo->prepare('
            SELECT id, name, email, created_at FROM users
            WHERE id = :id AND deleted_at IS NULL
        ');
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch();
        return $row ? new User(...$row) : null;
    }

    public function create(string $name, string $email, string $password): int
    {
        $stmt = $this->pdo->prepare('
            INSERT INTO users (name, email, password) VALUES (:name, :email, :password)
        ');
        $stmt->execute([
            'name'     => $name,
            'email'    => $email,
            'password' => password_hash($password, PASSWORD_ARGON2ID),
        ]);
        return (int) $this->pdo->lastInsertId();
    }

    public function update(int $id, string $name): bool
    {
        $stmt = $this->pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
        $stmt->execute(['id' => $id, 'name' => $name]);
        return $stmt->rowCount() > 0;
    }

    public function softDelete(int $id): bool
    {
        $stmt = $this->pdo->prepare('UPDATE users SET deleted_at = NOW() WHERE id = :id');
        $stmt->execute(['id' => $id]);
        return $stmt->rowCount() > 0;
    }
}

// Usage (wiring)
$repo  = new UserRepository($pdo);
$users = $repo->findAll(limit: 10);         // named args
$user  = $repo->findById(1);
$id    = $repo->create('Alice', '[email protected]', 'secret');
🎉 Course Complete!

You've finished the full PHP + MySQL course! You know PHP 8.4 with modern OOP, and MySQL from schema design to complex joins, indexes, and transactions. Next step: try Laravel — it wraps all of this in an elegant Eloquent ORM and Query Builder.

Claude — AI Tutor

Ask anything about this lesson

👋 Hi! I'm Claude, your PHP & MySQL tutor. Ask me anything about the current lesson — I can explain concepts, debug code, give examples, or help you understand why something works the way it does.
Quick questions

Claude — AI Tutor

Ask anything about this lesson

👋 Hi! I'm Claude, your PHP & MySQL tutor. Ask me anything about the current lesson — I can explain concepts, debug code, give examples, or help you understand why something works.
Quick questions