Oop Patterns
By the end of this lesson you'll wield PHP's full object toolkit — interfaces, abstract classes, traits, inheritance, polymorphism, final and late static binding — and see how they snap together into the Strategy and Factory patterns that real applications are built from.
Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Interfaces — a Contract to Sign
An interface is a promise. It lists method names and their signatures but no actual code — it describes what a class must be able to do, never how . A class then writes implements SomeInterface to "sign the contract", and PHP forces it to define every listed method or the file won't run. The power is that a function can type-hint the interface and then accept any class that signs it — your code depends on the capability, not on one specific class.
Notice describe(Shape $s) never mentions Circle or Rectangle . It only knows it received "something that is a Shape", so you can add a Triangle tomorrow and describe() keeps working untouched.
2️⃣ Abstract Classes — Half-Built Blueprints
An abstract class sits between an interface and a normal class. Like an interface it can declare abstract methods — empty methods that every child must fill in. But unlike an interface it can also hold real, shared code, properties, and a constructor. You can never write new Animal() directly; an abstract class exists only to be extends -ed. Use it when several closely related classes share behaviour but each needs to fill in one or two specifics.
Each child wrote speak() just once, yet both got the entire introduce() method and the constructor for free. That's the trade an interface can't make: shared code lives in one place.
3️⃣ Traits — Horizontal Reuse
PHP allows single inheritance only: a class can extends exactly one parent. So how do unrelated classes share the same code — say logging, used by both an Article and an Invoice ? A trait is a bundle of methods you use inside any class, as if they were pasted in. A class can pull in many traits. If two traits define a method with the same name , PHP stops with an error until you resolve it: insteadof picks the winner and as keeps the loser under a new name.
4️⃣ Inheritance, Polymorphism, final & Late Static Binding
When a class extends another, it inherits everything and may override a method by redefining it with a matching signature . Inside an override, parent::method() calls the version you just replaced so you can extend rather than discard it. Polymorphism ("many forms") is the pay-off: loop over a mixed list of objects, call the same method on each, and every object runs its own version. final locks a class or method so it can't be extended or overridden. And late static binding — the static keyword — makes new static() in a parent build the child that was actually called.
The single foreach loop is polymorphism in one line: three different classes, one method call, three different results. And CardPayment::of(99.99) proves late static binding — the of() code lives in the parent yet returned a CardPayment .
5️⃣ A Brief Look at Patterns: Strategy & Factory
Design patterns are just named, repeatable ways to combine the features above. The Strategy pattern defines an interface for an algorithm, then lets you swap interchangeable implementations at runtime — the client holds "a strategy" without caring which. The Factory pattern hides object creation behind a method, so callers ask for a type by name instead of writing new for a specific class. You've already used the bricks; here they are assembled.
Now you try. The script below is almost complete — fill in each ___ using the 👉 hint, then run it and check it against the Output panel.
One more. This time you'll extend an abstract class and fill in its one hole.
Common Errors (and the fix)
- "Cannot instantiate abstract class Animal" / "Cannot instantiate interface Shape" — you wrote new Animal() or new Shape() . Abstract classes and interfaces are blueprints, never objects. Create a concrete child instead — new Dog() — that extends / implements them.
- "Trait method save has not been applied as Backup::save, because of collision" — two traits define the same method name. PHP won't guess. Add a use { ... } block with TraitA::save insteadof TraitB; to pick a winner, and optionally TraitB::save as saveAlt; to keep the other.
- "Class EmailNotifier contains 1 abstract method and must be declared abstract (Notifier::send)" — you said implements Notifier but forgot to define one of its methods. Implement every method the interface lists, with a matching signature.
- "Declaration of Cat::speak(int $x) must be compatible with Animal::speak()" — your override changed the parameters or return type. An override must keep a compatible signature (same/compatible parameter and return types). Match the parent's declaration exactly unless you're widening it in an allowed way.
- "Cannot override final method PaymentMethod::describe()" — the parent marked that method final , which forbids overriding. Either remove final in the parent (if you control it) or leave the method alone and add new behaviour elsewhere.
Pro Tips
- 💡 Program to an interface. Type-hint the interface ( Shape ), not the concrete class — your code stays open to new implementations.
- 💡 Prefer composition (traits) over deep inheritance. Long chains of extends get brittle; a couple of small traits are easier to reason about.
- 💡 Use new static() , not new self() , in factory-style methods on a base class so subclasses return their own type.
- 💡 Reach for a pattern only when pain appears — duplication, tight coupling, hard-to-test code. Premature abstraction is worse than none.
📋 Quick Reference — interface vs abstract vs trait
Feature
interface
abstract class
trait
Keyword to use it
implements
extends
use
How many at once?
many
one
Can hold real code?
no (signatures only)
yes
Properties / constructor?
constants only
Can be instantiated?
no
no (mixed in)
Use it for
a capability / contract
shared code in a family
reuse across unrelated classes
Frequently Asked Questions
Mini-Challenge: A Shared Trait
No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This is the write-run-check loop you'll use on every real class.
🎉 Lesson Complete!
- ✅ An interface is a contract; a class signs it with implements and must define every method
- ✅ An abstract class mixes shared code with abstract methods children must fill, and can't be instantiated
- ✅ A trait gives horizontal reuse across unrelated classes; resolve clashes with insteadof and as
- ✅ extends inherits and overrides; parent:: reaches the version you replaced
- ✅ Polymorphism lets one loop run each object's own method; final locks classes/methods and new static() builds the called child
- ✅ Strategy and Factory are just these features combined into reusable, named designs
- ✅ Next lesson: Dependency Injection — wire objects together so your code stays decoupled and testable
Practice quiz
Which keyword does a class use to sign an interface contract?
- extends
- use
- implements
- abstract
Answer: implements. A class writes 'implements SomeInterface' and must then define every method the interface lists.
What is the key difference between an interface and an abstract class?
- An interface is signatures only; an abstract class can hold real shared code
- An abstract class has no methods
- An interface can be instantiated
- They are identical
Answer: An interface is signatures only; an abstract class can hold real shared code. An interface is a pure contract; an abstract class can also carry shared code, properties, and a constructor.
How many classes can a single class extend in PHP?
- Two
- Unlimited
- Zero
- One
Answer: One. PHP allows single inheritance only — a class extends exactly one parent. Traits give horizontal reuse.
What happens if you try new Animal() on an abstract class Animal?
- It creates an empty Animal
- An error — abstract classes cannot be instantiated
- It returns null
- It calls the first child class
Answer: An error — abstract classes cannot be instantiated. Abstract classes are blueprints; you must instantiate a concrete child that extends them.
Inside an override, how do you call the version you just replaced?
- parent::method()
- self::method()
- this::method()
- super.method()
Answer: parent::method(). parent::method() reaches the overridden parent version so you can extend rather than discard it.
What is late static binding — what does new static() build?
- Always an instance of the parent class
- A static array
- An instance of the class that was actually called at runtime
- A new interface
Answer: An instance of the class that was actually called at runtime. 'static' resolves to the called class, so new static() in a parent factory returns the child's type.
Why use 'static' instead of 'self' in a base-class factory method?
- self is faster
- self is locked to where it is written; static follows the called class
- static only works on properties
- There is no difference
Answer: self is locked to where it is written; static follows the called class. new self() always builds the parent; new static() builds whichever child invoked the method.
What does the final keyword do?
- Marks a class as abstract
- Makes a method static
- Allows multiple inheritance
- Prevents a class from being extended or a method from being overridden
Answer: Prevents a class from being extended or a method from being overridden. final locks behaviour: a final class can't be extended and a final method can't be overridden.
If two traits define a save() method, how do you pick which wins?
- FileStore::save overrides CloudStore;
- FileStore::save insteadof CloudStore;
- use FileStore not CloudStore;
- PHP picks automatically
Answer: FileStore::save insteadof CloudStore;. insteadof picks the winner; 'CloudStore::save as saveToCloud;' keeps the loser under a new name.
What does the Strategy pattern let you do?
- Create objects without using new
- Lock a class from extension
- Swap interchangeable algorithm implementations behind one interface at runtime
- Merge two traits
Answer: Swap interchangeable algorithm implementations behind one interface at runtime. Strategy defines an interface for an algorithm and lets the client hold any implementation without caring which.
Continue this course
- Previous: APIs & JSON
- Next: Dependency Injection