// A First Principles Guide

PHP & Apache
From the Ground Up

Everything you need to understand how PHP runs on Apache, how the language works, and how to build dynamic web applications — explained from first principles.

PHP 8.x Apache 2.4 macOS / M-series Beginner Friendly
01

What is PHP?

PHP stands for PHP: Hypertext Preprocessor — a recursive acronym. It is a general-purpose scripting language especially suited to server-side web development. Created by Rasmus Lerdorf in 1993 and first released in 1994, PHP now powers over 75% of all websites with a known server-side language, including WordPress, Wikipedia, and Facebook's original stack.

PHP is different from JavaScript, which runs in the browser. PHP runs on the server — it receives an HTTP request, executes code, and returns HTML (or JSON, XML, etc.) to the client. The browser never sees your PHP source code.

PHP is interpreted at runtime, not compiled. This makes it fast to iterate on but means errors only surface when the code runs.
PHP Strengths
  • Huge ecosystem & community
  • Runs everywhere Apache does
  • Built-in web primitives
  • Low barrier to entry
  • First-class MySQL support
  • Frameworks: Laravel, Symfony
Things to Know
  • Type coercion can surprise you
  • Pre-8.0 had inconsistent APIs
  • Needs careful security hygiene
  • Not ideal for real-time apps
  • Many legacy codebases exist
  • PHP 8.x fixed most old issues
02

How PHP & Apache Work Together

To understand the setup, you need to understand the request lifecycle:

  1. Browser sends HTTP request Your browser requests http://localhost/hello.php.
  2. Apache receives the request Apache (the web server) intercepts the request. It looks at the file extension .php.
  3. Apache hands off to PHP Via mod_php (a module) or PHP-FPM, Apache passes the file to the PHP interpreter.
  4. PHP executes the script PHP reads the .php file, executes code, and generates output (usually HTML).
  5. Apache returns the response Apache sends the PHP-generated HTML back to the browser. The browser renders it.
The browser only ever sees the output of your PHP script — pure HTML. Your PHP code is invisible to users, which is why you can safely put database passwords in PHP files (though environment variables are better practice).

Apache's Role

Apache is the HTTP server — the software that listens on port 80/443, handles incoming connections, maps URLs to files, and enforces rules like access control. Without Apache (or another web server like Nginx), PHP scripts would have no way to receive web requests.

The key Apache module for PHP is mod_php, which embeds the PHP interpreter directly inside Apache. Alternatively, PHP-FPM (FastCGI Process Manager) runs PHP as a separate process pool — this is more modern and used in production.

03

Setup on macOS (Apple Silicon)

macOS ships with Apache, but we recommend using Homebrew for full control. This gives you Apache 2.4 and PHP 8.x that you can update independently.

Option A — Homebrew (Recommended)

bash — Terminal
# 1. Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 2. Install Apache
brew install httpd

# 3. Install PHP 8.3
brew install php

# 4. Start Apache (and enable on login)
brew services start httpd

# 5. Verify PHP is installed
php -v
After running brew install httpd, Apache's config file is at:
/opt/homebrew/etc/httpd/httpd.conf
And the document root (where you put PHP files) defaults to:
/opt/homebrew/var/www

Option B — MAMP (Easiest GUI Option)

Download MAMP Free from mamp.info. It bundles Apache, PHP, and MySQL in a GUI app — one click to start/stop. The document root is /Applications/MAMP/htdocs/. Great for beginners.

Verify Everything Works

bash
# Check Apache is running
curl http://localhost

# Should output: "It works!" or your index page

# Check PHP CLI
php -v
# PHP 8.3.x (cli) ...

# Check PHP info from command line
php -r "echo phpversion();"
04

Apache Configuration for PHP

Apache's main config file is httpd.conf. You need to tell Apache to load the PHP module and process .php files correctly.

Enable PHP in httpd.conf

httpd.conf
# Load the PHP module (path varies by install)
LoadModule php_module /opt/homebrew/lib/httpd/modules/libphp.so

# Tell Apache to process .php files with PHP
<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

# Set document root (where your files live)
DocumentRoot "/opt/homebrew/var/www"

# Allow index.php as a directory index
DirectoryIndex index.php index.html

