Glossary
Object-oriented programming
OOP is neither merely the use of classes nor a contest to create the most abstractions. It helps divide code into clear responsibilities and define how objects collaborate when that fits the application’s problem.
Short definition
State and behaviour stay together within clear responsibilities.
An object can represent an order, an inventory reservation service, or a monetary value. It has its own state and methods that control how it can change. A class is the definition; an object is a specific instance created from that definition.
OOP in PHP uses classes, properties, methods, constructors, visibility, interfaces, inheritance, and composition. However, it does not mean every value needs a separate class or that inheritance is the default tool for sharing code.
The problem it solves
It keeps collaborating parts of a system understandable
Object-oriented design is useful when rules, dependencies, and lifecycles cannot be kept clear in a single set of functions.
- modelling order, payment, or inventory rules
- separating an application service from a specific HTTP or database adapter
- replacing an implementation with a test double through an interface
- encapsulating valid state and the permitted changes to an object
- passing dependencies clearly through a constructor and dependency injection
Practical example
An application service depends on an inventory reservation contract
A checkout service does not need to know the implementation details of inventory management. It works with an interface; the production implementation may call an inventory API, while a test uses a simple substitute. This demonstrates polymorphism and dependency injection, not a need to create an interface for every class.
The order itself can protect its local invariants, such as prohibiting a negative quantity. Coordination across several systems, transactions, and authorization often belongs in an application service rather than a single entity.
interface InventoryGateway {
public function reserve(string $sku, int $quantity): void;
}
final class CheckoutService {
public function __construct(private InventoryGateway $inventory) {}
public function reserveItem(string $sku, int $quantity): void {
$this->inventory->reserve($sku, $quantity);
}
}
How it works
Objects collaborate through clear contracts
Text diagram: application service → interface → concrete implementation → result.
- Naming the responsibility First determine what the object or service should actually do and what belongs to a different responsibility.
- State and behaviour An object maintains its valid state and provides methods with a specific meaning.
- Contract An interface describes the required capability without coupling to one implementation when such interchangeability is genuinely useful.
- Composition More complex behaviour emerges from composing collaborating objects and services, often through a constructor.
- Testing and change A test verifies observable behaviour; the implementation can be replaced when needed without changing the consumer of the contract.
Important concepts
A class, object, and contract have different meanings.
The greatest value of OOP lies not in its terminology but in clear responsibilities and adaptable code.
Class and object
A class is a definition; an object is a specific instance with state. They are not synonyms for a database table and row.
Encapsulation
An object limits how its state can change. Public properties that can be modified freely without rules often merely disguise a data structure.
Interfaces and polymorphism
An interface expresses a contract. Different implementations can provide the same capability in different ways as long as they preserve its meaning.
Composition and inheritance
Composition combines objects; inheritance creates a stronger parent–child relationship. It is often safer to begin with composition.
Immutability and responsibility
Some value objects do not change after creation. An object should have a narrow, understandable responsibility, not become a place for all logic.
Benefits and limitations
An abstraction is valuable only when it simplifies change.
Benefits
- a clearer division of state, behaviour, and dependencies
- the ability to replace an implementation behind a contract
- testing object collaboration without real infrastructure
- encapsulation of local invariants and type contracts
Common mistakes
- creating a class, interface, and factory for every minor value
- using inheritance where composition would suffice
- letting one object hold dozens of unrelated responsibilities
- treating the mere presence of classes as proof of sound object-oriented design
When it makes sense
For rules and collaborations that need clear boundaries.
OOP is practical for long-lived PHP applications with domain rules, integration boundaries, and testable services. This does not mean everything must be abstract; simple transformations, queries, and configuration can be more direct.
The appropriate degree of separation depends on complexity. For a small feature, one typed class may be clearer than several layers; in a complex process, clear contracts instead reduce the cost of future changes.
What to consider
Before adding an abstraction, find the real change it should protect.
Code is better when both an object’s responsibility and the reason for its dependency are easy to explain.
- prefer composition to inheritance when a parent–child relationship is not natural
- introduce an interface where a meaningful contract or interchangeable implementation exists
- do not thoughtlessly use an ORM entity as the entire domain model
- keep dependencies explicit and typed in the constructor
- test observable outcomes, not the internal calls of every small method
Common questions
OOP without academic shortcuts
What is the difference between a class and an object?
A class is a definition from which objects can be created. An object is a specific instance with state and behaviour.
Do I have to use inheritance for OOP?
No. Inheritance is only one technique, and composing collaborating objects is often more appropriate.
When should I use an interface?
When it expresses a real contract needed by multiple consumers or interchangeable implementations—not automatically for every class.
Is every Doctrine entity a domain object?
Not necessarily. An ORM entity can be a useful data mapping, but a domain model requires its own responsibilities and rules.
How I design applications in practice
I choose architecture based on the complexity of the specific system.
In PHP projects, I combine direct code with clear service and dependency boundaries where they support long-term maintenance.