PHP & Laravel — Zero to Hero Episode 14: Interfaces, Abstract Classes, and Namespaces — The Last Core PHP Concepts Before Laravel

What Are We Doing in This Post?

This is the final Core PHP episode. After this, we start Laravel.

In Episode 13 we learned classes, objects, inheritance, and encapsulation. Those are the foundations. This episode covers three more OOP concepts that are used everywhere in Laravel's source code — interfaces, abstract classes, and namespaces.

You will not build a full Laravel application using these directly as a beginner. But you will encounter them constantly while reading Laravel code. Understanding them now means Laravel will make sense immediately instead of feeling like an alien codebase.

Let us finish Core PHP strong.


Part 1 — Interfaces

The Problem Interfaces Solve

Imagine you are building a payment system. Your application needs to support multiple payment gateways — Razorpay, PayPal, and Stripe. Each gateway has different internal code, different API calls, different response formats.

But from your application's point of view, you want to treat them all the same way. You want to call charge(), refund(), and get_transaction_id() on any of them without caring which one you are using.

How do you guarantee that every payment gateway class has exactly these methods? You use an interface.

What is an Interface?

An interface is a contract. It says: any class that implements this interface must have these specific methods. The interface does not provide any implementation — just the list of method signatures that must exist.

Real world analogy: Think of an interface like a job description. The job description for a Driver says: must be able to start the vehicle, steer, and stop. Whether you are driving a car, a bus, or a truck — different vehicles, different internal mechanics — you must be able to do all three things. The job description enforces the contract without caring how each vehicle implements it internally.

<?php

interface PaymentGateway {
    public function charge($amount, $currency);
    public function refund($transaction_id);
    public function get_transaction_id();
}

?>

An interface only contains method signatures — the method name, parameters, and return nothing. No code inside the methods. No properties. Just the contract.

Implementing an Interface

<?php

interface PaymentGateway {
    public function charge($amount, $currency);
    public function refund($transaction_id);
    public function get_transaction_id();
}

class RazorpayGateway implements PaymentGateway {

    private $transaction_id;

    public function charge($amount, $currency) {
        $this->transaction_id = "RPY-" . rand(100000, 999999);
        echo "Charged Rs. $amount via Razorpay. Transaction: $this->transaction_id";
        echo "<br>";
    }

    public function refund($transaction_id) {
        echo "Refund initiated on Razorpay for transaction: $transaction_id";
        echo "<br>";
    }

    public function get_transaction_id() {
        return $this->transaction_id;
    }
}

class PayPalGateway implements PaymentGateway {

    private $transaction_id;

    public function charge($amount, $currency) {
        $this->transaction_id = "PPL-" . rand(100000, 999999);
        echo "Charged $currency $amount via PayPal. Transaction: $this->transaction_id";
        echo "<br>";
    }

    public function refund($transaction_id) {
        echo "Refund initiated on PayPal for transaction: $transaction_id";
        echo "<br>";
    }

    public function get_transaction_id() {
        return $this->transaction_id;
    }
}

function process_payment(PaymentGateway $gateway, $amount) {
    $gateway->charge($amount, "INR");
    echo "Transaction ID: " . $gateway->get_transaction_id();
    echo "<br>";
}

$razorpay = new RazorpayGateway();
$paypal   = new PayPalGateway();

process_payment($razorpay, 1999);
process_payment($paypal, 2999);

?>

Output:

Charged Rs. 1999 via Razorpay. Transaction: RPY-847291 Transaction ID: RPY-847291 Charged INR 2999 via PayPal. Transaction: PPL-192847 Transaction ID: PPL-192847

The process_payment() function accepts any object that implements PaymentGateway. It does not care whether it is Razorpay or PayPal — it just knows both of them have the charge() and get_transaction_id() methods because the interface guarantees it.

If you create a new StripeGateway class but forget to implement the refund() method, PHP throws a fatal error immediately. The interface enforces the contract strictly.

This is called polymorphism — different classes being used interchangeably because they all follow the same interface contract.

In Laravel: interfaces are used everywhere. Laravel's Cache, Mail, Queue, and Storage systems all work through interfaces. This is why you can switch from file-based caching to Redis caching by changing one line in a config file — both implement the same interface.


Part 2 — Abstract Classes

The Difference Between Interface and Abstract Class

An interface is a pure contract — no implementation at all. An abstract class sits in the middle — it can have some implemented methods and some unimplemented methods.

Think of it this way. An interface says: you must have these methods, figure out the implementation yourself. An abstract class says: here are some methods I have already implemented for you, but you must implement these specific ones yourself.

Real world analogy: An abstract class is like a house blueprint that already has the foundation and walls drawn in — the structural parts are fixed. But the interior layout, the rooms, the furniture placement — those are left for the specific house design to decide. You cannot build directly from an abstract blueprint. You must extend it and fill in the missing pieces.

<?php

abstract class Shape {

    protected $color;

    public function __construct($color) {
        $this->color = $color;
    }

    abstract public function area();
    abstract public function perimeter();

    public function describe() {
        echo "This is a $this->color shape.";
        echo "<br>";
        echo "Area: " . $this->area();
        echo "<br>";
        echo "Perimeter: " . $this->perimeter();
        echo "<br>";
    }
}

class Circle extends Shape {

    private $radius;

    public function __construct($color, $radius) {
        parent::__construct($color);
        $this->radius = $radius;
    }

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

    public function perimeter() {
        return round(2 * M_PI * $this->radius, 2);
    }
}

class Rectangle extends Shape {

    private $width;
    private $height;

    public function __construct($color, $width, $height) {
        parent::__construct($color);
        $this->width  = $width;
        $this->height = $height;
    }

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

    public function perimeter() {
        return 2 * ($this->width + $this->height);
    }
}

$circle    = new Circle("red", 7);
$rectangle = new Rectangle("blue", 10, 5);

$circle->describe();
echo "<br>";
$rectangle->describe();

?>

Output:

This is a red shape. Area: 153.94 Perimeter: 43.98

This is a blue shape. Area: 50 Perimeter: 30

The abstract keyword on area() and perimeter() means: every class that extends Shape must implement these methods. Shape provides the shared describe() method — both Circle and Rectangle get it for free through inheritance.

You cannot do new Shape() directly — PHP will throw an error. You can only instantiate the concrete child classes.

Interface vs Abstract Class — When to Use Which

Use an interface when you want to define a contract that completely unrelated classes must follow. A Car and a Bird are very different things — but both can implement a Moveable interface.

Use an abstract class when you have a group of related classes that share some common implementation but must each provide specific behavior. Circle and Rectangle are both Shapes — they share the describe() method but each calculates area differently.

In Laravel: the base Controller class is abstract. Eloquent's Model class has abstract-like behavior. Middleware classes implement an interface called Handle. You will see both constantly.


Part 3 — Namespaces

The Problem Namespaces Solve

As a project grows, you end up with hundreds of PHP files and classes. Name conflicts become a real problem. What if you have a User class in your authentication system and another User class in your admin panel? PHP cannot have two classes with the same name loaded at the same time.

Real world analogy: Think of namespaces like addresses. There can be a "Main Street" in Delhi and a "Main Street" in Mumbai — same street name, two completely different locations. The city name distinguishes them. Namespaces work the same way — they give your classes a unique address so they never conflict with each other.

<?php

namespace App\Auth;

class User {

    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function get_role() {
        return "Authenticated User";
    }
}

?>
<?php

namespace App\Admin;

class User {

    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function get_role() {
        return "Administrator";
    }
}

?>

Both files have a class called User. But they live in different namespaces — App\Auth and App\Admin. PHP treats them as completely separate classes.

Using Namespaced Classes

<?php

