1 — Procedural vs Object-Oriented Thinking
Before writing a single class, you need to understand why OOP exists.
Procedural code is a list of instructions executed top to bottom. It works fine for small scripts,
but as a codebase grows it develops a familiar set of problems: functions that take 10 parameters,
global state that anything can modify, and logic scattered across dozens of files with no clear owner.
OOP solves this by grouping data (what a thing is) and behaviour (what a thing does) together into a single unit — the object. Instead of a sprawl of functions operating on loose arrays, you have self-contained entities that manage their own state.
Side-by-side comparison
Imagine tracking a user's name and email and sending them a welcome message.
// Procedural: data is a loose array, functions are global $user = ['name' => 'Alice', 'email' => 'alice@example.com']; function sendWelcome(array $user): void { echo "Welcome, {$user['name']}! Check {$user['email']}"; } sendWelcome($user); // Nothing stops someone from doing $user['email'] = null anywhere
// OOP: data and behaviour live together, state is protected class User { public function __construct( private string $name, private string $email ) {} public function sendWelcome(): void { echo "Welcome, {$this->name}! Check {$this->email}"; } } $user = new User('Alice', 'alice@example.com'); $user->sendWelcome(); // $user->email is now inaccessible from outside — it's encapsulated
The OOP version might look like more code today, but it scales. When you need to add
a profile picture, a role, or a verification status, you add it to the User class —
not hunt through a codebase for every function that touches a $user array.
The four pillars — a quick map
You'll hear these constantly. Know what they mean before you go further:
- Encapsulation — bundle data + behaviour; hide internal details. (Sessions 1–3)
- Inheritance — build new classes on top of existing ones. (Session 4)
- Abstraction — expose only what's necessary; hide implementation. (Session 5)
- Polymorphism — different objects, same interface, different behaviour. (Sessions 4–5)
2 — Defining Your First Class
A class is a blueprint. A object is a thing built from that blueprint. Think of the class as the cookie cutter; objects are the cookies. One cutter, unlimited cookies — each with its own state (sprinkles, icing) but the same shape.
/** * Class declaration: PascalCase by convention * File name must match: Car.php */ class Car { // Properties — the data a Car holds public string $make; public string $model; public int $year; private int $speed = 0; // internal state, hidden from outside // Method — the behaviour a Car has public function accelerate(int $by): void { $this->speed += $by; } public function getSpeed(): int { return $this->speed; } }
Key syntax rules
- Class names use PascalCase:
BankAccount,UserRepository,EmailSender - Each class lives in its own file, named exactly after the class:
Car.php - Methods use camelCase:
getSpeed(),sendEmail() - Properties are declared with a type hint and access modifier (PHP 7.4+)
- The closing brace of a class has no semicolon — that's for interfaces
Adding type declarations (PHP 8+ style)
Modern PHP lets you declare property types directly. This catches bugs at runtime and documents intent far better than a comment ever could.
class Product { public string $name; // must be string, no null public ?string $description; // nullable: string OR null public float $price; public int $stock = 0; // default value public bool $active = true; }
If you try to assign a float to $stock,
PHP will throw a TypeError immediately — no silent bugs.
Always add type hints. They are free documentation and a safety net.
3 — Instantiating Objects with new
You turn a class (blueprint) into an object (real thing) with the new keyword.
Each call to new creates a completely independent object with its own copy of the properties.
$car1 = new Car(); // Object #1 — its own speed = 0 $car2 = new Car(); // Object #2 — completely separate $car1->make = 'Toyota'; $car1->model = 'Supra'; $car1->accelerate(60); $car2->make = 'Honda'; $car2->model = 'NSX'; $car2->accelerate(30); echo $car1->getSpeed(); // 60 — $car2 is unaffected echo $car2->getSpeed(); // 30 — $car1 is unaffected
The arrow operator ->
-> is how you access properties and call methods on an object.
Think of it as "go to this object and…":
$car1->make— read/write the$makeproperty$car1->accelerate(60)— call theaccelerate()method
Objects are passed by reference-handle
This is one of the most important things to know early. When you assign an object to a new variable, you're not copying the object — you're copying a handle that points to the same object in memory.
$a = new Car(); $a->make = 'BMW'; $b = $a; // $b points to the SAME object as $a $b->make = 'Audi'; echo $a->make; // "Audi" — $a was changed too! // To get a true copy, use clone: $c = clone $a; $c->make = 'Ferrari'; echo $a->make; // Still "Audi" — clone is a true copy
This trips up every PHP developer at least once. Internalize it now:
assignment copies a handle, not the object.
Use clone when you need an independent copy.
4 — The Mental Model: Blueprints → Buildings
The blueprint/building metaphor only gets you so far. Here's a more complete mental model that will serve you across the entire course:
Think in nouns, then add verbs
When designing a system, identify the things (nouns) it works with. Each important noun is likely a class. Then figure out what each thing does — those are methods.
// Nouns → classes: Product, Cart, Order, Customer, Invoice // Verbs → methods on those classes class Cart { private array $items = []; public function addItem(Product $product, int $qty = 1): void { $this->items[] = ['product' => $product, 'qty' => $qty]; } public function total(): float { return array_sum( array_map( fn($i) => $i['product']->price * $i['qty'], $this->items ) ); } public function isEmpty(): bool { return empty($this->items); } } // Usage reads like plain English: $cart = new Cart(); $cart->addItem($laptop, 1); $cart->addItem($mouse, 2); echo $cart->total(); // price of 1 laptop + 2 mice
Single Responsibility — one class, one job
The most important design rule you'll use every day: a class should have
one primary reason to change. A Cart class
calculates totals — it doesn't send emails or write to a database.
That keeps each class small, testable, and understandable.
- ❌
UserManagerEmailSenderAndLogger— does too many things - ✅
User+Mailer+Logger— each does one thing well
Checking understanding — use instanceof
$cart = new Cart(); if ($cart instanceof Cart) { echo 'Yes, it is a Cart'; } // Also works with get_class() and ::class constant: echo get_class($cart); // "Cart" echo Cart::class; // "Cart" (or full namespace\Cart)
15-Minute Review — Session 01
Stop the session timer and switch to the review timer in the sidebar. Answer these questions from memory — then check.
Q1 — What is the main advantage of OOP over procedural code in large projects?
Q2 — You write $b = $a; where $a is an object. You then set $b->name = 'Bob'. What happens to $a->name?
Q3 — Which of these class names follows PHP convention?
Q4 — What keyword creates a true independent copy of an object in PHP?