Anti-patterns are common solutions to recurring problems that are ineffective and counterproductive. This example covers the most common object-oriented anti-patterns found in WordPress development and provides better alternatives.
What it is: A design pattern that restricts instantiation of a class to a single instance and provides global access to it.
Why it's bad:
- Makes testing way harder - Can't mock or replace the instance in tests
- Creates hidden dependencies - Not clear what code depends on the singleton
- Violates Single Responsibility Principle - Class manages both its business logic AND its instantiation
- Global mutable state - All the problems of global variables
- Makes parallel execution impossible - Single instance means single point of contention
- Tight coupling - Everything that uses it is coupled to the concrete implementation
- No interface/polymorphism - Can't swap implementations
- Lifetime issues - Lives for entire application lifetime, can't be destroyed/reset
Common excuses (and why they're wrong):
- "But I only need one instance!" → That's a business rule, enforce it in your composition root, not in the class itself
- "WordPress uses it for
$wpdb!" → WordPress also has technical debt and backwards compatibility constraints. Don't repeat their mistakes. - "Database connections should be singletons!" → Connection pooling and dependency injection handle this better
- "It's convenient!" → So is
goto, doesn't make it good
The truth: There is bareyl any valid use case for the Singleton pattern in modern PHP. If you think you need one instance, manage that in your dependency injection container or composition root, not in the class itself.
Better alternative:
- Use dependency injection to pass instances explicitly
- Let your DI container manage instance lifecycle (can be configured to always return the same instance if needed)
- Make the class accept dependencies through constructor, making them explicit and testable
What it is: Creating utility classes filled with static methods.
Why it's bad:
- Impossible to mock in tests
- Can't use polymorphism or interfaces
- Creates hidden dependencies
- Difficult to extend or override behavior
- No state management possible
When static methods are OK:
- Pure utility functions with no dependencies (e.g., string formatting)
- Factory methods that create new instances
Better alternative: Use instance methods with dependency injection.
What it is: Relying heavily on global variables and WordPress globals.
Why it's bad:
- Hidden dependencies (not clear what a function needs)
- Makes testing nearly impossible
- Order-dependent execution
- Thread-unsafe (if PHP ever becomes truly multi-threaded)
- Hard to track where state changes
Better alternative: Pass dependencies explicitly through constructor or method parameters.
What it is: Two classes that depend on each other, creating a dependency loop.
Why it's bad:
- Makes code hard to understand
- Impossible to instantiate one without the other
- Prevents proper unit testing
- Often indicates poor design
- Can cause infinite loops or stack overflows
Better alternative: Use composition, interfaces, or repository patterns to break the cycle.
What it is: A global registry that provides services on demand.
Why it's bad:
- Hides dependencies (like global state)
- Makes testing difficult
- Runtime errors instead of compile-time errors
- Unclear what a class actually needs
- The "service locator" itself becomes a God object
When it's used:
- WordPress uses this pattern internally (e.g., getting services from the main
WPobject) - But it's generally considered an anti-pattern in modern development
Better alternative: Use a proper Dependency Injection Container that wires dependencies at construction time.
What it is: Deep inheritance hierarchies that require jumping up and down the class tree to understand behavior.
Why it's bad:
- Hard to understand what a class actually does
- Methods can be overridden at any level
- Changes to base classes affect all descendants
- Fragile base class problem
- Violates "composition over inheritance" principle
Better alternative: Use composition and interfaces. Favor shallow hierarchies (maximum 2-3 levels).
What it is: Using primitive types (strings, integers, arrays) instead of small objects for domain concepts.
Why it's bad:
- No validation of values
- Logic scattered across the codebase
- Easy to mix up parameters (all strings look the same)
- No encapsulation of related data
- Type hints don't help much (
string $emailvsstring $name)
Better alternative: Create Value Objects (like Email, Address, Money) that encapsulate validation and behavior.
What it is: Objects that only have properties (data) with all business logic in separate "service" classes.
Why it's bad:
- Violates Object-Oriented Programming principles
- Separates data from behavior
- Service classes become procedural code in OOP clothing
- Hard to maintain (logic and data are in different places)
- Duplicated logic across services
Better alternative: Create rich domain models where objects contain both data and the business logic that operates on that data.
What it is: A method that seems more interested in other objects' data than its own.
Why it's bad:
- Breaks encapsulation
- Logic is in the wrong place
- Tight coupling to other objects' internals
- Difficult to change object structure
- Often indicates misplaced responsibility
Better alternative: Move the behavior to the class that owns the data ("Tell, Don't Ask" principle).
What it is: Functions with too many parameters (typically more than 3-4).
Why it's bad:
- Hard to remember parameter order
- Easy to mix up parameters of the same type
- Difficult to add new parameters
- Usually indicates the function is doing too much
- Poor IDE support and autocomplete
Better alternative: Use Parameter Objects that group related parameters together. Consider using builder pattern or fluent interfaces for complex configurations.
What it is: Creating a separate database connection using mysqli or PDO instead of using WordPress's $wpdb object.
Why it's bad:
- Wastes resources - Opens a second connection to the same database
- No WordPress integration - Bypasses WordPress caching, filters, and hooks
- Security issues - WordPress handles charset, collation, and SQL injection protection
- No table prefix support - Hardcodes table names, breaks multisite and custom prefixes
- Transaction conflicts - Can't participate in WordPress transactions
- Query logging missed - Debugging tools and query monitors won't see these queries
- Connection pooling wasted - MySQL has connection limits, and you're using two per request
- Performance overhead - Connection handshake happens twice
- Maintenance nightmare - Two different ways to query the same database
Common excuses (and why they're wrong):
- "I need transactions!" →
$wpdbsupports transactions since WordPress 4.0 - "I need prepared statements!" →
$wpdb->prepare()exists for exactly this - "PDO is better!" →
$wpdbuses mysqli under the hood and is battle-tested - "I need custom queries!" →
$wpdb->query()handles any SQL you need - "Performance!" → Actually slower due to double connection overhead
Real-world impact:
- Database server hits max connections faster
- Increased memory usage
- Slower page loads
- WordPress caching ineffective
- Debugging becomes a nightmare
Better alternative: Always use $wpdb - it provides everything you need:
// GOOD: Use WordPress database object
global $wpdb;
// Transactions
$wpdb->query( 'START TRANSACTION' );
$wpdb->insert( $wpdb->prefix . 'custom_table', $data );
$wpdb->query( 'COMMIT' );
// Prepared statements
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}custom_table WHERE status = %s",
$status
)
);
// All CRUD operations
$wpdb->insert( $table, $data, $format );
$wpdb->update( $table, $data, $where, $format, $where_format );
$wpdb->delete( $table, $where, $where_format );There is a premium WordPress plugin that uses a custom Database connection and also provides not a single filter or hook. So it is impossible to modify its behavior in a reasonable way.
A client of mine just wanted to change the default color of the "post type" (which was not a post type), the only way I found, was to create an empty entry to the database with the desired defaults, which could then be modified via the plugins admin interface.
What it is: Either using hooks for everything (internal communication) or avoiding hooks entirely (not providing extensibility).
Why it's bad:
- Hard to trace code flow - Logic jumps around via
do_action()andapply_filters() - Debugging nightmare - Can't see what code will actually run
- Performance overhead - Hook system has overhead for internal method calls
- Hidden dependencies - Class methods depend on hooks being registered
- Order-dependent - Hook priority determines execution order, fragile
- No type safety - No way to enforce what data hooks receive/return
- Testing complexity - Must mock hook system or register test hooks
- Maintenance confusion - "Where is this actually called?" becomes a search problem
When hooks are misused internally:
// BAD: Using hooks for internal class communication
class OrderProcessor {
public function process( $order ) {
// Instead of calling directly, uses hooks
do_action( 'before_process_order', $order );
$this->validate( $order );
do_action( 'after_validate_order', $order );
$this->charge( $order );
do_action( 'after_charge_order', $order );
}
public function __construct() {
// Hooks to own methods
add_action( 'before_process_order', [ $this, 'log_processing' ] );
add_action( 'after_validate_order', [ $this, 'send_email' ] );
}
}Better alternative for internal logic: Direct method calls
// GOOD: Direct method calls for internal logic
class OrderProcessor {
public function process( $order ) {
$this->log_processing( $order );
$this->validate( $order );
$this->send_email( $order );
$this->charge( $order );
}
}Why it's bad:
- No extensibility - Other plugins/themes can't modify behavior
- Breaks WordPress conventions - Goes against WordPress architecture
- Forces code duplication - Users must copy/modify instead of extend
- No customization - Can't adapt to specific use cases
- Community backlash - WordPress developers expect hooks
When to ADD hooks:
- Before/after significant operations
- When returning data that might need modification
- At critical points in workflows
- When saving/deleting data
- When rendering output
Better alternative: Provide hooks at strategic points
// GOOD: Hooks for extensibility, direct calls for internal logic
class OrderProcessor {
public function process( $order ) {
// Allow others to intervene BEFORE processing
$order = apply_filters( 'myPlugin_pre_process_order', $order );
// Internal logic - direct calls
$this->validate( $order );
$this->charge( $order );
// Allow others to act AFTER processing
do_action( 'myPlugin_order_processed', $order );
return $order;
}
}Use hooks for:
- Public API / Extension points
- Inter-plugin communication
- Theme integration
- User customization
- Major lifecycle events
DON'T use hooks for:
- Internal class method communication
- Private implementation details
- Tight loops (performance)
- Core business logic flow
- Every single method call
Golden rule: If it's for other developers to extend, use hooks. If it's your internal implementation, use direct method calls.
- Single Responsibility: One class, one reason to change
- Open/Closed: Open for extension, closed for modification
- Liskov Substitution: Subtypes must be substitutable for base types
- Interface Segregation: Many specific interfaces > one general interface
- Dependency Inversion: Depend on abstractions, not concretions
- Tell, Don't Ask: Tell objects what to do, don't ask for their data
- Composition over Inheritance: Prefer composing objects over extending classes
- Law of Demeter: Only talk to immediate friends
- DRY: Don't Repeat Yourself
- YAGNI: You Aren't Gonna Need It
- KISS: Keep It Simple, Stupid
WordPress core itself uses some of these anti-patterns (especially global state and singletons). This is largely due to:
- Backwards compatibility requirements
- PHP 5.2 legacy (now 7.2+)
- The plugin architecture
- Historical reasons
However, in your own code, you should strive to avoid these patterns. You can write clean, modern object-oriented code even within WordPress's constraints.
Ask yourself these questions:
- Can I easily write unit tests for this? (No → probably has anti-patterns)
- Do I know what this class depends on by looking at its constructor? (No → hidden dependencies)
- Can I understand this class without reading its parent classes? (No → inheritance issues)
- Would changing this break many other parts of the code? (Yes → tight coupling)
- Does this class have a single, clear responsibility? (No → God object or doing too much)
- "Refactoring: Improving the Design of Existing Code" by Martin Fowler
- "Clean Code" by Robert C. Martin
- "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four)
- "Anti-Patterns: Refactoring Software, Architectures, and Projects in Crisis" by William J. Brown
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Singleton | Global state, impossible to test, hidden dependencies | DI Container manages lifecycle |
| Static Method Abuse | Can't mock, no polymorphism | Instance methods |
| Global State | Hidden dependencies | Explicit dependencies |
| Circular Dependencies | Tangled, hard to test | Composition, interfaces |
| Service Locator | Hidden dependencies | DI Container |
| Deep Inheritance | Hard to understand | Composition |
| Primitive Obsession | No validation, scattered logic | Value Objects |
| Anemic Domain Model | Data separated from behavior | Rich Domain Model |
| Feature Envy | Logic in wrong place | Tell, Don't Ask |
| Long Parameter List | Hard to use and maintain | Parameter Objects |
| Custom DB Connection | Wastes resources, bypasses WordPress | Always use $wpdb |
| Hook Over-use | Hard to trace, debug, performance | Direct calls internally |
| Hook Under-use | No extensibility | Add hooks at extension points |