require_once "AuthUser.php";
require_once "AdminUser.php";

use App\Auth\User as AuthUser;
use App\Admin\User as AdminUser;

$auth_user  = new AuthUser("Gagan");
$admin_user = new AdminUser("Rahul");

echo $auth_user->name . " — " . $auth_user->get_role();
echo "<br>";
echo $admin_user->name . " — " . $admin_user->get_role();

?>

Output:

Gagan — Authenticated User Rahul — Administrator

The use keyword imports a namespaced class so you can refer to it by a shorter name. as gives it an alias when two classes share the same short name.

In Laravel: every file starts with namespace App\Http\Controllers or namespace App\Models or similar. Every time you open a Laravel controller or model, you see use statements at the top importing other classes. Now you know exactly what those mean.


Part 4 — Autoloading With Composer

Right now we use require_once to include every file we need. In a large project with hundreds of classes, this becomes unmanageable — you would need dozens of require_once statements at the top of every file.

Autoloading solves this. With autoloading, PHP automatically loads the right class file the moment you use that class — no manual require_once needed.

Composer is PHP's dependency manager and it handles autoloading for us. Laravel uses Composer — you will install it before starting Laravel. Let us set it up now so you are ready.

Install Composer:

Go to getcomposer.org and download the Windows installer. Run it and follow the steps. After installation, open a new Command Prompt and type:

composer --version

You should see a version number. Composer is installed.

Setting Up Autoloading in a Project:

In your phplearning folder, create a file called composer.json:

