Toolmingo
Guides26 min read

PHP Tutorial for Beginners (2025): Learn PHP Step by Step

Complete PHP tutorial for absolute beginners. Learn PHP syntax, arrays, functions, OOP, forms, MySQL, and build real projects. Free guide with code examples.

PHP powers over 77% of all websites with a known server-side language — including WordPress, Facebook (originally), Wikipedia, and Laravel apps worldwide. This tutorial takes you from zero to building real PHP web applications, with no prior programming experience required.

What you'll learn

Topic What you'll be able to do
Setup Install PHP and run your first script
Syntax & variables Understand how PHP code works
Data types Work with strings, numbers, arrays, booleans
Control flow Use if/else, loops, and switch
Functions Write reusable, clean code
Arrays Work with indexed and associative arrays
Forms Handle HTML form data securely
OOP Build classes, objects, and interfaces
MySQL/PDO Connect to a database with prepared statements
Composer Manage packages like a professional
Projects Build 3 real web programs

PHP version used: PHP 8.3+ (latest stable as of 2025)


Part 1 — Why PHP?

PHP is the dominant language for server-side web development for several reasons:

1. Runs on every web host — shared hosting, VPS, cloud
2. Powers WordPress (43% of all websites)
3. Laravel is one of the best web frameworks ever built
4. Easy to learn: HTML + PHP in the same file to start
5. Huge ecosystem via Composer (Packagist: 400k+ packages)
6. PHP 8.x is modern: named args, attributes, fibers, JIT

PHP vs other languages

Language Strength When to pick
PHP Web hosting everywhere, WordPress, Laravel Web apps, CMSes, APIs, shared hosting
Python AI/ML, data science, general purpose ML projects, data pipelines, scripting
JavaScript (Node.js) Frontend + backend unified Full-stack JS apps, real-time
Ruby Rails productivity Rapid prototyping, startups
Go Performance, concurrency High-throughput APIs, microservices
Java Enterprise, Android Large-scale enterprise systems

Part 2 — Setup

Option A: Local server (recommended)

Windows: Install XAMPP — includes Apache + PHP + MySQL.

Mac: Install Homebrew then:

brew install php
php -v        # verify

Linux (Ubuntu/Debian):

sudo apt update
sudo apt install php php-cli php-mysql php-curl php-xml
php -v

Option B: PHP built-in server

mkdir myapp && cd myapp
echo "<?php echo 'Hello World!';" > index.php
php -S localhost:8000      # visit http://localhost:8000

Your first PHP file

Create hello.php:

<?php
echo "Hello, World!";
echo "<br>";
echo "PHP version: " . PHP_VERSION;
?>

Run with php hello.php in terminal, or open in browser via XAMPP.

Key facts about PHP files:

  • Must start with <?php (the opening tag)
  • Closing tag ?> is optional in pure PHP files (omitting it is best practice)
  • Statements end with ;
  • PHP is embedded in HTML — server processes PHP, browser receives HTML

Part 3 — Variables and Data Types

Variables

PHP variables start with $:

<?php
$name = "Alice";
$age = 30;
$height = 5.9;
$isStudent = true;

echo $name;          // Alice
echo $age;           // 30
var_dump($isStudent); // bool(true)

Rules:

  • Start with $ followed by a letter or underscore
  • Case-sensitive: $name and $Name are different
  • No type declaration needed (PHP infers the type)

Data types

Type Example Description
string "Hello" Text in quotes (single or double)
int 42 Whole numbers
float 3.14 Decimal numbers
bool true, false Boolean
array [1, 2, 3] List or map of values
null null No value
object new MyClass() Instance of a class
<?php
$str = "Hello";
$int = 42;
$float = 3.14;
$bool = true;
$null = null;
$arr = [1, 2, 3];

echo gettype($str);    // string
echo gettype($int);    // integer
echo gettype($bool);   // boolean
echo gettype($null);   // NULL

Type casting

<?php
$strNum = "42";
$num = (int) $strNum;    // 42 as integer
$flt = (float) "3.14";   // 3.14 as float
$str = (string) 100;     // "100" as string
$bool = (bool) 0;        // false (0 is falsy)

String operations

<?php
$first = "John";
$last = "Doe";

// Concatenation
$full = $first . " " . $last;   // "John Doe"

// String interpolation (double quotes only)
echo "Hello, $first!";          // Hello, John!
echo "Hello, {$first}!";        // Hello, John! (safe with complex vars)

// Useful string functions
echo strlen("hello");           // 5
echo strtoupper("hello");       // HELLO
echo strtolower("HELLO");       // hello
echo str_replace("o", "0", "hello");   // hell0
echo trim("  hello  ");         // "hello"
echo substr("hello world", 6);  // world
echo str_contains("hello world", "world");  // 1 (true) — PHP 8+
echo str_starts_with("hello", "he");        // 1 — PHP 8+
echo str_ends_with("hello", "lo");          // 1 — PHP 8+
echo strpos("hello world", "world");        // 6