# Enable .htaccess overrides (for URL rewriting)
<Directory "/opt/homebrew/var/www">
    AllowOverride All
    Require all granted
</Directory>

Key Apache Directives Explained

DirectivePurposeExample
DocumentRootRoot folder served by Apache/opt/homebrew/var/www
DirectoryIndexDefault file for a directoryindex.php index.html
LoadModuleLoads an Apache extensionLoadModule rewrite_module ...
AllowOverrideAllows .htaccess to overrideAllowOverride All
ListenPort Apache listens onListen 8080
ServerNameThe hostname of the serverlocalhost

Using .htaccess

A .htaccess file in any directory lets you override Apache settings for that directory without touching httpd.conf. Essential for URL rewriting (clean URLs).

.htaccess — URL Rewriting Example
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Route all requests to index.php (for MVC frameworks)
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
After editing httpd.conf, always restart Apache: brew services restart httpd
05

Your First PHP Script

Create a file called hello.php in your document root (/opt/homebrew/var/www/) with the following content:

hello.php
<!DOCTYPE html>
<html>
<body>

<?php
  $name = "World";
  echo "<h1>Hello, " . $name . "!</h1>";
  echo "<p>Today is: " . date("l, F j, Y") . "</p>";
?>

</body>
</html>

Visit http://localhost/hello.php in your browser. You should see a greeting with today's date — generated live by PHP on the server.

phpinfo() — Your Best Debugging Friend

Create a file called info.php with just this content. It shows every detail about your PHP + Apache configuration:

info.php
<?php phpinfo(); ?>
Never leave info.php on a production server! It reveals your server configuration, installed modules, and PHP settings to anyone who visits it.

06

PHP Syntax & Tags

PHP code is embedded inside HTML using PHP tags. Everything between the tags is executed by the PHP interpreter; everything outside is sent to the browser as-is.

PHP Tags
// Standard tags (always available, always use these)
<?php  // ... code ...  ?>

// Short echo tag (outputs a value directly)
<?= $variable ?>
// Equivalent to: <?php echo $variable; ?>

// If a file is ONLY PHP, omit the closing ?> tag
// This prevents accidental whitespace output
<?php
class MyClass {
    // ...
}
// No closing ?> needed here

Statements & Semicolons

Every PHP statement ends with a semicolon ;. Missing a semicolon is one of the most common beginner errors.

Comments

comments.php
<?php
// Single-line comment

# Also a single-line comment (shell-style)

/*
 * Multi-line comment
 * Great for documenting functions
 */

/**
 * DocBlock comment — used by IDEs and documentation tools
 * @param string $name  The person's name
 * @return string       A greeting
 */
function greet(string $name): string {
    return "Hello, {$name}!";
}
?>
07

Variables & Data Types

In PHP, variables start with a dollar sign $. PHP is dynamically typed — a variable can hold any type, and the type can change at runtime.

variables.php
<?php
// ── SCALAR TYPES ──────────────────────────────

$name    = "Alice";          // string
$age     = 30;               // integer
$price   = 19.99;            // float
$active  = true;             // boolean
$nothing = null;             // null

// ── TYPE CHECKING ──────────────────────────────
var_dump($age);              // int(30)
gettype($price);            // "double"
is_string($name);          // true
is_null($nothing);         // true

// ── TYPE CASTING ───────────────────────────────
$strNum  = "42";
$intNum  = (int) $strNum;    // 42
$floatN  = (float) $strNum; // 42.0
$boolN   = (bool) $strNum;  // true

// ── TYPE JUGGLING (be careful!) ─────────────────
$result = "3" + 4;           // 7  (PHP converts "3" to int)
$concat = "3" . 4;           // "34" (. is string concat)

// ── CONSTANTS ──────────────────────────────────
define('MAX_SIZE', 100);   // Old style
const VERSION = '1.0.0';   // Preferred in PHP 5.3+

echo MAX_SIZE;               // No $ for constants!
?>

Variable Variables & isset/empty

checking variables
<?php
$username = "alice";

// isset() — true if variable exists AND is not null
if (isset($username)) {
    echo "Username is set";
}