{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

This tells Composer: any class in the App namespace should be loaded from the src/ folder.

Create the folder src/ inside phplearning. Now create a class file src/Product.php:

<?php

namespace App;

class Product {

    public $name;
    public $price;

    public function __construct($name, $price) {
        $this->name  = $name;
        $this->price = $price;
    }

    public function get_formatted_price() {
        return "Rs. " . number_format($this->price, 2);
    }
}

?>

Now open Command Prompt, navigate to your phplearning folder, and run:

composer dump-autoload

Composer generates an autoload file. Now create index_autoload.php in phplearning:

<?php

require_once "vendor/autoload.php";

use App\Product;

$product = new Product("Mechanical Keyboard", 2499);
echo $product->name . " — " . $product->get_formatted_price();

?>

Visit http://localhost:8080/phplearning/index_autoload.php

Output: Mechanical Keyboard — Rs. 2,499.00

No manual require_once for the Product class. Composer loaded it automatically the moment you used it. This is exactly how Laravel works — you never manually include class files in Laravel. Composer's autoloader handles everything.

PSR-4 is a PHP standard that maps namespaces to folder structures. App\Models\User maps to src/Models/User.php. App\Http\Controllers\UserController maps to src/Http/Controllers/UserController.php. Laravel follows this standard throughout its entire codebase.


Putting It All Together — What You Now Know

Let us take a step back and look at what you have learned across all 14 Core PHP episodes.

Variables, data types, operators — how PHP stores and works with data.

Conditionals and loops — how PHP makes decisions and repeats tasks.

Arrays and functions — how PHP manages collections and reusable logic.

Forms, sessions, cookies — how PHP interacts with users and remembers state.

MySQL with PDO and prepared statements — how PHP stores and retrieves data permanently.

OOP — classes, objects, constructors, access modifiers, encapsulation, inheritance, interfaces, abstract classes.

Namespaces and autoloading — how PHP organizes large codebases.

This is a complete foundation. Most developers who struggle with Laravel struggled because they skipped this foundation. You did not skip it.


What Did We Learn in This Post?

Interfaces define a contract — a list of methods that any implementing class must provide. They enable polymorphism — different classes used interchangeably through a shared contract.

Abstract classes sit between interfaces and regular classes — they can have both implemented and unimplemented methods. You cannot instantiate an abstract class directly.

Use an interface for unrelated classes sharing a behavior contract. Use an abstract class for related classes sharing partial implementation.

Namespaces give classes a unique address to prevent naming conflicts in large projects. The use keyword imports namespaced classes. Laravel uses namespaces in every single file.

Composer manages PHP dependencies and handles autoloading via PSR-4. Every class in the right namespace and folder is loaded automatically — no manual require_once needed. Laravel is entirely Composer-based.


What is Coming in Episode 15?

Episode 15 is a major milestone. Core PHP is complete. We start Laravel.

We will install Laravel using Composer, understand what gets created, start the development server, and explore the folder structure in detail. Every folder, every file — what it is, what it does, and why it exists.

This is where everything accelerates. The foundation is solid. Now we build on it.

See you in the next one.


Next Episode: Installing Laravel and Understanding the Project Structure

This is Episode 14 of the PHP and Laravel — Zero to Hero series.


PHP & Laravel — Zero to Hero Episode 13: Object Oriented PHP — Classes, Objects, and the Foundation of Laravel

What Are We Doing in This Post?

Every episode so far has been procedural PHP — code that runs from top to bottom, one line after another. Functions helped us organize and reuse code. But there is a more powerful way to structure programs.

Object Oriented Programming — OOP — is a completely different way of thinking about code. Instead of writing a series of instructions, you model your program around real-world things called objects.

This is not optional knowledge. Laravel is built entirely on OOP. Every single feature of Laravel — controllers, models, middleware, requests, responses — is a class. If you do not understand OOP, Laravel will feel like magic you cannot control. After this episode, it will feel like logic you can read and write yourself.


The Problem With Procedural Code

Imagine you are building a school management system. You need to manage students. Each student has a name, age, grade, and email. You need functions to enroll them, calculate their GPA, and send them notifications.

In procedural PHP you would have scattered arrays and functions all over the place:

<?php

$student1_name  = "Gagan";
$student1_age   = 20;
$student1_grade = "A";

$student2_name  = "Rahul";
$student2_age   = 22;
$student2_grade = "B";

function calculate_gpa($grade) {
    if ($grade == "A") return 4.0;
    if ($grade == "B") return 3.0;
    return 0.0;
}

?>

This becomes unmanageable fast. Add 50 students and this is chaos. There is no connection between the data and the functions that work on it.

OOP solves this by bundling related data and functions together into a single unit called a class.


What is a Class?

A class is a blueprint. It defines what a thing is and what it can do.

Real world analogy: Think of a class like an architectural blueprint for a house. The blueprint describes how many rooms the house has, where the doors are, what materials to use. The blueprint itself is not a house — it is the plan. When you actually build a house from that blueprint, that built house is an object. You can build a hundred different houses from the same blueprint — each one is a separate object, but all follow the same plan.

In PHP: the class is the blueprint. The object is the actual instance created from that blueprint.

<?php

class Student {

    public $name;
    public $age;
    public $grade;

    public function greet() {
        echo "Hello, my name is $this->name and I am $this->age years old.";
    }

    public function calculate_gpa() {
        if ($this->grade == "A") return 4.0;
        if ($this->grade == "B") return 3.0;
        if ($this->grade == "C") return 2.0;
        return 0.0;
    }
}

?>

The variables inside a class are called properties. The functions inside a class are called methods. Together they define what a Student is and what a Student can do.

$this is a special variable inside a class that refers to the current object — the specific instance of the class that is running the method. When you call a method on an object, $this gives that method access to the object's own properties.


Creating Objects — Instances of a Class

<?php

$student1 = new Student();
$student1->name  = "Gagan";
$student1->age   = 20;
$student1->grade = "A";

$student2 = new Student();
$student2->name  = "Rahul";
$student2->age   = 22;
$student2->grade = "B";

$student1->greet();
echo "<br>";
echo "GPA: " . $student1->calculate_gpa();
echo "<br>";

$student2->greet();
echo "<br>";
echo "GPA: " . $student2->calculate_gpa();

?>

Output:

Hello, my name is Gagan and I am 20 years old. GPA: 4.0 Hello, my name is Rahul and I am 22 years old. GPA: 3.0

new Student() creates a new object from the Student blueprint. The -> operator accesses properties and methods on an object.

$student1 and $student2 are two completely separate objects. They both follow the Student blueprint but hold completely different data independently.


The Constructor — Setting Up an Object at Birth

Setting properties one by one after creating an object is repetitive. PHP gives you a special method called __construct() — the constructor. It runs automatically the moment you create a new object with new.

<?php

class Student {

    public $name;
    public $age;
    public $grade;

    public function __construct($name, $age, $grade) {
        $this->name  = $name;
        $this->age   = $age;
        $this->grade = $grade;
    }

    public function greet() {
        echo "Hello, my name is $this->name.";
    }

    public function calculate_gpa() {
        if ($this->grade == "A") return 4.0;
        if ($this->grade == "B") return 3.0;
        if ($this->grade == "C") return 2.0;
        return 0.0;
    }

    public function get_info() {
        return "$this->name | Age: $this->age | Grade: $this->grade | GPA: " . $this->calculate_gpa();
    }
}

$student1 = new Student("Gagan", 20, "A");
$student2 = new Student("Rahul", 22, "B");
$student3 = new Student("Priya", 21, "C");

echo $student1->get_info(); echo "<br>";
echo $student2->get_info(); echo "<br>";
echo $student3->get_info();

?>

Output:

Gagan | Age: 20 | Grade: A | GPA: 4.0 Rahul | Age: 22 | Grade: B | GPA: 3.0 Priya | Age: 21 | Grade: C | GPA: 2.0

Now creating a student is clean and compact. One line per student, all data provided upfront. This is exactly how Laravel models work — when Eloquent fetches a record from the database, it creates an object and populates its properties through a constructor-like mechanism.


Access Modifiers — public, protected, private

Access modifiers control who can access a property or method.

public — accessible from anywhere. Inside the class, outside the class, from child classes. This is the default.

private — accessible only from inside the class itself. Nothing outside can read or change it directly.

protected — accessible from inside the class and from child classes (we cover inheritance shortly). Not accessible from outside.

<?php

class BankAccount {

    public    $owner;
    private   $balance;
    protected $account_number;

    public function __construct($owner, $balance, $account_number) {
        $this->owner          = $owner;
        $this->balance        = $balance;
        $this->account_number = $account_number;
    }

    public function deposit($amount) {
        if ($amount > 0) {
            $this->balance += $amount;
            echo "Deposited Rs. $amount. New balance: Rs. $this->balance";
        }
    }

    public function withdraw($amount) {
        if ($amount > $this->balance) {
            echo "Insufficient balance.";
        } else {
            $this->balance -= $amount;
            echo "Withdrawn Rs. $amount. New balance: Rs. $this->balance";
        }
    }

    public function get_balance() {
        return $this->balance;
    }
}

$account = new BankAccount("Gagan", 5000, "ACC-001");

$account->deposit(2000);
echo "<br>";
$account->withdraw(1000);
echo "<br>";
echo "Balance: Rs. " . $account->get_balance();

echo "<br>";
echo $account->owner;

?>

Output:

Deposited Rs. 2000. New balance: Rs. 7000 Withdrawn Rs. 1000. New balance: Rs. 6000 Balance: Rs. 6000 Gagan

If you tried to access $account->balance directly from outside the class, PHP would throw an error — because it is private. The only way to interact with the balance is through the public methods deposit(), withdraw(), and get_balance(). This is called encapsulation — hiding internal details and exposing only what is necessary.

Real world analogy: Your bank account balance is private. You cannot just reach into the bank's database and change your own balance. You can only interact with it through the bank's controlled methods — deposit, withdraw, check balance. That is encapsulation.


Getters and Setters

When a property is private but you still need controlled access to it from outside, you use getter and setter methods.

<?php

class Product {

    private $name;
    private $price;

    public function __construct($name, $price) {
        $this->name  = $name;
        $this->price = $price;
    }

    public function get_name() {
        return $this->name;
    }

    public function get_price() {
        return $this->price;
    }

    public function set_price($price) {
        if ($price < 0) {
            echo "Price cannot be negative.";
            return;
        }
        $this->price = $price;
    }
}

$product = new Product("Laptop", 45000);
echo $product->get_name() . " — Rs. " . $product->get_price();
echo "<br>";

$product->set_price(42000);
echo "Updated price: Rs. " . $product->get_price();
echo "<br>";

$product->set_price(-500);

?>

Output:

Laptop — Rs. 45000 Updated price: Rs. 42000 Price cannot be negative.

The setter validates the data before allowing the change. This is the power of encapsulation — the object controls how its own data is modified.


Inheritance — Building on Existing Classes

Inheritance lets one class extend another, inheriting all its properties and methods while adding or overriding its own.

Real world analogy: Think of Animal as a parent class. Dog and Cat are child classes. All animals eat and breathe — those are inherited behaviors. But dogs bark and cats meow — those are behaviors specific to each child class.

<?php

class Animal {

    public $name;
    public $age;

    public function __construct($name, $age) {
        $this->name = $name;
        $this->age  = $age;
    }

    public function eat() {
        echo "$this->name is eating.";
    }

    public function describe() {
        echo "I am $this->name and I am $this->age years old.";
    }
}

class Dog extends Animal {

    public $breed;

    public function __construct($name, $age, $breed) {
        parent::__construct($name, $age);
        $this->breed = $breed;
    }

    public function bark() {
        echo "$this->name says: Woof!";
    }

    public function describe() {
        echo "I am $this->name, a $this->breed, and I am $this->age years old.";
    }
}

class Cat extends Animal {

    public function meow() {
        echo "$this->name says: Meow!";
    }
}

$dog = new Dog("Bruno", 3, "Labrador");
$cat = new Cat("Whiskers", 2);

$dog->describe(); echo "<br>";
$dog->bark();     echo "<br>";
$dog->eat();      echo "<br>";

$cat->describe(); echo "<br>";
$cat->meow();     echo "<br>";
$cat->eat();

?>

Output:

I am Bruno, a Labrador, and I am 3 years old. Bruno says: Woof! Bruno is eating. I am Whiskers and I am 2 years old. Whiskers says: Meow! Whiskers is eating.

extends makes Dog and Cat inherit everything from Animal. parent::__construct() calls the parent class constructor from inside the child constructor — so we do not have to re-write the $name and $age assignment logic.

Dog overrides the describe() method with its own version that includes the breed. Cat does not override it, so it uses Animal's version. This is called method overriding — a child class can replace any inherited method with its own implementation.

In Laravel: every Controller you write extends the base Controller class. Every Model extends the Model class. You get all of Laravel's built-in functionality for free through inheritance, and you only write the code specific to your feature.


Static Properties and Methods

Sometimes you want a property or method to belong to the class itself — not to any specific object. These are called static members.

<?php

class Counter {

    private static $count = 0;

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

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

Counter::increment();
Counter::increment();
Counter::increment();

echo "Total count: " . Counter::get_count();

?>

Output: Total count: 3

Static members are accessed with :: instead of ->. Inside the class, you use self:: instead of $this-> because static members belong to the class, not to an object instance.

Real world use in Laravel: Laravel's DB facade, Route facade, and many helper classes use static methods. When you write Route::get() or DB::table(), you are calling static methods.


A Real World Example — A Simple User Class With Database

Let us connect everything — OOP, PDO, and a real database — into one practical example.

Create User.php (capital U — by convention, class files are named with a capital letter):

<?php

require_once "pdo_db.php";

class User {

    private $pdo;

    public $id;
    public $name;
    public $email;
    public $created_at;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

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

    public function find($id) {
        $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = :id");
        $stmt->execute([":id" => $id]);
        $data = $stmt->fetch();

        if ($data) {
            $this->id         = $data["id"];
            $this->name       = $data["name"];
            $this->email      = $data["email"];
            $this->created_at = $data["created_at"];
            return true;
        }

        return false;
    }

    public function all() {
        $stmt = $this->pdo->prepare("SELECT * FROM users ORDER BY id DESC");
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function delete($id) {
        $stmt = $this->pdo->prepare("DELETE FROM users WHERE id = :id");
        $stmt->execute([":id" => $id]);
        return $stmt->rowCount();
    }

    public function get_display_name() {
        return ucwords(strtolower($this->name));
    }
}

$user = new User($pdo);

$new_id = $user->create("anjali sharma", "anjali@example.com");
echo "Created user with ID: $new_id <br>";

if ($user->find($new_id)) {
    echo "Found: " . $user->get_display_name() . " — " . $user->email . "<br>";
}

$all_users = $user->all();
echo "Total users: " . count($all_users) . "<br>";

foreach ($all_users as $u) {
    echo $u["id"] . " — " . $u["name"] . "<br>";
}

?>

This User class encapsulates all database operations for the users table. You create one User object and call methods on it. No raw SQL scattered across your files.

Look familiar? This is almost exactly what Laravel's Eloquent Model does. In Laravel you will write:

<?php

$user = User::create(["name" => "Gagan", "email" => "gagan@example.com"]);
$user = User::find(1);
$users = User::all();

?>

That is Eloquent — a much more advanced version of exactly what we just built. Now you understand what is happening underneath.


What Did We Learn in This Post?

A class is a blueprint that bundles related data (properties) and behavior (methods) together. An object is a specific instance created from that blueprint using new.

$this refers to the current object inside a class method. The constructor __construct() runs automatically when an object is created.

public properties and methods are accessible from anywhere. private ones are accessible only inside the class. protected ones are accessible inside the class and its children.

Encapsulation hides internal details and exposes only controlled access points — keeping data safe and code predictable.

Inheritance lets a child class extend a parent class, inheriting all its properties and methods while adding or overriding its own.

Static methods and properties belong to the class itself, not to any object. Accessed with ::.

Laravel is built entirely on OOP — every Controller, Model, and Middleware is a class. Everything you learned in this episode is the direct foundation of everything Laravel does.


What is Coming in Episode 14?

We have one more Core PHP topic before we start Laravel — PHP interfaces and abstract classes, plus a brief look at namespaces and autoloading.

These three concepts are exactly what Laravel uses to organize its massive codebase. Interfaces define contracts. Abstract classes define partial blueprints. Namespaces prevent naming conflicts across hundreds of files. Autoloading loads class files automatically without require_once everywhere.

After Episode 14, we start Laravel. The finish line of Core PHP is one episode away.

See you in the next one.


Next Episode: Interfaces, Abstract Classes, and Namespaces — The Last Core PHP Concepts Before Laravel

This is Episode 13 of the PHP and Laravel — Zero to Hero series.


PHP & Laravel — Zero to Hero Episode 12: Prepared Statements and PDO — The Professional Way to Handle Databases

What Are We Doing in This Post?

In Episode 11 we connected PHP to MySQL and built a working CRUD application. We used mysqli_real_escape_string() to protect against SQL injection.

But here is the truth — that approach, while functional, is old-fashioned. Professional PHP developers do not use it anymore.

The modern, secure, and universally recommended way to interact with databases in PHP is PDO with Prepared Statements. This is also exactly what Laravel uses internally. Understanding this episode means you will understand what Laravel is doing under the hood when it queries your database.


What is SQL Injection — And Why Should You Lose Sleep Over It?

Before we jump into PDO, let us truly understand the problem it solves.

Imagine your login form. A user enters their username and you build this SQL query:

<?php

$username = $_POST["username"];
$sql = "SELECT * FROM users WHERE username = '$username'";

?>

Now imagine a malicious user types this into the username field:

' OR '1'='1

Your query becomes:

SELECT * FROM users WHERE username = '' OR '1'='1'

The condition '1'='1' is always true. This query now returns every single user in your database. The attacker is logged in without a valid password. They have access to everything.

Real world analogy: Imagine a security guard checking IDs at a club entrance. He asks for your name and checks it against a list. But instead of your name, you say "Anyone OR everyone." The guard, following instructions literally, lets in literally everyone because "everyone" matches.

SQL injection has caused some of the biggest data breaches in history. It brought down companies, exposed millions of passwords, and leaked government data. It is not theoretical — it is the most common web attack in the world.

Prepared statements eliminate this completely — not by escaping, but by separating the SQL structure from the data entirely.


What is PDO?

PDO stands for PHP Data Objects. It is a database abstraction layer built into PHP.

MySQLi only works with MySQL. PDO works with twelve different database systems — MySQL, PostgreSQL, SQLite, Oracle, and more — using the exact same code. You switch databases by changing one line.

PDO also has a cleaner, more modern API that is much closer to how Laravel's Eloquent works internally.


Connecting to MySQL With PDO

Create a new file called pdo_db.php in your phplearning folder:

<?php

$host     = "localhost";
$dbname   = "phplearning";
$username = "root";
$password = "";

try {
    $pdo = new PDO(
        "mysql:host=$host;dbname=$dbname;charset=utf8mb4",
        $username,
        $password
    );

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

} catch (PDOException $e) {
    die("Connection failed: " . $e->getMessage());
}

?>

Let us break this down carefully.

new PDO() creates a new PDO connection. The first argument is called the DSN — Data Source Name. It tells PDO which database driver to use (mysql), where the server is (localhost), which database to connect to (phplearning), and what character encoding to use (utf8mb4 — the proper encoding that supports all characters including emoji).

The second and third arguments are the username and password.

setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) tells PDO to throw exceptions when something goes wrong — instead of silently failing. This means database errors will not pass quietly and you will always know when something breaks.

setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC) tells PDO to return rows as associative arrays by default — so $row["name"] instead of $row[0]. Much more readable.

The try/catch block handles connection failures gracefully. If the connection fails, the PDOException is caught and we display a clean error message instead of a confusing PHP crash.


What Are Prepared Statements?

A prepared statement splits a database query into two completely separate steps.

Step 1 — Send the SQL structure to MySQL first, with placeholders where the user data will go. MySQL parses and compiles the query at this point.

Step 2 — Send the actual data separately. MySQL inserts the data into the already-compiled query.

Because MySQL compiled the query before it ever saw the user data, it is impossible for user data to change the structure of the query. The data is just data — it can never become SQL code.

Real world analogy: Think of a prepared statement like a pre-printed form. The form structure — the boxes, labels, and fields — is fixed and printed in advance. The user only fills in the blank boxes. No matter what the user writes in the boxes, they cannot change the structure of the form itself. Prepared statements work exactly the same way — the SQL structure is fixed, the user data just fills the blanks.


Prepared Statements in Action — INSERT

Create pdo_insert.php:

<?php

require_once "pdo_db.php";

$name  = "Rahul Verma";
$email = "rahul@example.com";

$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");

$stmt->execute([
    ":name"  => $name,
    ":email" => $email
]);

echo "User added. ID: " . $pdo->lastInsertId();

?>

$pdo->prepare() sends the SQL structure to MySQL with named placeholders — :name and :email — where the real values will go. MySQL compiles this query template.

$stmt->execute() sends the actual data as an array. PDO binds each value to its placeholder and MySQL executes the query safely. The user data never touches the SQL structure.

lastInsertId() returns the auto-incremented ID of the row that was just inserted — useful when you need to immediately reference the new record.

Named placeholders (:name, :email) are cleaner and more readable than positional placeholders (?). You can use either — we will see both shortly.


Prepared Statements — SELECT With One Row

<?php

require_once "pdo_db.php";

$email = "rahul@example.com";

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute([":email" => $email]);

$user = $stmt->fetch();

if ($user) {
    echo "Found: " . $user["name"] . " — " . $user["email"];
} else {
    echo "No user found with that email.";
}

?>

fetch() retrieves a single row. Because we set FETCH_ASSOC as the default, it comes back as an associative array automatically.

If no row matches, fetch() returns false — which is why we check if ($user) before accessing it.


Prepared Statements — SELECT Multiple Rows

<?php

require_once "pdo_db.php";

$stmt = $pdo->prepare("SELECT * FROM users ORDER BY id DESC");
$stmt->execute();

$users = $stmt->fetchAll();

foreach ($users as $user) {
    echo $user["id"] . " — " . $user["name"] . " — " . $user["email"];
    echo "<br>";
}

?>

fetchAll() retrieves all matching rows at once and returns them as an array of associative arrays. You loop through them with foreach exactly like any other array.


Prepared Statements — UPDATE

<?php

require_once "pdo_db.php";

$id    = 1;
$name  = "Gagan Singh";
$email = "gagan.singh@example.com";

$stmt = $pdo->prepare("UPDATE users SET name = :name, email = :email WHERE id = :id");

$stmt->execute([
    ":name"  => $name,
    ":email" => $email,
    ":id"    => $id
]);

echo "Rows updated: " . $stmt->rowCount();

?>

rowCount() returns the number of rows affected by the last INSERT, UPDATE, or DELETE query. Useful for confirming that the operation actually changed something.


Prepared Statements — DELETE

<?php

require_once "pdo_db.php";

$id = 2;

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

echo "User deleted. Rows affected: " . $stmt->rowCount();

?>

Positional Placeholders — The ? Syntax

Instead of named placeholders like :name, you can use question marks as positional placeholders. The values are then passed in order.

<?php

require_once "pdo_db.php";

$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute(["Priya Sharma", "priya@example.com"]);

echo "User added.";

?>

The first ? maps to the first value in the array, the second ? to the second value, and so on. Named placeholders are generally preferred because they are clearer and order-independent, but positional placeholders are shorter when you have many parameters.


A Complete PDO CRUD Application

Let us rebuild our user management system from Episode 11 — this time using PDO and prepared statements properly.

Create pdo_users.php:

<?php

require_once "pdo_db.php";

$message = "";

if ($_SERVER["REQUEST_METHOD"] == "POST") {

    $action = $_POST["action"] ?? "";

    if ($action == "add") {
        $name  = trim($_POST["name"]  ?? "");
        $email = trim($_POST["email"] ?? "");

        if (!empty($name) && !empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
            $stmt->execute([":name" => $name, ":email" => $email]);
            $message = "User added successfully. ID: " . $pdo->lastInsertId();
        } else {
            $message = "Please provide a valid name and email.";
        }
    }

    if ($action == "delete") {
        $id   = (int) ($_POST["id"] ?? 0);
        $stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
        $stmt->execute([":id" => $id]);
        $message = "User deleted.";
    }
}

$stmt  = $pdo->prepare("SELECT * FROM users ORDER BY id DESC");
$stmt->execute();
$users = $stmt->fetchAll();

?>
<!DOCTYPE html>
<html>
<head>
    <title>User Management — PDO</title>
</head>
<body style="font-family:sans-serif; max-width:720px; margin:40px auto; padding:0 16px;">

<h2>User Management — PDO Version</h2>

<?php if ($message): ?>
    <p style="color:green; font-weight:bold;"><?php echo htmlspecialchars($message); ?></p>
<?php endif; ?>

<h3>Add New User</h3>
<form method="POST" action="pdo_users.php">
    <input type="hidden" name="action" value="add" />
    <input type="text"  name="name"  placeholder="Full Name"     style="padding:8px; margin-right:8px;" required />
    <input type="email" name="email" placeholder="Email Address" style="padding:8px; margin-right:8px;" required />
    <button type="submit" style="padding:8px 16px; background:#6366f1; color:#fff; border:none; border-radius:4px; cursor:pointer;">Add User</button>
</form>

<h3 style="margin-top:32px;">All Users (<?php echo count($users); ?>)</h3>

<?php if (!empty($users)): ?>
<table border="1" cellpadding="10" cellspacing="0" style="width:100%; border-collapse:collapse;">
    <tr style="background:#f1f5f9;">
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
        <th>Joined</th>
        <th>Action</th>
    </tr>
    <?php foreach ($users as $user): ?>
    <tr>
        <td><?php echo $user["id"]; ?></td>
        <td><?php echo htmlspecialchars($user["name"]); ?></td>
        <td><?php echo htmlspecialchars($user["email"]); ?></td>
        <td><?php echo $user["created_at"]; ?></td>
        <td>
            <form method="POST" action="pdo_users.php" style="display:inline;">
                <input type="hidden" name="action" value="delete" />
                <input type="hidden" name="id"     value="<?php echo $user['id']; ?>" />
                <button type="submit"
                        style="background:#ef4444; color:#fff; border:none; padding:4px 10px; border-radius:4px; cursor:pointer;"
                        onclick="return confirm('Delete this user?')">
                    Delete
                </button>
            </form>
        </td>
    </tr>
    <?php endforeach; ?>
</table>
<?php else: ?>
    <p>No users yet. Add one above.</p>
<?php endif; ?>

</body>
</html>

Visit http://localhost:8080/phplearning/pdo_users.php

This does everything the Episode 11 version did — but now with proper prepared statements, email validation, and PDO's clean API. Zero SQL injection risk.


PDO vs MySQLi — Quick Comparison

PDO works with 12 database systems. MySQLi works only with MySQL.

PDO has a cleaner and more consistent API. MySQLi mixes procedural and object-oriented styles.

PDO prepared statements use named placeholders. MySQLi uses positional only.

PDO is what Laravel uses internally. Understanding PDO makes Laravel feel familiar immediately.

For any new project, always use PDO. The only reason to learn MySQLi was so that you understand legacy code — which you will encounter in real jobs.


What Did We Learn in This Post?

SQL injection is the most common web attack — user input manipulates the SQL query structure to bypass security or destroy data.

PDO is PHP's modern database abstraction layer. It works with twelve database systems and has a clean, consistent API.

Prepared statements separate SQL structure from user data completely — making SQL injection impossible by design.

prepare() sends the SQL template to MySQL. execute() sends the data separately. fetch() retrieves one row. fetchAll() retrieves all rows. rowCount() returns affected rows. lastInsertId() returns the new row's ID.

Named placeholders (:name) are cleaner than positional placeholders (?). Both are valid.

PDO is what Laravel uses internally — mastering it now means Laravel's database layer will feel completely natural.


What is Coming in Episode 13?

We have now covered the core of PHP — variables, operators, conditionals, loops, arrays, functions, forms, sessions, and databases.

Episode 13 starts the final Core PHP section: Object Oriented PHP. This is where we learn classes, objects, properties, methods, constructors, inheritance, and access modifiers. OOP is absolutely essential before starting Laravel — because Laravel is built entirely on object oriented principles. Every controller, model, and middleware in Laravel is a class.

See you in the next one.


Next Episode: Object Oriented PHP — Classes, Objects, and the Foundation of Laravel

This is Episode 12 of the PHP and Laravel — Zero to Hero series.


PHP & Laravel — Zero to Hero Episode 11: PHP and MySQL — Connecting to a Database and Working With Real Data

What Are We Doing in This Post?

Every application you have ever used stores data somewhere. Your Instagram posts, your bank transactions, your college results, your Amazon orders — all of it lives in a database.

Until now our PHP programs stored data in variables. Variables die the moment the page reloads. A database stores data permanently — it survives page reloads, server restarts, and years of use.

This episode is where PHP becomes a real backend language. We connect PHP to MySQL, create a database, insert records, and fetch and display them on a webpage.


What is MySQL?

MySQL is a relational database management system. It stores data in tables — just like an Excel spreadsheet, but far more powerful, faster, and built for thousands of simultaneous users.

Real world analogy: Think of MySQL as a massive filing cabinet. Each drawer is a database. Each folder inside the drawer is a table. Each sheet of paper inside the folder is a row of data. Each column on the sheet is a field — like Name, Age, Email.

MySQL comes pre-installed with XAMPP. You already have it on your machine. We just need to start it and create our first database.


Step 1 — Start MySQL in XAMPP

Open the XAMPP Control Panel. Click Start next to MySQL. The row turns green and you see port 3306 appear. MySQL is now running.

Now click the Admin button next to MySQL. This opens phpMyAdmin in your browser — a visual interface for managing your databases. You will use this to create databases and tables during development.


Step 2 — Create a Database

In phpMyAdmin, look at the left sidebar. Click New at the top.

In the "Create database" field, type: phplearning

Leave the collation as utf8mb4_general_ci and click Create.

You now have a database called phplearning. This is where all the tables for our practice projects will live.


Step 3 — Create a Table

After creating the database, phpMyAdmin asks you to create a table. Let us create a users table.

Type users in the Name field. Set number of columns to 4. Click Go.

Now fill in the four columns exactly like this:

Column 1: Name = id, Type = INT, set it as PRIMARY KEY, check AUTO_INCREMENT

Column 2: Name = name, Type = VARCHAR, Length = 100

Column 3: Name = email, Type = VARCHAR, Length = 150

Column 4: Name = created_at, Type = TIMESTAMP, Default = CURRENT_TIMESTAMP

Click Save. Your users table is created.

The id column is the primary key — a unique identifier for every row. AUTO_INCREMENT means MySQL automatically assigns the next available number whenever a new row is inserted. You never have to set it manually.


Step 4 — Connect PHP to MySQL Using MySQLi

PHP gives you two ways to connect to MySQL: MySQLi and PDO. In this episode we use MySQLi — it is straightforward and beginner-friendly. We will cover PDO later when we go deeper into PHP.

Create a new file called db.php in your phplearning folder. This file will hold our database connection — we will include it in every file that needs database access.

<?php

$host     = "localhost";
$username = "root";
$password = "";
$database = "phplearning";

$conn = mysqli_connect($host, $username, $password, $database);

if (!$conn) {
    die("Connection failed: " . mysqli_connect_error());
}

?>

mysqli_connect() takes four arguments: the host, MySQL username, MySQL password, and database name.

In XAMPP, the default MySQL username is root and the password is empty — no password set. This is fine for local development. On a real live server you always set a strong password.

mysqli_connect() returns a connection object on success or false on failure.

die() stops the script immediately and displays a message. If the connection fails, there is no point continuing — so we stop and show the error.

We save this connection in $conn. Every database operation we do will use this $conn variable.


Step 5 — Insert Data Into the Database

Create a new file called insert.php:

<?php

require_once "db.php";

$name  = "Gagan Sharma";
$email = "gagan@example.com";

$sql = "INSERT INTO users (name, email) VALUES ('$name', '$email')";

if (mysqli_query($conn, $sql)) {
    echo "User added successfully.";
} else {
    echo "Error: " . mysqli_error($conn);
}

mysqli_close($conn);

?>

require_once "db.php" includes our connection file. require_once means: include this file, but only once even if this line runs multiple times. If the file is not found, stop the script entirely.

mysqli_query() executes an SQL query against the database. It takes two arguments: the connection object and the SQL string.

mysqli_error() returns the last error message from MySQL — useful for debugging when a query fails.

mysqli_close() closes the database connection when we are done. Good practice, though PHP closes it automatically at the end of the script anyway.

Visit http://localhost:8080/phplearning/insert.php

You should see: User added successfully.

Go back to phpMyAdmin, click on your phplearning database, click on the users table, and click Browse. You will see the row you just inserted — with an auto-assigned id of 1 and a created_at timestamp.


Step 6 — Fetch and Display Data

Now let us read that data back and display it on a webpage.

Create fetch.php:

<?php

require_once "db.php";

$sql    = "SELECT * FROM users";
$result = mysqli_query($conn, $sql);

?>
<!DOCTYPE html>
<html>
<head>
    <title>Users List</title>
</head>
<body>

<h2>All Users</h2>

<?php if (mysqli_num_rows($result) > 0): ?>

<table border="1" cellpadding="10" cellspacing="0">
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
        <th>Joined</th>
    </tr>

    <?php while ($row = mysqli_fetch_assoc($result)): ?>
    <tr>
        <td><?php echo $row["id"]; ?></td>
        <td><?php echo htmlspecialchars($row["name"]); ?></td>
        <td><?php echo htmlspecialchars($row["email"]); ?></td>
        <td><?php echo $row["created_at"]; ?></td>
    </tr>
    <?php endwhile; ?>

</table>

<?php else: ?>
    <p>No users found in the database.</p>
<?php endif; ?>

</body>
</html>

<?php mysqli_close($conn); ?>

mysqli_num_rows() returns the number of rows in the result. We check if it is greater than 0 before trying to display any data.

mysqli_fetch_assoc() fetches one row from the result as an associative array, then moves the internal pointer to the next row. We call it in a while loop — it keeps fetching rows one by one until there are no more rows, at which point it returns false and the loop ends.

Each $row looks like this:

<?php

// $row = [
//     "id"         => 1,
//     "name"       => "Gagan Sharma",
//     "email"      => "gagan@example.com",
//     "created_at" => "2025-01-15 10:30:00"
// ]

?>

Notice we are using the alternative syntax for if and while inside HTML — if(): endif; and while(): endwhile; instead of curly braces. This is the standard PHP practice when mixing PHP and HTML. It makes the code far more readable.


Step 7 — A Complete CRUD Example

CRUD stands for Create, Read, Update, Delete. These four operations are the foundation of every database-driven application. Let us build a complete users management page.

Create users.php:

<?php

require_once "db.php";

$message = "";

if ($_SERVER["REQUEST_METHOD"] == "POST") {

    if (isset($_POST["action"])) {

        if ($_POST["action"] == "add") {
            $name  = trim($_POST["name"] ?? "");
            $email = trim($_POST["email"] ?? "");

            if (!empty($name) && !empty($email)) {
                $name  = mysqli_real_escape_string($conn, $name);
                $email = mysqli_real_escape_string($conn, $email);
                $sql   = "INSERT INTO users (name, email) VALUES ('$name', '$email')";
                if (mysqli_query($conn, $sql)) {
                    $message = "User added successfully.";
                }
            }
        }

        if ($_POST["action"] == "delete") {
            $id  = (int) $_POST["id"];
            $sql = "DELETE FROM users WHERE id = $id";
            if (mysqli_query($conn, $sql)) {
                $message = "User deleted.";
            }
        }
    }
}

$result = mysqli_query($conn, "SELECT * FROM users ORDER BY id DESC");

?>
<!DOCTYPE html>
<html>
<head>
    <title>User Management</title>
</head>
<body style="font-family:sans-serif; max-width:700px; margin:40px auto; padding:0 16px;">

<h2>User Management</h2>

<?php if ($message): ?>
    <p style="color:green; font-weight:bold;"><?php echo $message; ?></p>
<?php endif; ?>

<h3>Add New User</h3>
<form method="POST" action="users.php">
    <input type="hidden" name="action" value="add" />
    <input type="text"  name="name"  placeholder="Full Name"      style="padding:8px; margin-right:8px;" required />
    <input type="email" name="email" placeholder="Email Address"  style="padding:8px; margin-right:8px;" required />
    <button type="submit" style="padding:8px 16px; background:#6366f1; color:#fff; border:none; border-radius:4px; cursor:pointer;">Add User</button>
</form>

<h3 style="margin-top:32px;">All Users</h3>

<?php if (mysqli_num_rows($result) > 0): ?>
<table border="1" cellpadding="10" cellspacing="0" style="width:100%; border-collapse:collapse;">
    <tr style="background:#f1f5f9;">
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
        <th>Joined</th>
        <th>Action</th>
    </tr>
    <?php while ($row = mysqli_fetch_assoc($result)): ?>
    <tr>
        <td><?php echo $row["id"]; ?></td>
        <td><?php echo htmlspecialchars($row["name"]); ?></td>
        <td><?php echo htmlspecialchars($row["email"]); ?></td>
        <td><?php echo $row["created_at"]; ?></td>
        <td>
            <form method="POST" action="users.php" style="display:inline;">
                <input type="hidden" name="action" value="delete" />
                <input type="hidden" name="id"     value="<?php echo $row['id']; ?>" />
                <button type="submit" style="background:#ef4444; color:#fff; border:none; padding:4px 10px; border-radius:4px; cursor:pointer;" onclick="return confirm('Delete this user?')">Delete</button>
            </form>
        </td>
    </tr>
    <?php endwhile; ?>
</table>
<?php else: ?>
    <p>No users yet. Add one above.</p>
<?php endif; ?>

</body>
</html>

<?php mysqli_close($conn); ?>

Visit http://localhost:8080/phplearning/users.php

Add a few users using the form. Watch them appear in the table immediately. Delete one. This is a fully functional database-driven application.

Two important things to highlight from this example.

mysqli_real_escape_string():

<?php

$name = mysqli_real_escape_string($conn, $name);

?>

This escapes special characters in user input before inserting it into a SQL query. It prevents a serious security vulnerability called SQL Injection — where a malicious user types SQL code into a form field to manipulate or destroy your database. For example, if someone types ' OR '1'='1 as their name, it could break your query and expose all records. mysqli_real_escape_string() neutralizes this.

We will cover a better solution called Prepared Statements in the next episode. Prepared statements are the modern standard — but understanding why escaping exists first helps you appreciate prepared statements properly.

(int) casting for IDs:

<?php

$id = (int) $_POST["id"];

?>

When deleting by ID, we cast the value to an integer. This guarantees that even if someone tampers with the hidden input field and sends something like DROP TABLE users, it gets converted to 0 — a harmless integer. Always cast numeric inputs to int before using them in queries.


What Did We Learn in This Post?

MySQL is a relational database that stores data in tables with rows and columns. It comes with XAMPP and is managed through phpMyAdmin.

mysqli_connect() establishes a connection from PHP to MySQL. Store the connection in a variable and include db.php in every file that needs database access.

mysqli_query() executes SQL statements. mysqli_fetch_assoc() fetches one row at a time as an associative array inside a while loop.

mysqli_num_rows() tells you how many rows a query returned.

mysqli_real_escape_string() escapes user input before inserting it into SQL queries to prevent SQL Injection. Casting numeric inputs to (int) adds another layer of safety.

CRUD — Create, Read, Update, Delete — are the four fundamental database operations that every application is built around.


What is Coming in Episode 12?

In Episode 12 we level up our database skills with Prepared Statements and PDO.

Prepared statements are the modern, secure, and professional way to run database queries in PHP. They completely eliminate SQL injection vulnerabilities, work with both MySQL and other databases, and are what Laravel's Eloquent ORM uses under the hood. This is the last major Core PHP topic before we transition into Object Oriented PHP and then Laravel.

See you in the next one.


Next Episode: Prepared Statements and PDO — The Professional Way to Handle Databases

This is Episode 11 of the PHP and Laravel — Zero to Hero series.


PHP & Laravel — Zero to Hero Episode 10: Sessions and Cookies — How PHP Remembers Users

What Are We Doing in This Post?

In Episode 09 we learned how to receive user input through forms. But there is a problem we have not solved yet.

HTTP is stateless. That means every time your browser sends a request to the server, PHP has absolutely no memory of the previous request. You log in on one page — and on the very next page, PHP has already forgotten who you are.

This is one of the most fundamental challenges of web development. And sessions and cookies are how we solve it.


The Problem — PHP Has No Memory

Real world analogy: Imagine calling a customer support line where every time you are transferred to a new agent, they have zero record of your previous conversation. You have to introduce yourself and explain your entire problem from scratch every single time. That is exactly what HTTP is like by default — completely stateless.

Sessions and cookies are the solution. They are two different mechanisms that let PHP remember information about a user across multiple requests and multiple pages.


What is a Cookie?

A cookie is a small piece of data that the server sends to the browser, and the browser stores it on the user's computer. On every future request to the same website, the browser automatically sends that cookie back to the server.

Real world analogy: Think of a cookie like a stamp you get when you enter a theme park. The staff stamps your hand when you first enter. Every time you go in and out of different areas, the staff checks your stamp instead of asking you to buy a new ticket each time.

Cookies are stored on the user's machine. The user can see them, edit them, or delete them. Never store sensitive data like passwords in cookies.

Setting a cookie in PHP:


    <?php

    setcookie("username", "Gagan", time() + (86400 * 7));

    echo "Cookie has been set.";

setcookie() takes three main arguments: the cookie name, the cookie value, and the expiry time.

time() returns the current Unix timestamp — the number of seconds since January 1, 1970. Adding 86400 * 7 adds seven days worth of seconds. So this cookie expires in 7 days.

Important: setcookie() must be called before any HTML output — before any echo, before any HTML tags. PHP needs to send cookie data in the HTTP headers, and headers must go before any body content.

Reading a cookie:


    <?php

    if (isset($_COOKIE["username"])) {
        echo "Welcome back, " . $_COOKIE["username"];
    } else {
        echo "No cookie found. This is your first visit.";
    }

$_COOKIE is a superglobal just like $_POST and $_GET. PHP automatically populates it with all cookies the browser sent with the current request.

Note: a cookie you just set with setcookie() will not appear in $_COOKIE until the next request. The browser receives it, stores it, and sends it back on the next page load.

Deleting a cookie:


    <?php

    setcookie("username", "", time() - 3600);

    echo "Cookie deleted.";

To delete a cookie, set it again with an expiry time in the past. The browser sees it has expired and removes it.


A Practical Cookie Example — Theme Preference

Let us build something real. A page that remembers the user's preferred theme.

Create theme.php in your phplearning folder:


    <?php

    if (isset($_POST["theme"])) {
        setcookie("theme", $_POST["theme"], time() + (86400 * 30));
        $theme = $_POST["theme"];
    } elseif (isset($_COOKIE["theme"])) {
        $theme = $_COOKIE["theme"];
    } else {
        $theme = "light";
    }

    $bg    = ($theme == "dark") ? "#1a1a2e" : "#ffffff";
    $color = ($theme == "dark") ? "#e2e8f0" : "#1a1a2e";

    ?>
    <!DOCTYPE html>
    <html>

    <head>
        <title>Theme Preference</title>
    </head>

    <body style="background:<?php echo $bg; ?>; color:<?php echo $color; ?>; padding:40px; font-family:sans-serif;">

        <h2>Theme Preference Demo</h2>
        <p>Current theme: <strong><?php echo $theme; ?></strong></p>
        <p>Close this tab, reopen it, and your theme choice will still be here.</p>

        <form method="POST" action="theme.php">
            <button name="theme" value="light" type="submit">Light Theme</button>
            <button name="theme" value="dark" type="submit">Dark Theme</button>
        </form>

    </body>

    </html>

Visit http://localhost:8080/phplearning/theme.php

Switch between themes. Close the tab. Reopen it. Your choice persists — because it is stored in a cookie on your computer.


What is a Session?

A session is similar to a cookie but with one critical difference: the data is stored on the server, not in the browser.

When a session starts, PHP generates a unique random session ID and sends it to the browser as a cookie. On every future request, the browser sends that session ID back to the server. PHP uses that ID to look up the session data stored on the server side.

Real world analogy: Think of a session like a coat check at a restaurant. When you arrive, the staff takes your coat and gives you a numbered ticket. You carry only the small ticket. Every time you need your coat, you show the ticket and they retrieve it from the back. Your actual belongings stay safely with the restaurant — you only carry an ID that points to them.

The browser only ever holds a session ID — a random string like a3f8b2c9d1e0. The actual session data sits on the server. This makes sessions far more secure than cookies for storing sensitive information.

Starting a session:


    <?php

    session_start();

session_start() must be the very first thing in your PHP file — before any HTML output, before any echo. It either creates a new session or resumes an existing one based on the session ID cookie the browser sends.

Storing data in a session:


    <?php

    session_start();

    $_SESSION["username"] = "Gagan";
    $_SESSION["role"]     = "admin";
    $_SESSION["user_id"]  = 42;

    echo "Session data saved.";

$_SESSION is a superglobal array. Whatever you store in it is available on any page of your website as long as the session is active.

Reading session data on another page:


    <?php

    session_start();

    if (isset($_SESSION["username"])) {
        echo "Welcome, " . $_SESSION["username"];
        echo "<br>Your role is: " . $_SESSION["role"];
    } else {
        echo "No active session. Please log in.";
    }

Every page that needs session data must call session_start() at the top. PHP then loads the session data for that user automatically.

Destroying a session — logout:


    <?php

    session_start();

    $_SESSION = [];
    session_destroy();

    echo "You have been logged out.";

$_SESSION = [] clears all session variables. session_destroy() removes the session from the server entirely. This is exactly what happens when a user clicks "Log Out" on any website.

A Complete Real World Example — Simple Login System

Let us build a proper multi-page login system using sessions. This will be three files.

File 1 — login.php:


    <?php

    session_start();

    if (isset($_SESSION["username"])) {
        header("Location: dashboard.php");
        exit();
    }

    $error = "";

    if ($_SERVER["REQUEST_METHOD"] == "POST") {

        $username = trim($_POST["username"] ?? "");
        $password = trim($_POST["password"] ?? "");

        $valid_username = "gagan";
        $valid_password = "password123";

        if ($username === $valid_username && $password === $valid_password) {
            $_SESSION["username"] = $username;
            $_SESSION["logged_in"] = true;
            header("Location: dashboard.php");
            exit();
        } else {
            $error = "Invalid username or password.";
        }
    }

    ?>
    <!DOCTYPE html>
    <html>

    <head>
        <title>Login</title>
    </head>

    <body style="font-family:sans-serif; max-width:400px; margin:60px auto; padding:0 16px;">

        <h2>Login</h2>

        <?php if ($error): ?>
            <p style="color:red;"><?php echo $error; ?></p>
        <?php endif; ?>

        <form method="POST" action="login.php">
            <label>Username</label><br>
            <input type="text" name="username" style="width:100%; padding:8px; margin:6px 0 14px;" /><br>
            <label>Password</label><br>
            <input type="password" name="password" style="width:100%; padding:8px; margin:6px 0 14px;" /><br>
            <button
                type="submit"
                style="background:#6366f1; color:#fff; padding:10px 24px; border:none; border-radius:6px; cursor:pointer;">
                Login
            </button>
        </form>

    </body>

    </html>

File 2 — dashboard.php:


    <?php

    session_start();

    if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
        header("Location: login.php");
        exit();
    }

    ?>
    <!DOCTYPE html>
    <html>

    <head>
        <title>Dashboard</title>
    </head>

    <body style="font-family:sans-serif; max-width:600px; margin:60px auto; padding:0 16px;">

        <h2>Welcome to your Dashboard</h2>
        <p>Hello, <strong><?php echo htmlspecialchars($_SESSION["username"]); ?></strong>! You are logged in.</p>
        <p>This page is protected. Only logged-in users can see it.</p>
        <br>
        <a href="logout.php">Log Out</a>

    </body>

    </html>

