Welcome friends, gather round, grab yourself a coffee or a can of Irn Bru. I’m going to talk about Interfaces and Abstract Classes.
Woah, woah, woah there, I see you looking to escape already, but, don’t run away just yet, I’ll try NOT to be boring. I’ll throw in some humour maybe?
This stuff ain’t sexy, but it’s pretty much essential, like flossing your teeth, changing the oil in your car, or getting the plumber round to sort out your pipework (actually…..).
Still with me? Great.
Now, here’s the thing: none of this shit ever gets mentioned. You rarely see it in job descriptions. Nobody brings it up in interviews. And yet, somehow, you're just expected to know it.
Modern frameworks — Laravel, Symfony etc — they abstract a lot of this away for you. You bind an interface in a service provider, the container magically injects the right implementation, and everything feels lovely.
However, and call me old-fashioned if you want, but I believe it’s important to understand what’s going on under the hood.
The only time it was ever treated like a critical “thing” was back at University and I couldn’t be arsed. I just wanted to code. If it worked, it worked. Who cares, right?
Turns out: future me cares. These concepts? They’re not academic fluff. They’re the scaffolding that keeps your code from being dirtier than Ron Jeremy’s underpants.
So what’s an Interface?
Before that. Lets look at some nasty code.
class CheckoutService
{
public function processOrder(string $gateway, float $total): bool
{
if ($gateway === 'stripe') {
$payment = new StripeGateway();
return $payment->charge($total);
}
if ($gateway === 'paypal') {
$payment = new PaypalGateway();
return $payment->charge($total);
}
if ($gateway === 'squirrel') {
$payment = new SquirrelGateway();
return $payment->charge($total);
}
throw new Exception("Unsupported payment gateway: {$gateway}");
}
}Every time a new payment method is introduced or you need to remove or edit the existing gateways, you have to edit this class.
Testing becomes a pain. You can’t easily mock out different gateways.
This
CheckoutServiceknows way too much about what gateways exist and how to create them.
There’s a much easier way.
Alright? So an interface is basically a contract. It says,
"I don’t care how you do it, but if you’re implementing this contract, you’re got to provide the methods I’ve specified."
It doesn’t contain logic. It doesn’t care what your class is doing. It just wants you to show up with the right methods.
Here’s a basic example:
interface PaymentGateway
{
public function charge(float $amount): bool;
}This is saying: “Any class that says it’s a PaymentGateway needs to have a charge method that takes a float and returns a boolean.”
Now let’s implement it:
class StripeGateway implements PaymentGateway
{
public function charge(float $amount): bool
{
// Logic to hit Stripe's API
return true;
}
}
class PaypalGateway implements PaymentGateway
{
public function charge(float $amount): bool
{
// Logic to hit PayPal's API
return true;
}
}
class SquirrelWithAContactLessCardTapedToAGolfClub implements PaymentGateway
{
public function charge(float $amount): bool
{
// No idea what happens in here tbh.
return true;
}
}Now these classes can be used interchangeably, because they stick to the contract.
But why does this matter?
Imagine you’ve got a CheckoutService and you don’t want it caring about which payment gateway you’re using. You just want it to charge the customer and get on with life.
class CheckoutService
{
public function __construct(private PaymentGateway $gateway)
{
}
public function processOrder(float $total): bool
{
return $this->gateway->charge($total);
}
}Your CheckoutService doesn’t give a shit whether it’s Stripe, PayPal, or a roided-up squirrel with a contactless card taped to a golf club.
As long as it gets something that follows the PaymentGateway interface, it’ll work.
That’s the power of interfaces. They let you:
Swap implementations easily
Mock stuff in tests
Write code that isn’t tightly glued to specific classes.
And finally…
What’s an Abstract Class?
Unlike an interface, which can only declare method signatures without any implementation, an abstract class allows you to define both method implementations and abstract declarations.
Why use Abstract Classes?
To enforce a contract: Ensuring subclasses implement certain methods.
To avoid code duplication by providing shared functionality.
To allow polymorphism: You can type-hint the abstract class and work with any subclass.
Key points:
You cannot create an instance of an abstract class.
Abstract classes can have normal methods with implementations.
Abstract classes can have abstract methods without implementations; subclasses must implement these.
Abstract classes are meant to be extended.
abstract class Animal
{
public function __construct(protected string $name)
{
}
// Abstract method — subclasses must implement this
abstract public function makeSound();
public function getName(): string
{
return $this->name;
}
}
class Dog extends Animal
{
public function makeSound(): string
{
return "Woof!";
}
}
class Cat extends Animal
{
public function makeSound(): string
{
return "Meow!";
}
}
class RoidedUpSquirrel extends Animal
{
public function makeSound(): string
{
return "Gainz bro";
}
}
// $animal = new Animal("Generic");
// Error: cannot instantiate abstract class
$dog = new Dog("Rex");
echo $dog->getName() . " says " . $dog->makeSound();
// Rex says Woof!
$cat = new Cat("Whiskers");
echo $cat->getName() . " says " . $cat->makeSound();
// Whiskers says Meow!
$squirrel = new RoidedUpSquirrel("Chad");
echo $squirrel->getName() . " says " . $squirrel->makeSound();
// Chad says Gainz bro!TDLR;
An abstract class provides a common base with shared properties and methods but can’t be instantiated, while interfaces only define method signatures without implementations.
A class can extend one abstract class but implement multiple interfaces. Together, they help enforce contracts and promote code reuse, flexibility, and clean architecture.
Give them a go, they’re great. Or don’t, and keep making me look good.


