Composition lets PHP applications reuse behavior without creating inheritance trees that become hard to understand.
The inheritance trap
Inheritance feels attractive because it promises reuse. A base class holds common behavior and child classes specialize it. The problem is that child classes inherit both the useful behavior and the assumptions that came with it. Over time, small differences between subclasses create overrides, flags, and fragile parent methods. The hierarchy becomes a place where changes have surprising side effects.
Composition changes the direction
Composition means an object gets work done by collaborating with other objects. Instead of extending a base exporter, a report service might receive a formatter, a storage writer, and a notification channel. Each collaborator can vary independently. The service does not need to know every concrete class; it only needs the contracts that describe the behavior it requires.
Laravel examples
Laravel already encourages composition through the service container. Controllers receive services, services receive repositories or clients, and jobs receive dependencies when they run. This makes it natural to replace a payment client in tests, introduce a new mail provider, or add a different search strategy without editing a base class.
When inheritance still works
Inheritance is not forbidden. Framework base classes, abstract test cases, and small template methods can be useful. The warning is about using inheritance to model every variation in business behavior. If the relationship is not truly an is-a relationship, composition is usually safer. A PDF invoice and an HTML invoice are both outputs, but they might be better represented as different renderers behind one interface.
Composition and design patterns
Many design patterns are formal names for composition. Strategy composes interchangeable algorithms. Decorator wraps behavior around another object. Adapter composes a third-party client behind an application interface. Observer composes publishers and subscribers. Once composition feels natural, patterns stop looking like ceremony and start looking like vocabulary.
Refactoring toward composition
A practical refactor is to identify an overridden method that varies between subclasses. Extract that variation into an interface, inject it into the original class, and let each variation become a small strategy. Tests should cover the behavior before and after the refactor. This avoids a dramatic rewrite and gives the codebase a cleaner extension point.
Where to go next
The Strategy pattern article shows this idea in detail. The dependency injection article explains how Laravel wires these collaborators together.
Internal reading path
This article is part of a connected OOP and design patterns series. Continue with these related guides:
- Strategy Pattern in PHP: Replacing Conditionals With Behavior
- Dependency Injection in Laravel: Writing Flexible Services
- SOLID Principles in PHP: A Practical Guide for Real Projects
Practical checklist
- Name the responsibility before choosing a pattern.
- Prefer small contracts where behavior varies or external services are involved.
- Keep controllers at the edge and move workflows into named application code.
- Add tests around the behavior that is most likely to change.
- Use patterns to reduce coupling, not to make simple code look advanced.