// empty() — true if falsy: "", 0, "0", [], null, false
$value = "";
if (empty($value)) {
    echo "Value is empty";
}

// Nullsafe: PHP 8.0+ null coalescing
$name = $_POST['name'] ?? 'Guest'; // default if null/unset
?>
08

Operators

OperatorNameExampleResult
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division10 / 33.333...
%Modulo10 % 31
**Exponentiation2 ** 8256
.String concat"Hi" . " " . "there""Hi there"
==Loose equal"1" == 1true
===Strict equal"1" === 1false
!=Not equal"1" != 2true
!==Strict not equal"1" !== 1true
<=>Spaceship1 <=> 2-1
??Null coalescing$x ?? 'default'$x or 'default'
?:Elvis / Ternary$x ?: 'fallback'$x if truthy
Always use === (strict equality) instead of ==. Loose comparison with == can produce surprising results due to PHP's type juggling (e.g., 0 == "foo" is true in older PHP).
09

Control Flow

control-flow.php
<?php
$score = 85;

// ── IF / ELSEIF / ELSE ──────────────────────────
if ($score >= 90) {
    echo "Grade: A";
} elseif ($score >= 80) {
    echo "Grade: B";
} elseif ($score >= 70) {
    echo "Grade: C";
} else {
    echo "Grade: F";
}

// ── TERNARY ────────────────────────────────────
$status = ($score >= 60) ? "Pass" : "Fail";

// ── MATCH (PHP 8.0+) — like switch but strict ──
$grade = match(true) {
    $score >= 90 => "A",
    $score >= 80 => "B",
    $score >= 70 => "C",
    default      => "F",
};

// ── SWITCH ─────────────────────────────────────
$day = "Monday";
switch ($day) {
    case "Saturday":
    case "Sunday":
        echo "Weekend!";
        break;
    default:
        echo "Weekday";
}
?>
10

Loops

loops.php
<?php
// ── WHILE ──────────────────────────────────────
$i = 0;
while ($i < 5) {
    echo $i . " ";
    $i++;
}
// Output: 0 1 2 3 4

// ── DO-WHILE (runs at least once) ───────────────
$n = 10;
do {
    echo $n;
    $n--;
} while ($n > 0);

// ── FOR ────────────────────────────────────────
for ($i = 1; $i <= 5; $i++) {
    echo "Item {$i}<br>";
}

// ── FOREACH (perfect for arrays) ───────────────
$fruits = ["apple", "banana", "cherry"];
foreach ($fruits as $index => $fruit) {
    echo "{$index}: {$fruit}<br>";
}
// 0: apple  1: banana  2: cherry

// ── BREAK & CONTINUE ───────────────────────────
for ($i = 0; $i < 10; $i++) {
    if ($i === 5) break;     // Stop entirely
    if ($i % 2 === 0) continue; // Skip even numbers
    echo $i . " ";             // Output: 1 3
}
?>
11

Functions

Functions are reusable blocks of code. PHP 8.x supports typed parameters, return types, default values, variadic arguments, named arguments, and arrow functions.

functions.php
<?php
// ── BASIC FUNCTION ─────────────────────────────
function add(int $a, int $b): int {
    return $a + $b;
}
echo add(3, 4); // 7

// ── DEFAULT PARAMETERS ─────────────────────────
function greet(string $name, string $greeting = "Hello"): string {
    return "{$greeting}, {$name}!";
}
greet("Alice");            // "Hello, Alice!"
greet("Bob", "Hi");       // "Hi, Bob!"

// ── NAMED ARGUMENTS (PHP 8.0+) ─────────────────
greet(greeting: "Hey", name: "Carol");

// ── VARIADIC (splat) ────────────────────────────
function sum(int ...$nums): int {
    return array_sum($nums);
}
sum(1, 2, 3, 4, 5); // 15

// ── NULLABLE TYPES ─────────────────────────────
function findUser(int $id): ?array {
    return null; // or return an array
}

// ── CLOSURES (anonymous functions) ─────────────
$multiply = function(int $x, int $y): int {
    return $x * $y;
};
echo $multiply(4, 5); // 20

// ── ARROW FUNCTIONS (PHP 7.4+) ─────────────────
$double = fn(int $n) => $n * 2;
echo $double(7); // 14

