<?php OOP: Zero to Hero Chapter 08 of 20

Chapter 08 ยท Going Deeper

Abstract Classes & Interfaces

Sometimes a parent class is only an idea, never a real thing. You can meet a Student and a Lecturer โ€” but have you ever met a plain "Person-in-general" walking down the street? This chapter teaches PHP's two tools for describing ideas and contracts: abstract classes and interfaces.

Abstract classes โ€” templates that can't be built

๐Ÿ“
Real-world analogy

"Shape" is an idea. You can draw a circle, a square, a triangle โ€” but you cannot draw "a shape in general". Yet all shapes share things: they all have an area, they can all be described. An abstract class captures exactly this: shared code + a promise of what every child must provide โ€” while forbidding anyone from building the raw idea itself.

<?php
abstract class Shape {
    public function __construct(public string $color) {}

    // Abstract method: NO body. Every child MUST write its own version.
    abstract public function area(): float;

    // Normal method: shared by all children, written once.
    public function describe(): string {
        return "A $this->color shape with area " . $this->area();
    }
}

class Circle extends Shape {
    public function __construct(string $color, private float $radius) {
        parent::__construct($color);
    }
    public function area(): float {
        return round(3.14159 * $this->radius ** 2, 2);
    }
}

class Rectangle extends Shape {
    public function __construct(string $color, private float $w, private float $h) {
        parent::__construct($color);
    }
    public function area(): float {
        return $this->w * $this->h;
    }
}

$shapes = [new Circle("red", 5), new Rectangle("blue", 4, 6)];
foreach ($shapes as $shape) {
    echo $shape->describe() . "<br>";
}

$s = new Shape("green");   // ๐Ÿ’ฅ Error: Cannot instantiate abstract class
A red shape with area 78.54 A blue shape with area 24 Fatal error: Uncaught Error: Cannot instantiate abstract class Shape

Three rules to memorise:

Look at describe() โ€” it calls $this->area() even though Shape itself has no idea how to compute an area! It trusts that whichever child is running will supply one. Shared logic on top, specific logic below: that's the abstract-class superpower.

Interfaces โ€” pure contracts

๐Ÿ”Œ
Real-world analogy

Think of a wall socket. It doesn't care whether you plug in a phone charger, a kettle or a TV. It only demands one thing: your plug must have the right pins. An interface is that socket shape โ€” a list of methods a class promises to have, with zero code inside. Any class, from any family, can promise to "fit the socket".

interface Payable {
    public function pay(float $amount): string;   // no body โ€” only the promise
}

interface Printable {
    public function printOut(): string;
}

// A class "implements" an interface โ€” and may implement several at once
class TuitionInvoice implements Payable, Printable {
    public function __construct(public string $student, public float $total) {}

    public function pay(float $amount): string {
        $this->total -= $amount;
        return "Paid K$amount. Remaining: K$this->total";
    }
    public function printOut(): string {
        return "INVOICE โ€” $this->student โ€” K$this->total";
    }
}

class LibraryFine implements Payable {
    public function __construct(public float $total) {}

    public function pay(float $amount): string {
        $this->total -= $amount;
        return "Fine reduced by K$amount.";
    }
}

// The magic: this function accepts ANYTHING that fits the Payable socket
function processPayment(Payable $bill, float $amount): void {
    echo $bill->pay($amount) . "<br>";
}

processPayment(new TuitionInvoice("Chanda", 5000), 1500);
processPayment(new LibraryFine(80), 50);
Paid K1500. Remaining: K3500 Fine reduced by K50.

The function processPayment() doesn't know or care what kind of bill it received. It only knows: "this thing promised it can pay()". Invoices and fines aren't even related by inheritance โ€” they just fit the same socket. That is polymorphism at its purest.

Abstract class vs interface โ€” which one when?

Abstract classInterface
Can contain real, shared code?โœ“ Yes (normal methods, properties)โœ— No โ€” method signatures and constants only
How many can a class take?Only one (extends one parent)Many (implements A, B, C)
The relationship in words"is a" โ€” Circle is a Shape"can do" โ€” Invoice can be paid
Typical namesNouns: Shape, Person, ModelAbilities: Payable, Printable, Countable
Simple rule of thumb

Sharing actual code between related classes โ†’ abstract class. Guaranteeing that unrelated classes offer the same ability โ†’ interface. Still unsure? Start with an interface โ€” it's the lighter promise.

โœ๏ธ Try it yourself

Create an interface Notifiable with one method send(string $message): string. Implement it in two classes: EmailNotifier (returns "EMAIL: your message") and SmsNotifier (returns "SMS: your message"). Write a function alertStudent(Notifiable $channel) that sends "Results are out!" through whichever channel it receives. Test with both.

Show solution
<?php
interface Notifiable {
    public function send(string $message): string;
}

class EmailNotifier implements Notifiable {
    public function send(string $message): string {
        return "EMAIL: $message";
    }
}

class SmsNotifier implements Notifiable {
    public function send(string $message): string {
        return "SMS: $message";
    }
}

function alertStudent(Notifiable $channel): void {
    echo $channel->send("Results are out!") . "<br>";
}

alertStudent(new EmailNotifier());
alertStudent(new SmsNotifier());
EMAIL: Results are out! SMS: Results are out!
Quick quiz

What happens if you write new Shape() when Shape is abstract?

Quick quiz

How many interfaces can one class implement?

๐Ÿ”‘ Key points
  • An abstract class is an idea: it can hold shared code but can never be built with new.
  • An abstract method has no body โ€” every child is forced to implement it.
  • An interface is a pure contract: method signatures only, no code.
  • A class extends one parent but can implements many interfaces.
  • Type-hinting an interface (function f(Payable $x)) accepts any class that fits the contract โ€” powerful polymorphism.
  • Rule of thumb: shared code โ†’ abstract class; shared ability โ†’ interface.