Procedural vs OOP
A programming paradigm is a broad way of organizing code and reasoning about a program. Procedural programming and object-oriented programming are two useful paradigms; neither one is automatically better in every situation.
Procedural programming
Section titled “Procedural programming”Procedural code is organized primarily around operations and the flow of data between them. A clear one-time data migration might be expressed as a small pipeline:
<?php
declare(strict_types=1);
$rows = readCsv('orders.csv');$cleanRows = cleanRows($rows);saveRows($cleanRows);This can be a good design. The functions have clear inputs and outputs, and introducing multiple classes would not necessarily make the task safer or easier to understand.
Object-oriented programming
Section titled “Object-oriented programming”Object-oriented code is organized around objects that have clear responsibilities and collaborate to perform a use case. An object may own state, expose behavior, and protect rules that belong to that state.
A backend checkout flow may eventually involve an order, a payment component, persistence, and confirmation delivery. Object-oriented design asks:
- Which component owns each responsibility?
- Which object should make each decision?
- What data or state must remain valid?
- How should the objects communicate without one component doing everything?
The important change is the way responsibilities are modeled—not merely the
presence of the class keyword.
A class does not automatically make code object-oriented
Section titled “A class does not automatically make code object-oriented”This class is still essentially a collection of unrelated procedures:
final class OrderHelper{ public static function calculateTotal(array $order): int { return array_sum($order['prices']); }
public static function pay(array $order): void { // Payment details... }
public static function sendEmail(array $order): void { // Email details... }}The class mixes calculation, payment, and communication. Moving functions into a static helper changed their location, but it did not create meaningful objects or clarify ownership.
When objects help
Section titled “When objects help”Objects become useful when the domain has meaningful state, rules that must be protected, independent responsibilities, replaceable behavior, or several components that need to collaborate. They can also provide useful boundaries for testing and change.
Typical examples include orders with lifecycle rules, wallets that must protect their balance, and payment providers with different implementations.
When procedural code is enough
Section titled “When procedural code is enough”Procedural functions are often the simpler choice for direct transformations, small scripts, and stable calculations with explicit inputs and outputs:
function calculateTax(float $subtotal, float $rate): float{ return $subtotal * $rate;}The number of steps alone does not decide the paradigm. Ten focused functions may be clearer than ten classes if the problem has no meaningful object state or collaboration.
Overengineering
Section titled “Overengineering”Overengineering happens when a solution introduces more abstraction than the problem currently needs. A simple addition does not become better merely by adding an interface, a service, and a factory around it.
Every abstraction has a cost: more names, files, relationships, and decisions for the reader to understand. Introduce one when it protects a real rule, separates a real responsibility, or supports a variation the system genuinely needs.
Common mistakes
Section titled “Common mistakes”- Treating procedural programming as inherently bad.
- Assuming that more steps always require more objects.
- Turning every function into a class or service.
- Using static helper classes as a substitute for modeling responsibilities.
- Creating interfaces and factories before a real need for variation exists.
- Assuming every object must contain mutable state; a focused stateless service can still represent a valid responsibility.
Mental model
Section titled “Mental model”Procedural design: operations drive the flow of data.Object-oriented design: responsible objects collaborate to perform behavior.Good design: choose the simplest model that keeps the current problem clear.Summary
Section titled “Summary”Procedural and object-oriented programming are tools, not rankings. Procedural code can be ideal for a straightforward pipeline or calculation. OOP becomes valuable when objects provide meaningful ownership, protect state or rules, and make collaboration easier to understand. Adding classes without those benefits only changes syntax and may create overengineering.
Knowledge check
Section titled “Knowledge check”- Why can a pipeline of several functions still be a good procedural design?
- Why is a class full of unrelated static methods not necessarily good OOP?
- Which signals suggest that objects could help a growing backend feature?
- Give one example where introducing an interface and factory would be unnecessary.