// ── PASSING BY REFERENCE ───────────────────────
function increment(int &$val): void {
    $val++;
}
$count = 5;
increment($count);
echo $count; // 6  (modified in place)
?>
12

Arrays

Arrays in PHP are incredibly versatile — they function as lists, maps (associative arrays), and can be nested arbitrarily deep.

arrays.php
<?php
// ── INDEXED ARRAY ──────────────────────────────
$colors = ["red", "green", "blue"];
echo $colors[0];          // "red"
$colors[] = "yellow";    // append

// ── ASSOCIATIVE ARRAY (key => value) ───────────
$user = [
    "name"  => "Alice",
    "email" => "alice@example.com",
    "age"   => 30,
];
echo $user["name"];       // "Alice"

// ── NESTED ARRAYS ──────────────────────────────
$users = [
    ["name" => "Alice", "age" => 30],
    ["name" => "Bob",   "age" => 25],
];
echo $users[1]["name"]; // "Bob"

// ── COMMON ARRAY FUNCTIONS ─────────────────────
count($colors);          // length: 4
array_push($colors, "purple"); // add to end
array_pop($colors);      // remove from end
array_shift($colors);   // remove from start
array_unshift($colors, "black"); // add to start
in_array("red", $colors); // true/false
array_keys($user);      // ["name","email","age"]
array_values($user);    // ["Alice","alice@...",30]
array_merge($arr1, $arr2); // combine arrays
sort($colors);           // sort indexed array
asort($user);            // sort assoc, keep keys
ksort($user);            // sort by key

// ── ARRAY MAP / FILTER / REDUCE ────────────────
$nums = [1, 2, 3, 4, 5];

$doubled   = array_map(fn($n) => $n * 2, $nums);
$evens     = array_filter($nums, fn($n) => $n % 2 === 0);
$total     = array_reduce($nums, fn($carry, $n) => $carry + $n, 0);

// ── ARRAY DESTRUCTURING ────────────────────────
[$first, $second] = $colors;
["name" => $name] = $user;
?>
13

Strings

strings.php
<?php
$s = "Hello, World!";

// ── SINGLE vs DOUBLE QUOTES ────────────────────
$name = "Alice";
echo "Hello, $name";       // "Hello, Alice" (interpolation)
echo 'Hello, $name';       // "Hello, $name" (literal)
echo "Hello, {$name}!";    // Best practice: use {}

// ── HEREDOC (multi-line with interpolation) ────
$html = <<<EOT
    <p>Hello, {$name}!</p>
    <p>Welcome to PHP.</p>
EOT;

// ── COMMON STRING FUNCTIONS ────────────────────
strlen($s);               // 13 (length)
strtoupper($s);          // "HELLO, WORLD!"
strtolower($s);          // "hello, world!"
trim("  hello  ");       // "hello" (strip whitespace)
substr($s, 7, 5);        // "World"
str_replace("World", "PHP", $s); // "Hello, PHP!"
strpos($s, "World");     // 7 (position) or false
str_contains($s, "Hello"); // true (PHP 8.0+)
str_starts_with($s, "Hello"); // true (PHP 8.0+)
str_ends_with($s, "!");  // true (PHP 8.0+)
explode(",", "a,b,c");   // ["a","b","c"]
implode("-", ["a","b"]); // "a-b"
sprintf("%.2f", 3.14159);// "3.14"
number_format(1234567, 2);// "1,234,567.00"
?>

14

Superglobals

PHP provides built-in global arrays called superglobals that are always accessible, regardless of scope. They are your bridge between HTTP and PHP.

VariableContains
$_GETURL query string parameters: ?name=Alice
$_POSTData from HTTP POST form submissions
$_REQUESTCombines $_GET, $_POST, $_COOKIE
$_COOKIECookies sent by the browser
$_SESSIONSession variables (server-side)
$_FILESUploaded files
$_SERVERServer info: headers, paths, script name
$_ENVEnvironment variables
$GLOBALSReferences all global variables
superglobals.php
<?php
// URL: http://localhost/page.php?id=42&name=Alice
$id   = $_GET['id']   ?? null;   // "42"
$name = $_GET['name'] ?? 'Guest'; // "Alice"

