A junior dev on my team asked for a code review last week. He was building a custom registration form for a client site, and he was stuck. Not on the functionality—it worked. He was stuck on the *philosophy*. He’d written a mix of procedural functions and a couple of classes, and he felt like he was just making a mess. “I’m trying to do proper WordPress OOP,” he said, “but it feels like I’m just making it more complex for no reason.”
And he’s not wrong. It’s a classic trap. You see all this advice about object-oriented programming, design patterns, and SOLID principles. But then you look at WordPress itself, and a lot of the time, the simplest way to get something done is a quick `add_action` call in your `functions.php`. Total opposite.
The WordPress OOP Lego Problem
Here’s the deal. My first instinct, maybe ten years ago, would’ve been to build a dozen classes for that registration form. A `Form` class, a `Field` class, a `Validator` class, a `SubmitHandler` class… you get the picture. I was doing OOP for the sake of it. An architecture astronaut, as they say. It was a total nightmare to maintain.
The root of the problem isn’t about whether OOP is “better.” It’s that WordPress doesn’t give you many good building blocks to start with. A great article over at carlalexander.ca framed this as a Lego problem. OOP works best when you have a box of standard bricks (classes) you can snap together. WordPress, for all its strengths, gives you a pile of custom, oddly-shaped Duplo blocks. You can’t easily connect a `WP_Post` object to a `WP_User` object in a clean, object-oriented way. They don’t snap together.
So you end up having to create your own bricks. You build abstractions. For a simple form, this can feel like over-engineering, and sometimes it is. But for a feature that needs to grow, it’s essential. My mistake wasn’t using classes; it was building the *wrong* classes. I was building a space shuttle when the client just needed a car.
<?php
/**
* A pragmatic handler for a custom form.
* Not over-engineered, just organized.
*/
class Custom_Registration_Handler {
public function __construct() {
// Point the form to admin-post.php with our action.
add_action( 'admin_post_nopriv_custom_register', [ $this, 'handle_submission' ] );
add_action( 'admin_post_custom_register', [ $this, 'handle_submission' ] );
}
/**
* Handles the form data.
*/
public function handle_submission() {
// 1. Nonce check is crucial.
if ( ! isset( $_POST['custom_reg_nonce'] ) || ! wp_verify_nonce( $_POST['custom_reg_nonce'], 'custom_register_action' ) ) {
wp_die( 'Security check failed!' );
}
// 2. Sanitize and validate your inputs.
$email = sanitize_email( $_POST['user_email'] );
if ( ! is_email( $email ) ) {
// Redirect back with an error.
$this->redirect_with_error( 'invalid_email' );
return;
}
// 3. Do the work (e.g., create the user).
$user_id = wp_create_user( $email, wp_generate_password(), $email );
if ( is_wp_error( $user_id ) ) {
$this->redirect_with_error( 'user_exists' );
return;
}
// 4. Redirect on success.
wp_redirect( home_url( '/registration-success' ) );
exit;
}
/**
* Helper to handle redirects.
*/
private function redirect_with_error( $error_code ) {
wp_redirect( home_url( '/register?error=' . $error_code ) );
exit;
}
}
// Just instantiate it.
new Custom_Registration_Handler();
So, What’s the Point?
Look at that code block. It’s not a spaceship. It’s one class. It does one job: it handles the form submission. All the logic is in one place. If the client comes back and says, “Can we also add the user to our CRM via an API?” I know exactly where to put that code. I add a new private method and call it from `handle_submission`. Clean.
This is the pragmatic approach to WordPress OOP. You don’t need a complex framework for every little thing. You just need to group related logic together. Ask yourself one question: “Is this feature likely to change or grow?”
- If the answer is no—like a simple filter to add a class to the body tag—a procedural function is fine. Period.
- If the answer is yes—like a registration form, a data export routine, or anything involving an external API—start with a simple class. Your future self will thank you.
Look, 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.
The goal isn’t to be “object-oriented.” The goal is to build something that doesn’t fall over when a client asks for a simple change. Sometimes that means using a class. Sometimes it doesn’t. The trick is knowing the difference.
Leave a Reply