PHP Inheritance: The Protected Property That Broke the CMS
After a refactor, previews showed empty titles and undefined author errors from protected property rename.
- PHP inheritance uses
extendsto create parent-child class relationships. - Child classes inherit all public and protected properties and methods automatically.
- Method overriding lets children redefine behavior; use
parent::to call the parent version. - Access control:
privatestays hidden,protectedallows child access. - Abstract classes enforce method contracts without instantiation.
- Production pitfall: forgetting
parent::__construct()leaves parent properties uninitialized.
Imagine a generic 'Vehicle' blueprint that defines things every vehicle has — wheels, an engine, the ability to move. Now you want to build a 'Car' and a 'Motorcycle'. Instead of writing the wheels-and-engine stuff twice, you say 'start with the Vehicle blueprint and add Car-specific stuff on top'. That's inheritance. The Car inherits everything from Vehicle automatically and only has to describe what makes it uniquely a Car.
Every non-trivial PHP application has classes that share behaviour. A BlogPost and a NewsArticle both have a title, a publish date, and an author. An AdminUser and a RegularUser both log in, have a name, and belong to an account. Without inheritance, you'd copy-paste that shared logic into every class — and the moment requirements change, you'd have to update it in five places instead of one. That's not a PHP problem, that's a maintenance nightmare waiting to happen.
Inheritance solves the DRY (Don't Repeat Yourself) problem at the class level. It lets you define common behaviour once in a parent (or base) class and have multiple child (or derived) classes automatically gain that behaviour. Child classes can then specialise — adding new properties and methods, or overriding existing ones to behave differently. The hierarchy reads like plain English: a Car IS-A Vehicle. An AdminUser IS-A User.
By the end of this article you'll understand not just the syntax of PHP inheritance but when to reach for it versus other tools like interfaces or traits. You'll be able to build a realistic multi-level class hierarchy, safely override parent methods, call parent behaviour with parent::, and sidestep the classic errors that trip up intermediate developers in code reviews and interviews.
The `extends` Keyword — Building Your First Parent-Child Relationship
The word extends is the entire engine of PHP inheritance. When ClassB extends ClassA, ClassB automatically gets every public and protected property and method that ClassA defines. Private members stay private to the parent — the child can't see them directly.
Let's model something real: a content publishing platform. Every piece of content — whether it's an article, a video, or a podcast — shares a common core: a title, an author, a publication date, and the ability to render a summary. We put all of that in a Content parent class. Then Article extends it and adds article-specific things like a word count.
Notice how the child class constructor calls parent::__construct(). That's critical. The parent's constructor sets up the shared properties. If the child doesn't call it, those properties never get initialised and you'll get null where you expect a string. Always call the parent constructor when the parent has one — unless you have a very deliberate reason not to.
parent::__construct(...), those parent properties will never be set. You'll get empty strings, nulls, or typed property errors at runtime. Always explicitly call parent::__construct() with the required arguments as the first line of the child constructor.__construct() unless you intentionally skip parent initialisation.extends copies public and protected members, not private ones.parent::__construct() in a child constructor.Method Overriding — Teaching a Child to Do Things Differently
Inheritance gives you a starting point. Method overriding lets child classes customise that starting point. When a child class defines a method with the same name as a parent method, the child's version wins for objects of that type. This is the mechanism behind polymorphism — one interface, different behaviours.
Back to our publishing platform: every Content type has a getSummaryLine(). But a Podcast should also show the episode duration. We override the method on Podcast to add that extra information. The trick is that we can still call the parent's version of the method using parent::getSummaryLine() and build on top of it — instead of rewriting the whole thing.
This is the real power move. You're not throwing away the parent's logic; you're extending it. Think of it as: 'do everything the parent does, and then do this extra thing'. The alternative — copy-pasting the parent's summary format into the child — means two places to update when the format changes. Use parent::methodName() to reuse rather than repeat.
final public function methodName(). PHP will throw a fatal error if any child class tries to override it. This is a great way to enforce contracts in shared libraries or team codebases.parent::methodName() reuses logic.Access Modifiers and Multi-Level Inheritance — What Children Can Actually See
PHP has three access modifiers that interact with inheritance in very specific ways. public members are visible everywhere — inside the class, in child classes, and from outside code. protected members are visible inside the class AND in any child class, but not from external code. private members are visible only inside the class that defines them — not even in child classes.
This distinction matters a lot in practice. When you mark a parent property private, child classes can't read or write it directly. They have to go through a getter or setter. Mark it protected and the child class can access it like its own property. The rule of thumb: start with private. Promote to protected only when a child class genuinely needs direct access. Never go straight to public for properties.
Multi-level inheritance works naturally in PHP — Child extends Parent, Grandchild extends Child. But keep the chain shallow. A three-level hierarchy is usually fine. Going deeper than that is often a sign you need composition (using other objects) instead of more inheritance.
protected is designed for inheritance — it explicitly says 'child classes are expected to use this'. private says 'this is an internal implementation detail, not even children should depend on it'. Choosing between them is a design decision, not just a syntax choice.Abstract Classes — Enforcing a Contract Without Finishing the Blueprint
Sometimes a parent class is so general that it doesn't make sense to instantiate it directly. You'd never create a raw Content object on your platform — you'd always create an Article, a Podcast, or a Video. Abstract classes formalise this pattern.
An abstract class does two things. First, it can't be instantiated with new — PHP throws a fatal error if you try. Second, it can define abstract methods — method signatures with no body — that every concrete child class must implement. It's a contract: 'if you extend me, you promise to provide these methods'.
This is more powerful than a normal parent class because it gives you compile-time enforcement instead of runtime surprises. You don't find out a child class is missing a method when a user hits a bug in production — PHP tells you immediately when the class is loaded. Use abstract classes when you have shared logic that all children should inherit and a set of behaviours that each child must implement in its own way.
Inheritance vs Composition — When Extending a Class Is the Wrong Choice
The 'favor composition over inheritance' principle isn't just a design-pattern mantra — it's a practical survival tactic. Inheritance creates tight coupling: a change to the parent can silently break every child. Composition (delegation) keeps classes independent.
Consider a ReadOnlyList. You might be tempted to extend ArrayList and throw exceptions on . That violates Liskov substitution — you'd break code that expects a normal add()ArrayList. The right approach: compose an internal ArrayList and expose only the methods you want.
This principle also applies to deep hierarchies. If you find yourself extending a class three levels deep just to reuse one method, ask: is a true 'is-a' relationship here? If not, extract that method into a service class and pass it in via constructor injection. Composition wins where inheritance adds fragility.
- IS-A: Car IS-A Vehicle — true subtype polymorphism.
- HAS-A: Car HAS-A Engine — reuse with delegation.
- If you can't honestly say 'Child IS-A Parent', use composition.
- Composition is looser coupling; inheritance leaks implementation.
protected property change at the top cascades to every child.The Protected Property That Broke the CMS — A PHP Inheritance Failure
$title to $contentTitle in the parent class, but forgot that child classes directly accessed $this->title. The change was not caught because no child class overrides were updated.final on core getters to prevent overrides. Audit all child class references after any parent property change.- Protected properties are part of the public API of inheritance — treat them as contract, not implementation detail.
- Always use getter/setter methods for any property that child classes might access.
- Add CI checks that detect direct property access in child classes (static analysis).
parent::__construct(...). If child defines own constructor and doesn't call parent's, parent properties remain uninitialized.final keyword in parent method declaration. Either remove final from parent (if safe) or redesign to not require override.private, child cannot access. Change to protected if child needs direct access, or add getter method.parent::__construct(...) as first line in child constructor.Key takeaways
extends copies all public and protected members to the childprivate members stay locked inside the parent class only.parent::__construct() in a child constructor when the parent has oneparent::methodName() is the sweet spotCommon mistakes to avoid
3 patternsNot calling parent::__construct() in the child constructor
parent::__construct(...) the first line of your child constructor whenever the parent defines one.Changing a method's return type or parameter signature when overriding
public function getPrice(): float and the child declares public function getPrice(): string, PHP rejects it.Making everything `protected` instead of `private` 'just in case'
private. Promote to protected only when a child class has a genuine, deliberate need for direct access. Use getters and setters as the interface between parent and child where possible.Interview Questions on This Topic
What's the difference between `private` and `protected` in PHP inheritance, and how would you decide which one to use for a parent class property?
private restricts visibility to the class that defines it — even child classes cannot access private properties or methods directly. protected allows access within the class and all its descendants. The decision rule: start with private. Only promote to protected if a child class has a deliberate need to access the member directly. If the property is merely needed for internal state that children shouldn't depend on, keep it private and provide a protected getter if necessary. Never use protected as a default 'just in case' — it creates tight coupling.Frequently Asked Questions
That's OOP in PHP. Mark it forged?
5 min read · try the examples if you haven't