// Useful $_SERVER keys
echo $_SERVER['REQUEST_METHOD'];  // "GET" or "POST"
echo $_SERVER['REQUEST_URI'];     // "/page.php?id=42"
echo $_SERVER['REMOTE_ADDR'];    // Client IP
echo $_SERVER['HTTP_USER_AGENT'];// Browser string
echo $_SERVER['SERVER_NAME'];    // "localhost"
?>
15

Forms & User Input

Handling HTML forms is one of PHP's core strengths. The pattern is: render a form → user submits → PHP processes it. You can use a single file for both display and processing.

contact-form.php
<?php
$errors  = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize: strip tags and extra whitespace
    $name    = trim(strip_tags($_POST['name'] ?? ''));
    $email   = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
    $message = trim(strip_tags($_POST['message'] ?? ''));

    // Validate
    if (empty($name))
        $errors[] = "Name is required.";
    if (!filter_var($email, FILTER_VALIDATE_EMAIL))
        $errors[] = "Valid email required.";
    if (strlen($message) < 10)
        $errors[] = "Message must be at least 10 chars.";

    if (empty($errors)) {
        // Process: save to DB, send email, etc.
        $success = true;
    }
}
?>
<!DOCTYPE html>
<html>
<body>

<?php if ($success): ?>
    <p>Thanks! Your message was sent.</p>
<?php else: ?>

    <?php foreach ($errors as $err): ?>
        <p style="color:red"><?= htmlspecialchars($err) ?></p>
    <?php endforeach; ?>

    <form method="POST">
        <input type="text" name="name"
               value="<?= htmlspecialchars($name ?? '') ?>">
        <input type="email" name="email">
        <textarea name="message"></textarea>
        <button type="submit">Send</button>
    </form>

<?php endif; ?>
</body></html>
Always use htmlspecialchars() when outputting user input back to HTML. This prevents Cross-Site Scripting (XSS) attacks by converting characters like < to &lt;.
16

Sessions & Cookies

HTTP is stateless — each request is independent. Sessions and cookies let you persist data across requests (e.g., keeping a user logged in).

sessions.php
<?php
// session_start() MUST be called before any output
session_start();

// Store data in session
$_SESSION['user_id'] = 42;
$_SESSION['username'] = 'alice';

// Read session data
if (isset($_SESSION['user_id'])) {
    echo "Logged in as: " . $_SESSION['username'];
}

// Destroy session (logout)
session_destroy();

// ── COOKIES ────────────────────────────────────
// Set a cookie (30-day expiry, path /, secure, httponly)
setcookie(
    'theme',
    'dark',
    [
        'expires'  => time() + 30 * 24 * 3600,
        'path'     => '/',
        'secure'   => true,
        'httponly' => true,  // JS can't access this
        'samesite' => 'Strict',
    ]
);

// Read a cookie
$theme = $_COOKIE['theme'] ?? 'light';

// Delete a cookie (set expiry in the past)
setcookie('theme', '', time() - 3600);
?>
Sessions store data on the server; the browser only holds a session ID in a cookie. Cookies store data in the browser. Use sessions for sensitive data (like login state), cookies for preferences.
17

File I/O

file-io.php
<?php
// ── READING FILES ──────────────────────────────
$content  = file_get_contents('data.txt');  // entire file as string
$lines    = file('data.txt');               // array of lines

// ── WRITING FILES ──────────────────────────────
file_put_contents('log.txt', "New entry\n", FILE_APPEND);

// ── READING LINE BY LINE (memory efficient) ────
$handle = fopen('bigfile.csv', 'r');
while (($line = fgets($handle)) !== false) {
    echo trim($line) . "<br>";
}
fclose($handle);

// ── FILE EXISTENCE & INFO ───────────────────────
file_exists('data.txt');   // true/false
is_file('data.txt');       // true if it's a file
is_dir('uploads/');        // true if it's a directory
filesize('data.txt');      // size in bytes

