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.


No comments:

Post a Comment

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...