Constants

<?php
define('MAX_USERS', 100);
const DB_NAME = 'myapp';

echo MAX_USERS;   // 100
echo DB_NAME;     // myapp

// PHP built-in constants
echo PHP_VERSION;    // 8.3.x
echo PHP_EOL;        // newline character
echo PHP_INT_MAX;    // 9223372036854775807

Part 4 — Control Flow

if / elseif / else

<?php
$age = 20;

if ($age >= 18) {
    echo "Adult";
} elseif ($age >= 13) {
    echo "Teenager";
} else {
    echo "Child";
}

Comparison and logical operators

<?php
// Comparison
$a = 5;
$b = "5";

var_dump($a == $b);   // true  (loose: value only)
var_dump($a === $b);  // false (strict: value AND type)
var_dump($a !== $b);  // true
var_dump($a > 3);     // true
var_dump($a <= 5);    // true

// Logical
$x = true;
$y = false;

var_dump($x && $y);   // false
var_dump($x || $y);   // true
var_dump(!$x);        // false

// Spaceship operator (PHP 7+)
echo 1 <=> 2;   // -1 (left is less)
echo 2 <=> 2;   //  0 (equal)
echo 3 <=> 2;   //  1 (left is greater)

switch

<?php
$day = "Monday";

switch ($day) {
    case "Saturday":
    case "Sunday":
        echo "Weekend";
        break;
    case "Monday":
        echo "Start of the week";
        break;
    default:
        echo "Weekday";
}

match (PHP 8+)

<?php
$status = 200;

$text = match($status) {
    200 => "OK",
    301 => "Moved Permanently",
    404 => "Not Found",
    500 => "Server Error",
    default => "Unknown",
};

echo $text;   // OK

match is strict (uses ===), has no fallthrough, and is an expression.

Ternary and null coalescing

<?php
$age = 20;

// Ternary
$label = $age >= 18 ? "adult" : "minor";

// Null coalescing (PHP 7+)
$username = $_GET['user'] ?? 'guest';  // 'guest' if not set or null

// Null coalescing assignment (PHP 7.4+)
$config['debug'] ??= false;  // set to false if not already set

Loops

<?php
// for
for ($i = 0; $i < 5; $i++) {
    echo $i . " ";   // 0 1 2 3 4
}

// while
$count = 0;
while ($count < 3) {
    echo $count++;
}   // 012

// do-while (runs at least once)
$n = 5;
do {
    echo $n;
    $n--;
} while ($n > 0);

// foreach (arrays)
$fruits = ["apple", "banana", "cherry"];
foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}

// foreach with key
$person = ["name" => "Alice", "age" => 30];
foreach ($person as $key => $value) {
    echo "$key: $value\n";
}

// break and continue
for ($i = 0; $i < 10; $i++) {
    if ($i === 5) break;     // stop loop
    if ($i % 2 === 0) continue;  // skip even
    echo $i . " ";   // 1 3
}

Part 5 — Functions

Basic functions

<?php
function greet(string $name): string {
    return "Hello, $name!";
}

echo greet("Alice");   // Hello, Alice!

Default parameters

<?php
function createUser(string $name, string $role = "user", int $age = 0): array {
    return ['name' => $name, 'role' => $role, 'age' => $age];
}

print_r(createUser("Alice"));               // role=user, age=0
print_r(createUser("Bob", "admin", 25));

Named arguments (PHP 8+)

<?php
function makeTag(string $tag, string $content, string $class = ""): string {
    $cls = $class ? " class=\"$class\"" : "";
    return "<$tag$cls>$content</$tag>";
}

echo makeTag(content: "Hello", tag: "p", class: "intro");
// <p class="intro">Hello</p>

Variadic functions

<?php
function sum(int ...$numbers): int {
    return array_sum($numbers);
}

echo sum(1, 2, 3, 4, 5);   // 15

Type declarations

<?php
declare(strict_types=1);  // Enforce strict types

function divide(float $a, float $b): float {
    if ($b === 0.0) {
        throw new InvalidArgumentException("Cannot divide by zero");
    }
    return $a / $b;
}

echo divide(10, 3);   // 3.3333...

Anonymous functions and closures

<?php
// Anonymous function
$double = function(int $n): int {
    return $n * 2;
};
echo $double(5);   // 10

// Arrow function (PHP 7.4+) — automatically captures outer variables
$multiplier = 3;
$multiply = fn(int $n) => $n * $multiplier;
echo $multiply(5);   // 15