// ── FILE UPLOAD HANDLING ───────────────────────
if (isset($_FILES['upload'])) {
    $file    = $_FILES['upload'];
    $allowed = ['image/jpeg', 'image/png', 'image/gif'];

    if (!in_array($file['type'], $allowed)) {
        die("Only images allowed");
    }
    if ($file['size'] > 2_000_000) {
        die("Max 2MB");
    }

    $dest = 'uploads/' . basename($file['name']);
    move_uploaded_file($file['tmp_name'], $dest);
}
?>
18

MySQL & PDO

PDO (PHP Data Objects) is the modern, secure way to talk to databases. It supports MySQL, PostgreSQL, SQLite, and more — all with the same API.

database.php — PDO
<?php
// ── CONNECT ────────────────────────────────────
try {
    $dsn = "mysql:host=localhost;dbname=myapp;charset=utf8mb4";
    $pdo = new PDO($dsn, 'root', 'password', [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES  => false,
    ]);
} catch (PDOException $e) {
    die("Connection failed: " . $e->getMessage());
}

// ── CREATE TABLE ───────────────────────────────
$pdo->exec("
    CREATE TABLE IF NOT EXISTS users (
        id       INT AUTO_INCREMENT PRIMARY KEY,
        name     VARCHAR(100) NOT NULL,
        email    VARCHAR(255) UNIQUE NOT NULL,
        created  DATETIME DEFAULT CURRENT_TIMESTAMP
    )
");

// ── INSERT (prepared statement — prevents SQL injection!) ──
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
$stmt->execute(['name' => 'Alice', 'email' => 'alice@example.com']);
$newId = $pdo->lastInsertId();

// ── SELECT ALL ─────────────────────────────────
$stmt  = $pdo->query("SELECT * FROM users ORDER BY id DESC");
$users = $stmt->fetchAll();

foreach ($users as $user) {
    echo htmlspecialchars($user['name']) . "<br>";
}

// ── SELECT ONE ─────────────────────────────────
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
$user = $stmt->fetch();

// ── UPDATE ─────────────────────────────────────
$stmt = $pdo->prepare("UPDATE users SET name = :name WHERE id = :id");
$stmt->execute(['name' => 'Alice Smith', 'id' => 1]);

// ── DELETE ─────────────────────────────────────
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);

// ── TRANSACTIONS ───────────────────────────────
try {
    $pdo->beginTransaction();
    // multiple queries...
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    throw $e;
}
?>
Never concatenate user input into SQL queries: "SELECT * WHERE name = '$name'". This is a SQL injection vulnerability. Always use prepared statements with bound parameters as shown above.

19

Object-Oriented PHP

PHP supports full OOP with classes, inheritance, interfaces, traits, abstract classes, and more. Modern PHP frameworks (Laravel, Symfony) are built entirely on OOP principles.

oop.php
<?php
// ── INTERFACE ─────────────────────────────────
interface Describable {
    public function describe(): string;
}

// ── ABSTRACT CLASS ─────────────────────────────
abstract class Animal implements Describable {
    public function __construct(
        protected string $name,   // PHP 8 constructor promotion
        protected int    $age,
    ) {}

    abstract public function speak(): string;

    public function describe(): string {
        return "{$this->name} is {$this->age} years old";
    }

    public static function create(string $name, int $age): static {
        return new static($name, $age);
    }
}

// ── TRAIT (mixin) ──────────────────────────────
trait HasTimestamps {
    private int $createdAt;

    public function setCreatedAt(): void {
        $this->createdAt = time();
    }

    public function getCreatedAt(): int {
        return $this->createdAt;
    }
}

// ── CONCRETE CLASS ─────────────────────────────
class Dog extends Animal {
    use HasTimestamps;

    public function __construct(
        string $name,
        int    $age,
        private string $breed,
    ) {
        parent::__construct($name, $age);
        $this->setCreatedAt();
    }

    public function speak(): string { return "Woof!"; }

    public function describe(): string {
        return parent::describe() . ", breed: {$this->breed}";
    }
}

// ── USAGE ──────────────────────────────────────
$dog = new Dog("Rex", 3, "Labrador");
echo $dog->speak();         // "Woof!"
echo $dog->describe();      // "Rex is 3 years old, breed: Labrador"
echo ($dog instanceof Animal) ? "yes" : "no"; // yes

