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.
- Huge ecosystem & community
- Runs everywhere Apache does
- Built-in web primitives
- Low barrier to entry
- First-class MySQL support
- Frameworks: Laravel, Symfony
- 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
How PHP & Apache Work Together
To understand the setup, you need to understand the request lifecycle:
-
Browser sends HTTP request
Your browser requests
http://localhost/hello.php. -
Apache receives the request
Apache (the web server) intercepts the request. It looks at the file extension
.php. -
Apache hands off to PHP
Via
mod_php(a module) or PHP-FPM, Apache passes the file to the PHP interpreter. -
PHP executes the script
PHP reads the
.phpfile, executes code, and generates output (usually HTML). - Apache returns the response Apache sends the PHP-generated HTML back to the browser. The browser renders it.
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.
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)
# 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
brew install httpd, Apache's config file is at:/opt/homebrew/etc/httpd/httpd.confAnd 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
# 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();"
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
# 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
| Directive | Purpose | Example |
|---|---|---|
DocumentRoot | Root folder served by Apache | /opt/homebrew/var/www |
DirectoryIndex | Default file for a directory | index.php index.html |
LoadModule | Loads an Apache extension | LoadModule rewrite_module ... |
AllowOverride | Allows .htaccess to override | AllowOverride All |
Listen | Port Apache listens on | Listen 8080 |
ServerName | The hostname of the server | localhost |
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).
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]
httpd.conf, always restart Apache: brew services restart httpd
Your First PHP Script
Create a file called hello.php in your document root (/opt/homebrew/var/www/) with the following content:
<!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:
<?php phpinfo(); ?>
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.
// 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
<?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}!";
}
?>
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.
<?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
<?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
?>
Operators
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 5 + 3 | 8 |
- | Subtraction | 5 - 3 | 2 |
* | Multiplication | 5 * 3 | 15 |
/ | Division | 10 / 3 | 3.333... |
% | Modulo | 10 % 3 | 1 |
** | Exponentiation | 2 ** 8 | 256 |
. | String concat | "Hi" . " " . "there" | "Hi there" |
== | Loose equal | "1" == 1 | true |
=== | Strict equal | "1" === 1 | false |
!= | Not equal | "1" != 2 | true |
!== | Strict not equal | "1" !== 1 | true |
<=> | Spaceship | 1 <=> 2 | -1 |
?? | Null coalescing | $x ?? 'default' | $x or 'default' |
?: | Elvis / Ternary | $x ?: 'fallback' | $x if truthy |
=== (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).
Control Flow
<?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";
}
?>
Loops
<?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
}
?>
Functions
Functions are reusable blocks of code. PHP 8.x supports typed parameters, return types, default values, variadic arguments, named arguments, and arrow functions.
<?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)
?>
Arrays
Arrays in PHP are incredibly versatile — they function as lists, maps (associative arrays), and can be nested arbitrarily deep.
<?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;
?>
Strings
<?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"
?>
Superglobals
PHP provides built-in global arrays called superglobals that are always accessible, regardless of scope. They are your bridge between HTTP and PHP.
| Variable | Contains |
|---|---|
$_GET | URL query string parameters: ?name=Alice |
$_POST | Data from HTTP POST form submissions |
$_REQUEST | Combines $_GET, $_POST, $_COOKIE |
$_COOKIE | Cookies sent by the browser |
$_SESSION | Session variables (server-side) |
$_FILES | Uploaded files |
$_SERVER | Server info: headers, paths, script name |
$_ENV | Environment variables |
$GLOBALS | References all global variables |
<?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"
?>
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.
<?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>
htmlspecialchars() when outputting user input back to HTML. This prevents Cross-Site Scripting (XSS) attacks by converting characters like < to <.
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).
<?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);
?>
File I/O
<?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);
}
?>
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.
<?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;
}
?>
"SELECT * WHERE name = '$name'". This is a SQL injection vulnerability. Always use prepared statements with bound parameters as shown above.
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.
<?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"
?>
Error Handling
<?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");
?>
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
<?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');
// <script>alert("hacked")</script>
?>
Passwords — Never Store Plain Text
<?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
| Threat | Prevention |
|---|---|
| SQL Injection | Always use PDO prepared statements |
| XSS | htmlspecialchars() on all output |
| CSRF | Use per-form hidden tokens, verify on submit |
| Plain passwords | password_hash() / password_verify() |
| Session fixation | session_regenerate_id(true) on login |
| Sensitive errors | Turn off display_errors in production |
| Directory traversal | basename() and whitelist allowed paths |
| Untrusted uploads | Validate MIME type, rename files, serve outside docroot |
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.