// Using closures with array functions
$numbers = [1, 2, 3, 4, 5, 6];
$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
$doubled = array_map(fn($n) => $n * 2, $numbers);
$sum = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);

print_r($evens);    // [2, 4, 6]
print_r($doubled);  // [2, 4, 6, 8, 10, 12]
echo $sum;          // 21

Part 6 — Arrays

PHP arrays are extremely flexible — they act as lists, maps, stacks, and queues.

Indexed arrays

<?php
$fruits = ["apple", "banana", "cherry"];

echo $fruits[0];         // apple
echo count($fruits);     // 3

// Add / remove
$fruits[] = "date";              // append
array_push($fruits, "elderberry");  // append (multiple)
$last = array_pop($fruits);      // remove last
$first = array_shift($fruits);   // remove first
array_unshift($fruits, "avocado");  // prepend

// Iterate
foreach ($fruits as $index => $fruit) {
    echo "$index: $fruit\n";
}

Associative arrays (maps)

<?php
$user = [
    "name"  => "Alice",
    "age"   => 30,
    "email" => "alice@example.com",
];

echo $user["name"];   // Alice

// Add / update / delete
$user["city"] = "London";
$user["age"] = 31;
unset($user["email"]);

// Check existence
var_dump(isset($user["name"]));        // true
var_dump(array_key_exists("city", $user));  // true

// Get all keys / values
print_r(array_keys($user));    // ["name", "age", "city"]
print_r(array_values($user));  // ["Alice", 31, "London"]

Array functions

<?php
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];

sort($numbers);                    // sort ascending (in-place)
rsort($numbers);                   // sort descending
$sorted = [...$numbers];
sort($sorted);                     // spread to avoid mutating original

$unique = array_unique([1, 2, 2, 3, 3]);  // [1, 2, 3]
$sliced = array_slice($numbers, 1, 3);     // 3 items from index 1
$merged = array_merge([1, 2], [3, 4]);     // [1, 2, 3, 4]
$flipped = array_flip(["a" => 1, "b" => 2]); // [1 => "a", 2 => "b"]

// Search
$pos = array_search("banana", ["apple", "banana", "cherry"]);  // 1
$found = in_array(5, [1, 2, 5, 8]);  // true

// Map, filter, reduce
$nums = [1, 2, 3, 4, 5];
$squared = array_map(fn($n) => $n ** 2, $nums);       // [1, 4, 9, 16, 25]
$odds = array_filter($nums, fn($n) => $n % 2 !== 0);  // [1, 3, 5]
$total = array_reduce($nums, fn($c, $n) => $c + $n, 0);  // 15

// Sorting associative arrays
$people = [
    ["name" => "Charlie", "age" => 30],
    ["name" => "Alice", "age" => 25],
    ["name" => "Bob", "age" => 35],
];
usort($people, fn($a, $b) => $a["age"] <=> $b["age"]);
// Sorted by age: Alice, Charlie, Bob

Multidimensional arrays

<?php
$matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

echo $matrix[1][2];   // 6

$users = [
    ["name" => "Alice", "role" => "admin"],
    ["name" => "Bob",   "role" => "user"],
];

foreach ($users as $user) {
    echo "{$user['name']} is a {$user['role']}\n";
}

Spread operator and destructuring

<?php
$first = [1, 2, 3];
$second = [4, 5, 6];
$combined = [...$first, ...$second];  // [1, 2, 3, 4, 5, 6]

// Array destructuring
[$a, $b, $c] = [10, 20, 30];
echo $a;   // 10

// Skip elements
[, $second, , $fourth] = [1, 2, 3, 4];

// Named destructuring (associative)
["name" => $name, "age" => $age] = ["name" => "Alice", "age" => 30];

Part 7 — Handling Forms (Web)

This is where PHP's web superpowers shine.

HTML form + PHP handler

<!-- form.html -->
<form method="POST" action="process.php">
    <input type="text" name="username" placeholder="Username" required>
    <input type="email" name="email" placeholder="Email" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Register</button>
</form>
<?php
// process.php
declare(strict_types=1);

// Only process POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    die("Method Not Allowed");
}

// Sanitize and validate input
$username = trim($_POST['username'] ?? '');
$email    = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';

$errors = [];

if (strlen($username) < 3) {
    $errors[] = "Username must be at least 3 characters";
}

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

if (strlen($password) < 8) {
    $errors[] = "Password must be at least 8 characters";
}

if (!empty($errors)) {
    foreach ($errors as $error) {
        echo htmlspecialchars($error) . "<br>";
    }
    exit;
}

// Hash password before storing
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);

echo "User registered: " . htmlspecialchars($username);

Superglobals cheat sheet