File 3 — logout.php:


    <?php

    session_start();

    $_SESSION = [];
    session_destroy();

    header("Location: login.php");
    exit();

Visit http://localhost:8080/phplearning/login.php

Try going directly to dashboard.php without logging in — you get redirected to login.php automatically.

Log in with username: gagan and password: password123. You land on the dashboard. Close the tab and reopen dashboard.php — you are still logged in because the session persists.

Click Log Out — session is destroyed and you are sent back to login.

This is the exact flow that every login system in the world follows. Laravel's authentication system does the same thing — just with more features and security layers built on top.

Two things to note from this example.

header("Location: ..."):

This is how PHP redirects a user to a different page. header() sends an HTTP header to the browser. The Location header tells the browser to navigate to a new URL. exit() after header() is critical — without it, PHP continues executing the rest of the code even after redirecting.

The session guard pattern:


    <?php

    if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
        header("Location: login.php");
        exit();
    }

This block at the top of any protected page is called a session guard. It checks if the user is logged in and immediately redirects them if they are not. You will put this at the top of every page that requires authentication. In Laravel, middleware does this automatically — but understanding the raw PHP version makes Laravel's middleware make complete sense.


Sessions vs Cookies — When to Use Which

Use sessions when storing sensitive data — user ID, role, login status. The data stays on the server. The browser only holds a random ID. Expires when the browser is closed by default.