// ── ENUMS (PHP 8.1+) ───────────────────────────
enum Status: string {
    case Active   = 'active';
    case Inactive = 'inactive';
    case Banned   = 'banned';
}
$s = Status::Active;
echo $s->value; // "active"
?>
20

Error Handling

error-handling.php
<?php
// ── PHP.INI SETTINGS (dev vs production) ───────
// Development: show all errors
ini_set('display_errors', 1);
error_reporting(E_ALL);

// Production: log errors, never display
ini_set('display_errors', 0);
ini_set('log_errors', 1);

// ── TRY / CATCH / FINALLY ──────────────────────
function divide(int $a, int $b): float {
    if ($b === 0) {
        throw new InvalidArgumentException("Cannot divide by zero");
    }
    return $a / $b;
}

try {
    echo divide(10, 2);  // 5
    echo divide(10, 0);  // throws
} catch (InvalidArgumentException $e) {
    echo "Math error: " . $e->getMessage();
} catch (Exception $e) {
    echo "General error: " . $e->getMessage();
} finally {
    // Always runs, even if exception was thrown
    echo "Done.";
}

// ── CUSTOM EXCEPTION CLASS ─────────────────────
class NotFoundException extends RuntimeException {
    public function __construct(string $resource) {
        parent::__construct("{$resource} not found", 404);
    }
}
throw new NotFoundException("User");
?>
21

Security Best Practices

PHP security vulnerabilities are well-documented. Following these principles will protect your application against the most common attack vectors.

XSS — Cross-Site Scripting

xss-prevention.php
<?php
// ALWAYS escape output before rendering in HTML
$input = '<script>alert("hacked")</script>';

// ✗ DANGEROUS: echo $input;
// ✓ SAFE:
echo htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// &lt;script&gt;alert(&quot;hacked&quot;)&lt;/script&gt;
?>

Passwords — Never Store Plain Text

passwords.php
<?php
// Hash a password (use PHP's built-in bcrypt)
$hash = password_hash($_POST['password'], PASSWORD_BCRYPT);
// Store $hash in the database — never the plain password

// Verify a password at login
if (password_verify($_POST['password'], $hash)) {
    echo "Password correct!";
}

// Check if hash needs to be upgraded (e.g., after cost change)
if (password_needs_rehash($hash, PASSWORD_BCRYPT)) {
    $hash = password_hash($password, PASSWORD_BCRYPT);
    // update hash in database
}
?>

Security Checklist

ThreatPrevention
SQL InjectionAlways use PDO prepared statements
XSShtmlspecialchars() on all output
CSRFUse per-form hidden tokens, verify on submit
Plain passwordspassword_hash() / password_verify()
Session fixationsession_regenerate_id(true) on login
Sensitive errorsTurn off display_errors in production
Directory traversalbasename() and whitelist allowed paths
Untrusted uploadsValidate MIME type, rename files, serve outside docroot
22

Next Steps

You now have a solid foundation in PHP and Apache. Here's where to go next:

Frameworks

Laravel
The most popular PHP framework. Elegant MVC, ORM (Eloquent), routing, templating (Blade), queues, and more. Install via Composer: composer create-project laravel/laravel myapp
Symfony
Enterprise-grade, highly modular. More complex but extremely powerful. Laravel is built on Symfony components.
Slim
Micro-framework for building APIs and small apps. Great for learning the basics before jumping to a full framework.

Tooling

Composer
PHP's package manager. Install it at getcomposer.org. Use it to add third-party libraries.
Xdebug
PHP's debugger. Enables step-debugging in VS Code and PhpStorm.
PHPUnit
Unit testing framework for PHP. Test-driven development in PHP.
PHP-CS-Fixer
Auto-formats your code to PSR standards.

Standards to Follow

PSR-1 / PSR-2 / PSR-12
PHP coding style standards. Follow these for clean, consistent code.
PSR-4 Autoloading
The standard for autoloading classes. Composer handles this automatically in modern PHP.
Best next project: Build a simple CRUD app — create, read, update, delete items from a MySQL database using PHP + Apache + a tiny bit of HTML/CSS. This will tie together forms, sessions, PDO, and OOP in one real exercise.