Superglobal Contains
$_GET URL query parameters (?name=value)
$_POST POST form data
$_REQUEST Both GET and POST
$_FILES Uploaded file data
$_SESSION Session variables (per user)
$_COOKIE Browser cookies
$_SERVER Server info (headers, paths, methods)
$_ENV Environment variables
$GLOBALS All global variables

NEVER trust user input

<?php
// WRONG — XSS vulnerability
echo $_GET['name'];

// RIGHT — escape output
echo htmlspecialchars($_GET['name'] ?? '', ENT_QUOTES, 'UTF-8');

// WRONG — SQL injection
$sql = "SELECT * FROM users WHERE name = '{$_GET['name']}'";

// RIGHT — prepared statements (see Part 9)
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$_GET['name']]);

Part 8 — Object-Oriented Programming

Classes and objects

<?php
class Animal {
    // Properties
    protected string $name;
    protected string $sound;

    // Constructor
    public function __construct(string $name, string $sound) {
        $this->name  = $name;
        $this->sound = $sound;
    }

    // Methods
    public function speak(): string {
        return "{$this->name} says {$this->sound}!";
    }

    public function getName(): string {
        return $this->name;
    }
}

$dog = new Animal("Rex", "Woof");
echo $dog->speak();   // Rex says Woof!

Constructor property promotion (PHP 8+)

<?php
class Product {
    public function __construct(
        private readonly string $name,
        private float $price,
        private int $stock = 0,
    ) {}

    public function getPrice(): float { return $this->price; }
    public function getName(): string { return $this->name; }
    public function isAvailable(): bool { return $this->stock > 0; }

    public function applyDiscount(float $percent): static {
        $this->price *= (1 - $percent / 100);
        return $this; // method chaining
    }
}

$product = new Product("Laptop", 999.99, 5);
echo $product->applyDiscount(10)->getPrice();  // 899.991

Inheritance

<?php
class Shape {
    public function __construct(protected string $color = 'red') {}

    public function area(): float {
        return 0.0;
    }

    public function describe(): string {
        return "A {$this->color} shape with area " . $this->area();
    }
}

class Circle extends Shape {
    public function __construct(
        private float $radius,
        string $color = 'red'
    ) {
        parent::__construct($color);
    }

    public function area(): float {
        return M_PI * $this->radius ** 2;
    }
}

class Rectangle extends Shape {
    public function __construct(
        private float $width,
        private float $height,
        string $color = 'blue'
    ) {
        parent::__construct($color);
    }

    public function area(): float {
        return $this->width * $this->height;
    }
}

$shapes = [
    new Circle(5, 'red'),
    new Rectangle(4, 6, 'blue'),
];

foreach ($shapes as $shape) {
    echo $shape->describe() . "\n";
}

Interfaces

<?php
interface Cacheable {
    public function getCacheKey(): string;
    public function ttl(): int;
}

interface Serializable {
    public function toArray(): array;
    public static function fromArray(array $data): static;
}

class UserProfile implements Cacheable, Serializable {
    public function __construct(
        private int $id,
        private string $name,
        private string $email,
    ) {}

    public function getCacheKey(): string {
        return "user:{$this->id}";
    }

    public function ttl(): int {
        return 3600; // 1 hour
    }

    public function toArray(): array {
        return ['id' => $this->id, 'name' => $this->name, 'email' => $this->email];
    }

    public static function fromArray(array $data): static {
        return new static($data['id'], $data['name'], $data['email']);
    }
}

Abstract classes

<?php
abstract class PaymentGateway {
    abstract public function charge(float $amount, string $currency): bool;
    abstract public function refund(string $transactionId): bool;

    // Shared concrete method
    protected function formatAmount(float $amount): string {
        return number_format($amount, 2);
    }

    public function processPayment(float $amount, string $currency): string {
        $success = $this->charge($amount, $currency);
        return $success
            ? "Charged " . $this->formatAmount($amount) . " $currency"
            : "Payment failed";
    }
}

class StripeGateway extends PaymentGateway {
    public function charge(float $amount, string $currency): bool {
        // Stripe API call here
        return true;
    }

    public function refund(string $transactionId): bool {
        // Stripe refund here
        return true;
    }
}

Abstract class vs Interface

Abstract class Interface
Can have concrete methods Yes Only default methods (PHP 8+)
Can have properties Yes No
Constructor Yes No
Multiple inheritance No (single) Yes (multiple)
When to use Share code between related classes Define a contract for unrelated classes

Static members

<?php
class Counter {
    private static int $count = 0;

    public static function increment(): void {
        self::$count++;
    }

    public static function getCount(): int {
        return self::$count;
    }

    public static function reset(): void {
        self::$count = 0;
    }
}

Counter::increment();
Counter::increment();
Counter::increment();
echo Counter::getCount();  // 3

Traits

