SOLID Principles for WordPress Development
Open/Closed Principle in WordPress
Software entities should be open for extension, but closed for modification
The Open/Closed Principle (OCP) ensures your WordPress code can grow and adapt without breaking existing functionality. Instead of modifying working code, you extend it – adding new features through hooks, filters, inheritance, or composition.
Open for Extension
Modular Plugins
Plugin Architecture
Open for Extension
Your code should be designed so new functionality can be added easily without touching existing code. In WordPress, this means:
Using do_action() and apply_filters()
Creating plugin APIs with hooks
Using class inheritance and interfaces
Implementing Strategy pattern for variations
Closed for Modification
Once code is tested and working, you shouldn’t need to change it to add features. Benefits include:
No risk of breaking existing functionality
Easier to maintain and test
Child themes can extend parents safely
Plugins can hook into core without editing it
WordPress Examples: Violation vs Compliance
See how OCP applies in real WordPress development scenarios
Violating OCP
Modifying plugin code directly
// In vendor plugin file - BAD!
function send_order_email($order_id) {
$order = get_order($order_id);
$email = $order->customer_email;
send_sms($order->phone, "Order placed!");
wp_mail($email, "Order Confirmed",
get_email_body($order));
}
PROBLEMS:
Modification gets lost on plugin update
Can’t disable SMS without editing
Breaks warranty and support; hard to test
Following OCP
Using hooks to extend behavior
// Original plugin - stays closed
function send_order_email($order_id) {
$order = get_order($order_id);
do_action('before_order_email', $order_id, $order);
wp_mail($email, "Order Confirmed", get_email_body($order));
}
// Your plugin - extends
add_action('before_order_email', 'send_order_sms', 10, 2);
function send_order_sms($order_id, $order) {
send_sms($order->phone, "Order #$order_id placed!");
}
BENEFITS:
Original code never modified
Can enable/disable via plugin
Update-safe extension; easy to test independently
Violating OCP
Hardcoded payment logic with if/else
class PaymentProcessor {
public function process($order, $method) {
if ($method === 'paypal') return $this->process_paypal($order);
elseif ($method === 'stripe') return $this->process_stripe($order);
// Adding crypto? Modify this file!
}
}
PROBLEMS:
Must edit core class for new gateway
Growing if/else; tight coupling
Following OCP
Strategy pattern with gateway registration
interface PaymentGateway { public function process($order); }
class PaymentProcessor {
private $gateways = [];
public function register($name, $gateway) { $this->gateways[$name] = $gateway; }
public function process($order, $method) { return $this->gateways[$method]->process($order); }
}
// Add crypto without touching processor!
class CryptoGateway implements PaymentGateway { public function process($order) { /*...*/ } }
BENEFITS:
Add gateways via registration
Core processor never changes
Test each gateway independently
Violating OCP
Modifying parent theme directly
// In parent theme - BAD!
function theme_setup() {
add_theme_support('post-thumbnails');
add_theme_support('custom-logo');
register_nav_menus(['primary' => 'Primary', 'social' => 'Social']);
}
PROBLEMS:
Changes lost on theme update
Can’t share between sites; violates update workflow
Following OCP
Using child theme to extend parent
// Parent - stays closed
function theme_setup() {
add_theme_support('post-thumbnails');
register_nav_menus(['primary' => 'Primary']);
do_action('after_theme_setup');
}
add_action('after_setup_theme', 'theme_setup');
// Child theme - extends
add_action('after_theme_setup', 'child_theme_setup');
function child_theme_setup() {
add_theme_support('custom-logo');
register_nav_menu('social', 'Social Menu');
}
BENEFITS:
Parent theme stays untouched
Update parent safely; portable customizations
Real-World Example: WooCommerce Extension
How WooCommerce follows OCP to allow thousands of extensions without modifying core
WooCommerce’s Extension Architecture
Open for Extension
- 700+ action hooks in core
- 400+ filter hooks for customization
- Abstract classes for payment gateways
- Abstract classes for shipping methods
- Template override system
Closed for Modification
- Core checkout flow stays stable
- Payment processing logic protected
- Order management untouched
- Backward compatibility maintained
// Example: Adding custom field to checkout
// WooCommerce core provides hook - closed for modification
do_action('woocommerce_after_order_notes', $checkout);
// Your plugin extends - open for extension
add_action('woocommerce_after_order_notes', 'add_gift_message_field');
function add_gift_message_field($checkout) {
echo '<div class="gift-message">';
woocommerce_form_field('gift_message', array(
'type' => 'textarea',
'label' => 'Gift Message',
'placeholder' => 'Optional message...'
), $checkout->get_value('gift_message'));
echo '</div>';
}
// Save the field
add_action('woocommerce_checkout_create_order', 'save_gift_message', 10, 2);
function save_gift_message($order, $data) {
if (!empty($data['gift_message'])) {
$order->update_meta_data('_gift_message',
sanitize_textarea_field($data['gift_message']));
}
}
Result: WooCommerce has 1000+ extensions without changing a single line of core code. Subscriptions, memberships, bookings, custom payment gateways ? all added through hooks and abstract classes.
Common OCP Violations in WordPress
Avoid these frequent mistakes that break the Open/Closed Principle
Editing Core/Vendor Files
Modifying WordPress core, plugins, or themes directly to add features
Why it’s bad:
Updates overwrite your changes, breaking your site. Use hooks, filters, or child themes instead.
No Extension Points
Creating plugins/themes without hooks or filters for others to extend
Why it’s bad:
Creating plugins/themes without hooks or filters for others to extend
Hardcoded Conditionals
Using if/else chains for variations instead of extensible patterns
Why it’s bad:
Every new variation requires modifying the same function. Use strategy pattern or hooks instead.
Static Functions Everywhere
Using static methods that can’t be extended or overridden
Why it’s bad:
Static methods can’t be overridden in child classes. Use instance methods or provide hooks.
Frequently Asked Questions
A: Add hooks at logical extension points – before/after major operations, when data changes, or when rendering output. Follow WordPress core’s approach: do_action('before_save_'), apply_filters('the_content'), etc. Don’t overdo it – too many hooks make code hard to follow.
A: Initially yes, but it pays off as the project grows. Start simple – a few hooks are enough for small plugins. Add interfaces and abstract classes when you have 3+ similar implementations. The complexity is justified when it prevents breaking existing functionality while adding features.
A: Absolutely! WordPress hooks (add_action, add_filter) are a perfect OCP implementation using procedural code. Your functions stay closed (working code), others extend via hooks (adding functionality). OOP adds more tools, but hooks alone achieve OCP beautifully.
A: No, OCP is about adding features, not fixing bugs. Bug fixes are legitimate modifications. The principle means “don’t modify working code to add new features” – fixing broken code is different. After the fix, that code should be closed for new features again.
A: OCP is crucial for plugin compatibility. If your plugin follows OCP (provides hooks), users can extend it safely. When you update, their extensions keep working because they’re not modifying your code. This is why editing vendor plugin files is dangerous – updates erase custom changes.
A: No, only functions that return data users might want to modify. Use filters for content transformation (apply_filters('post_title', $title)), use actions for event notifications (do_action('post_saved', $id)). Internal helper functions don’t need hooks.
Official WordPress Resources
Learn how OCP aligns with WordPress plugin and theme development standards