I took over a client’s site last month. Total mess. The previous developer had promised them a “modern, object-oriented plugin” to handle their custom reporting. What I found was one giant 5,000-line file with a class named DataManager, and inside it were dozens of static methods that were really just procedural WordPress code with a class wrapper. It was a textbook case of bad WordPress OOP, and it made debugging a nightmare.
This is the trap most WordPress developers fall into. We are so used to the procedural flow of hooks and filters that our brains think in steps, not objects. So when we try to write object-oriented code, we just build containers for our functions. It feels like the right thing to do, but it doesn’t actually make the code any better.
Why most WordPress OOP isn’t OOP at all
The problem is that wrapping functions in a class isn’t object-oriented programming, it’s just namespacing. The original code in that plugin I inherited had stuff like this everywhere:
class DataManager {
public static function get_recent_posts_for_report() {
// ... a massive WP_Query call ...
return $posts;
}
public static function get_popular_authors() {
// ... another complex database query ...
return $authors;
}
}See the problem? The class itself doesn’t represent anything. It holds no data and has no state, it’s just a bucket of functions. You can’t pass a DataManager around as a “thing,” you’re only calling static methods. This is procedural code in disguise.
My first instinct was to break that monster class into smaller ones: a PostManager, an AuthorManager, and so on. That cleaned it up a bit, but it was really just shuffling deck chairs. I was still only organizing functions, so the core problem stayed put. The code wasn’t any more testable or reusable, it was just spread across more files. That was my blind spot, thinking smaller files meant better architecture. They don’t.
From organizing functions to defining objects
The shift happens when you stop thinking about what your code does and start thinking about what it is. Instead of a DataManager, what if we had an actual Report object, a thing that represents the report itself?
This Report object would have properties, like a start date and an end date, and it would be responsible for its own data. You’d create a new instance of it for each report you need. It’s a genuinely different way of thinking.
class SalesReport {
private $start_date;
private $end_date;
private $report_data;
public function __construct( $start, $end ) {
$this->start_date = $start;
$this->end_date = $end;
$this->fetch_report_data();
}
private function fetch_report_data() {
// Private method to run the query based on dates
// and populate $this->report_data.
$this->report_data = // ... results from a query ...
}
public function get_results() {
return $this->report_data;
}
public function get_title() {
return 'Sales Report: ' . $this->start_date . ' to ' . $this->end_date;
}
}
// Now you can use it like a real object.
$report = new SalesReport('2023-01-01', '2023-01-31');
echo '<h2>' . esc_html( $report->get_title() ) . '</h2>';Now that’s an object. It has state (the date range and the data) and behavior (methods to get the title and results). It’s a self-contained, reusable component, and that is the goal of WordPress OOP.
So, what’s the point?
The mental leap from procedural to object-oriented programming isn’t about learning the class syntax. It’s about changing how you model problems. Think of the difference between following a flat recipe and building with LEGO: one is a fixed sequence of steps, the other is a set of components you can snap together in different ways. A well-designed object is one of those bricks.
This builds on an idea I saw over at carlalexander.ca, where he points out that WordPress developers often have to design the LEGO bricks themselves, not just use them. That’s the hard part, and also where you grow into a stronger developer.
- Stop putting procedural functions inside a class.
- Start by identifying the “things” or “nouns” in your system, such as Report, User, or Order.
- Give those things properties (data) and methods (behavior).
- Instantiate them and pass them around your application.
This stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.