Use cookies when storing non-sensitive preferences — theme choice, language preference, recently viewed items. The data lives in the browser. Can persist for days, weeks, or months. User can see and delete them.

Never store passwords, payment details, or any sensitive data in cookies. Always use sessions for anything security-related.


What Did We Learn in This Post?

HTTP is stateless — PHP has no memory between requests by default. Sessions and cookies solve this.

Cookies store data in the browser. setcookie() creates them. $_COOKIE reads them. They can persist for days or weeks. Never store sensitive data in cookies.

Sessions store data on the server. session_start() begins or resumes a session. $_SESSION stores and reads session data. session_destroy() ends a session.

header("Location: url") redirects the user to another page. Always call exit() immediately after.

A session guard at the top of protected pages checks login status and redirects unauthenticated users — this is the foundation of every authentication system.


What is Coming in Episode 11?

Now that we understand how to receive user input and remember user state, it is time to start working with databases.

Episode 11 covers MySQL and PHP together — connecting PHP to a MySQL database, creating tables, inserting data, fetching records, and displaying them on a webpage. This is the episode where everything starts feeling like a real application.

See you in the next one.


Next Episode: PHP and MySQL — Connecting to a Database and Working With Real Data

This is Episode 10 of the PHP and Laravel — Zero to Hero series.


PHP & Laravel — Zero to Hero Episode 14: Interfaces, Abstract Classes, and Namespaces — The Last Core PHP Concepts Before Laravel

What Are We Doing in This Post? This is the final Core PHP episode. After this, we start Laravel. In Episode 13 we learned classes, objec...