<?php
trait Timestamps {
    private ?DateTime $createdAt = null;
    private ?DateTime $updatedAt = null;

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

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

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

trait SoftDelete {
    private ?DateTime $deletedAt = null;

    public function delete(): void {
        $this->deletedAt = new DateTime();
    }

    public function isDeleted(): bool {
        return $this->deletedAt !== null;
    }
}

class Post {
    use Timestamps, SoftDelete;

    public function __construct(private string $title) {
        $this->setCreatedAt();
    }
}

$post = new Post("Hello World");
$post->delete();
var_dump($post->isDeleted()); // true

Enums (PHP 8.1+)

<?php
enum Status: string {
    case Active   = 'active';
    case Inactive = 'inactive';
    case Pending  = 'pending';

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

$status = Status::Active;
echo $status->value;   // active
echo $status->label(); // Active

// From value
$fromDb = Status::from('pending');
echo $fromDb->label(); // Pending Review

Part 9 — MySQL with PDO

PDO (PHP Data Objects) lets you connect to MySQL, PostgreSQL, SQLite, and more with a unified API and prepared statements.

Connect to MySQL

<?php
declare(strict_types=1);

$host   = 'localhost';
$dbname = 'myapp';
$user   = 'root';
$pass   = 'secret';
$port   = '3306';

try {
    $pdo = new PDO(
        "mysql:host=$host;port=$port;dbname=$dbname;charset=utf8mb4",
        $user,
        $pass,
        [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ]
    );
} catch (PDOException $e) {
    // Never echo $e->getMessage() in production (leaks credentials)
    error_log($e->getMessage());
    die("Database connection failed");
}

CRUD with prepared statements

<?php
// CREATE TABLE
$pdo->exec("
    CREATE TABLE IF NOT EXISTS users (
        id         INT AUTO_INCREMENT PRIMARY KEY,
        name       VARCHAR(100) NOT NULL,
        email      VARCHAR(200) NOT NULL UNIQUE,
        password   VARCHAR(255) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
");

// INSERT (positional params)
$stmt = $pdo->prepare(
    "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"
);
$stmt->execute(["Alice", "alice@example.com", password_hash("secret", PASSWORD_BCRYPT)]);
$newId = (int) $pdo->lastInsertId();

// INSERT (named params)
$stmt = $pdo->prepare(
    "INSERT INTO users (name, email, password) VALUES (:name, :email, :pass)"
);
$stmt->execute([
    ':name'  => 'Bob',
    ':email' => 'bob@example.com',
    ':pass'  => password_hash("hunter2", PASSWORD_BCRYPT),
]);

// SELECT all
$stmt = $pdo->query("SELECT id, name, email FROM users");
$users = $stmt->fetchAll();  // array of assoc arrays

foreach ($users as $user) {
    echo "{$user['name']} — {$user['email']}\n";
}

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

if ($user) {
    echo $user['name'];
}

// UPDATE
$stmt = $pdo->prepare("UPDATE users SET name = ? WHERE id = ?");
$stmt->execute(["Alice Smith", 1]);
echo $stmt->rowCount() . " row(s) updated";

// DELETE
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([2]);

Transactions

<?php
try {
    $pdo->beginTransaction();

    $stmt = $pdo->prepare("INSERT INTO orders (user_id, total) VALUES (?, ?)");
    $stmt->execute([1, 99.99]);
    $orderId = (int) $pdo->lastInsertId();

    $stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, qty) VALUES (?, ?, ?)");
    $stmt->execute([$orderId, 42, 2]);

    $stmt = $pdo->prepare("UPDATE products SET stock = stock - ? WHERE id = ?");
    $stmt->execute([2, 42]);

    $pdo->commit();
    echo "Order placed successfully";
} catch (PDOException $e) {
    $pdo->rollBack();
    error_log($e->getMessage());
    echo "Order failed — rolled back";
}

Part 10 — Composer and Packages

Composer is PHP's package manager.

Install Composer

# Download installer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"
mv composer.phar /usr/local/bin/composer   # Linux/Mac

Basic usage

composer init              # Create composer.json
composer require guzzlehttp/guzzle   # Install a package
composer require --dev phpunit/phpunit  # Dev dependency
composer install           # Install all from composer.json
composer update            # Update to latest allowed versions
composer autoload          # Regenerate autoloader

Autoloading

// composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}
composer dump-autoload
<?php
// index.php
require __DIR__ . '/vendor/autoload.php';

use App\Models\User;

$user = new User("Alice");

Popular PHP packages

Package Purpose Install
guzzlehttp/guzzle HTTP client composer require guzzlehttp/guzzle
monolog/monolog Logging composer require monolog/monolog
vlucas/phpdotenv .env files composer require vlucas/phpdotenv
ramsey/uuid UUID generation composer require ramsey/uuid
carbon/carbon Date/time composer require nesbot/carbon
phpmailer/phpmailer Email sending composer require phpmailer/phpmailer
league/csv CSV processing composer require league/csv
symfony/validator Validation composer require symfony/validator

Part 11 — Error Handling

<?php
declare(strict_types=1);

// Custom exception
class ValidationException extends RuntimeException {
    public function __construct(
        private array $errors,
        string $message = "Validation failed"
    ) {
        parent::__construct($message);
    }

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

function validateAge(int $age): void {
    if ($age < 0 || $age > 150) {
        throw new ValidationException(
            ["age" => "Age must be between 0 and 150"],
        );
    }
}

try {
    validateAge(-5);
} catch (ValidationException $e) {
    echo $e->getMessage() . "\n";
    print_r($e->getErrors());
} catch (TypeError $e) {
    echo "Type error: " . $e->getMessage();
} finally {
    echo "Validation complete\n";  // always runs
}

// Set global error handler
set_exception_handler(function (Throwable $e) {
    error_log($e->getMessage() . "\n" . $e->getTraceAsString());
    http_response_code(500);
    echo "Something went wrong";
});

Part 12 — Sessions and Cookies

<?php
// sessions.php

session_start();  // Must be called before any output

// Set session variables
$_SESSION['user_id'] = 1;
$_SESSION['username'] = 'Alice';
$_SESSION['logged_in'] = true;

// Read
$username = $_SESSION['username'] ?? 'guest';

// Delete one variable
unset($_SESSION['temp_data']);

// Destroy entire session (logout)
session_destroy();

// Regenerate session ID (prevent fixation attacks)
session_regenerate_id(true);
<?php
// Cookies
$thirtyDays = 60 * 60 * 24 * 30;

setcookie(
    name:     'user_pref',
    value:    'dark_mode',
    expires:  time() + $thirtyDays,
    path:     '/',
    domain:   '',
    secure:   true,   // HTTPS only
    httponly: true,   // No JS access
);

// Read
$pref = $_COOKIE['user_pref'] ?? 'light_mode';

// Delete
setcookie('user_pref', '', time() - 3600);

Part 13 — Three Real Projects

Project 1 — CLI To-Do Manager

<?php
// todo.php
declare(strict_types=1);

const TODO_FILE = __DIR__ . '/todos.json';

function loadTodos(): array {
    if (!file_exists(TODO_FILE)) return [];
    return json_decode(file_get_contents(TODO_FILE), true) ?? [];
}

function saveTodos(array $todos): void {
    file_put_contents(TODO_FILE, json_encode($todos, JSON_PRETTY_PRINT));
}

function addTodo(string $text): void {
    $todos = loadTodos();
    $todos[] = ['id' => uniqid(), 'text' => $text, 'done' => false];
    saveTodos($todos);
    echo "Added: $text\n";
}

function listTodos(): void {
    $todos = loadTodos();
    if (empty($todos)) { echo "No todos!\n"; return; }
    foreach ($todos as $i => $todo) {
        $status = $todo['done'] ? '[x]' : '[ ]';
        echo ($i + 1) . ". $status {$todo['text']} ({$todo['id']})\n";
    }
}

function completeTodo(int $index): void {
    $todos = loadTodos();
    $i = $index - 1;
    if (!isset($todos[$i])) { echo "Invalid index\n"; return; }
    $todos[$i]['done'] = true;
    saveTodos($todos);
    echo "Completed: {$todos[$i]['text']}\n";
}

function deleteTodo(int $index): void {
    $todos = loadTodos();
    $i = $index - 1;
    if (!isset($todos[$i])) { echo "Invalid index\n"; return; }
    $text = $todos[$i]['text'];
    array_splice($todos, $i, 1);
    saveTodos($todos);
    echo "Deleted: $text\n";
}

// CLI entry point
$command = $argv[1] ?? 'list';

match ($command) {
    'add'      => addTodo(implode(' ', array_slice($argv, 2))),
    'list'     => listTodos(),
    'done'     => completeTodo((int) ($argv[2] ?? 0)),
    'delete'   => deleteTodo((int) ($argv[2] ?? 0)),
    default    => print("Usage: php todo.php [add|list|done|delete] [args]\n"),
};
php todo.php add "Buy groceries"
php todo.php add "Learn PHP"
php todo.php list
php todo.php done 1
php todo.php delete 2

Project 2 — Simple REST API (no framework)

<?php
// api.php — Simple REST API for notes
declare(strict_types=1);

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

// Simple in-memory storage (replace with DB in real app)
const DATA_FILE = __DIR__ . '/notes.json';

function loadNotes(): array {
    if (!file_exists(DATA_FILE)) return [];
    return json_decode(file_get_contents(DATA_FILE), true) ?? [];
}

function saveNotes(array $notes): void {
    file_put_contents(DATA_FILE, json_encode(array_values($notes)));
}

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

function getBody(): array {
    $raw = file_get_contents('php://input');
    return json_decode($raw, true) ?? [];
}

$method = $_SERVER['REQUEST_METHOD'];
$path   = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$parts  = array_filter(explode('/', trim($path, '/')));
$parts  = array_values($parts);

// Routes: GET /notes, POST /notes, GET /notes/{id}, PUT /notes/{id}, DELETE /notes/{id}
if ($parts[0] !== 'notes') {
    respond(['error' => 'Not found'], 404);
}

$notes = loadNotes();
$id    = $parts[1] ?? null;

match ([$method, $id !== null]) {
    ['GET', false]    => respond(array_values($notes)),
    ['GET', true]     => isset($notes[$id])
        ? respond($notes[$id])
        : respond(['error' => 'Not found'], 404),
    ['POST', false]   => (function() use (&$notes) {
        $body = getBody();
        $note = ['id' => uniqid(), 'title' => $body['title'] ?? '', 'body' => $body['body'] ?? ''];
        $notes[$note['id']] = $note;
        saveNotes($notes);
        respond($note, 201);
    })(),
    ['PUT', true]     => (function() use (&$notes, $id) {
        if (!isset($notes[$id])) respond(['error' => 'Not found'], 404);
        $body = getBody();
        $notes[$id] = array_merge($notes[$id], $body);
        saveNotes($notes);
        respond($notes[$id]);
    })(),
    ['DELETE', true]  => (function() use (&$notes, $id) {
        if (!isset($notes[$id])) respond(['error' => 'Not found'], 404);
        unset($notes[$id]);
        saveNotes($notes);
        respond(['message' => 'Deleted']);
    })(),
    default           => respond(['error' => 'Method not allowed'], 405),
};
php -S localhost:8000 api.php

# Test with curl
curl -X POST http://localhost:8000/notes \
  -H 'Content-Type: application/json' \
  -d '{"title":"First note","body":"Hello PHP!"}'

curl http://localhost:8000/notes

Project 3 — User Login System (PDO + Sessions)

<?php
// auth.php — complete login/registration with hashed passwords
declare(strict_types=1);

session_start();

// PDO setup
function getDb(): PDO {
    static $pdo = null;
    if ($pdo === null) {
        $pdo = new PDO('sqlite:' . __DIR__ . '/auth.db', null, null, [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $pdo->exec("CREATE TABLE IF NOT EXISTS users (
            id       INTEGER PRIMARY KEY AUTOINCREMENT,
            name     TEXT NOT NULL,
            email    TEXT NOT NULL UNIQUE,
            password TEXT NOT NULL
        )");
    }
    return $pdo;
}

function register(string $name, string $email, string $password): string {
    if (strlen($name) < 2) return "Name too short";
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return "Invalid email";
    if (strlen($password) < 8) return "Password too short";

    $pdo  = getDb();
    $hash = password_hash($password, PASSWORD_BCRYPT);

    try {
        $stmt = $pdo->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?)");
        $stmt->execute([$name, $email, $hash]);
        return "ok";
    } catch (PDOException) {
        return "Email already registered";
    }
}

function login(string $email, string $password): bool {
    $pdo  = getDb();
    $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
    $stmt->execute([$email]);
    $user = $stmt->fetch();

    if ($user && password_verify($password, $user['password'])) {
        session_regenerate_id(true);
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['name']    = $user['name'];
        return true;
    }
    return false;
}

function logout(): void {
    session_unset();
    session_destroy();
}

function currentUser(): ?array {
    return isset($_SESSION['user_id'])
        ? ['id' => $_SESSION['user_id'], 'name' => $_SESSION['name']]
        : null;
}

// Simple router
$action = $_POST['action'] ?? $_GET['action'] ?? '';

switch ($action) {
    case 'register':
        $result = register(
            $_POST['name'] ?? '',
            $_POST['email'] ?? '',
            $_POST['password'] ?? ''
        );
        echo $result === 'ok' ? 'Registered!' : "Error: $result";
        break;

    case 'login':
        $ok = login($_POST['email'] ?? '', $_POST['password'] ?? '');
        echo $ok ? 'Logged in as ' . $_SESSION['name'] : 'Invalid credentials';
        break;

    case 'logout':
        logout();
        echo 'Logged out';
        break;

    case 'me':
        $user = currentUser();
        echo $user ? "Hello, {$user['name']}!" : "Not logged in";
        break;

    default:
        echo 'Auth API: register | login | logout | me';
}

Quick Reference

PHP operators

Category Operators
Arithmetic + - * / % **
Comparison == === != !== < > <= >= <=>
Logical && || ! and or xor
String . (concatenate) .= (append)
Assignment = += -= *= /= .= ??=
Null coalescing ??

Useful PHP functions

Function Purpose
var_dump($x) Debug any variable (type + value)
print_r($arr) Human-readable array/object output
json_encode($data) PHP → JSON string
json_decode($str, true) JSON string → PHP array
date('Y-m-d') Current date as string
time() Current Unix timestamp
rand(1, 100) Random integer
array_key_exists($k, $arr) Key exists?
is_array($x) / is_null($x) Type checks
empty($x) Is empty? (null/0/''/'0'/[]/false)
isset($x) Is set and not null?
intval($x) / floatval($x) Convert to int/float
number_format(1234.5, 2) Format number "1,234.50"
sprintf("%.2f", 3.14159) Format string
str_pad($s, 10, '0', STR_PAD_LEFT) Pad string
explode(',', $str) String → array
implode(', ', $arr) Array → string
preg_match('/\d+/', $str) Regex match
preg_replace('/\s+/', ' ', $str) Regex replace
file_get_contents($path) Read file
file_put_contents($path, $data) Write file
is_file($path) / is_dir($path) File/dir check
glob('*.php') List files by pattern

Learning Path

Stage What to learn Time
1 Syntax, variables, control flow, functions 1–2 weeks
2 Arrays, strings, forms, superglobals, security basics 1–2 weeks
3 OOP: classes, inheritance, interfaces, traits 2–3 weeks
4 MySQL/PDO, prepared statements, CRUD 1–2 weeks
5 Composer, PSR standards, autoloading 1 week
6 Laravel or Symfony basics 4–8 weeks
7 REST APIs, JWT auth, testing (PHPUnit) 3–4 weeks
8 Production: queues, caching, Docker Ongoing

Free resources

Resource URL
PHP.net docs php.net/manual
PHP: The Right Way phptherightway.com
Laracasts (free tier) laracasts.com
PHP School phpschool.io
Laravel docs laravel.com/docs
Symfony docs symfony.com/doc
PHPUnit docs phpunit.de

Common Mistakes

Mistake Problem Fix
Using == instead of === "0" == false is true (loose comparison) Use === for strict type+value check
Echoing user input directly XSS vulnerability Always htmlspecialchars() before output
String interpolation in SQL SQL injection Use PDO prepared statements
$_POST without sanitization Security risk Validate + sanitize all inputs
Not calling session_start() Sessions don't work Call at top of every file that uses sessions
Suppressing errors with @ Hides real problems Fix the root cause; use try/catch
No declare(strict_types=1) Silent type coercions Always declare at top of files
Storing plain text passwords Critical security flaw Use password_hash() / password_verify()

PHP vs Related Terms

Term What it is
PHP Server-side scripting language
PHP 8 Current major version (8.0–8.4) with JIT, named args, enums
Composer PHP's package manager (like npm for JS)
Packagist Central package repository for Composer
Laravel Most popular PHP framework (Rails-like)
Symfony Enterprise PHP framework (powers Laravel internals)
WordPress CMS built on PHP (43% of all websites)
PSR PHP Standards Recommendations (coding standards)
Eloquent Laravel's ORM for database access
Artisan Laravel's CLI (like Rails rails command)
PDO PHP's built-in database abstraction layer
Twig / Blade Templating engines for Symfony / Laravel

FAQ

Q: Is PHP still relevant in 2025?

Yes. PHP 8.x brought named arguments, attributes, enums, fibers, readonly properties, and a JIT compiler. Laravel is one of the most productive web frameworks available. PHP powers ~77% of sites with a known server-side language and has a massive job market.

Q: Should I learn PHP or JavaScript (Node.js)?

Learn both eventually. Start with PHP if you want to work with WordPress/WooCommerce, need shared hosting, or are building a traditional web app. Start with Node.js if you want a unified JS stack or are focused on real-time apps. They are complementary, not competing.

Q: What's the difference between PHP and HTML?

HTML is a markup language that browsers render. PHP is a programming language that runs on the server, generates HTML (or JSON), and sends it to the browser. The browser never sees PHP code.

Q: Do I need a framework to build PHP apps?

No, but you should use one for anything beyond small scripts. Laravel is the standard choice for new projects. Symfony is preferred for large enterprise apps. Start with plain PHP to understand the fundamentals, then move to Laravel.

Q: What is the difference between include and require?

include shows a warning and continues if the file is missing. require throws a fatal error and stops execution. Use require for files your app cannot run without. Use include_once / require_once to prevent double-inclusion.

Q: How long does it take to learn PHP?

You can build functional web apps within 4–6 weeks of consistent study. To be job-ready (with Laravel, MySQL, REST APIs, testing), expect 3–6 months of practice. PHP has a gentle learning curve compared to statically typed languages.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools