Skip to content

Latest commit

 

History

History
417 lines (316 loc) · 15.6 KB

File metadata and controls

417 lines (316 loc) · 15.6 KB

Anti-Patterns in WordPress Development

Overview

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.

Bad Practices Demonstrated

1. Singleton Pattern

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

2. Static Method Abuse

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.


3. Global State Coupling

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.


4. Circular Dependencies

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.


5. Service Locator Pattern

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 WP object)
  • 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.


6. Yo-Yo Problem (Deep Inheritance)

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).


7. Primitive Obsession

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 $email vs string $name)

Better alternative: Create Value Objects (like Email, Address, Money) that encapsulate validation and behavior.


8. Anemic Domain Model

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.


9. Feature Envy

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).


10. Long Parameter List

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.


11. Custom Database Connection (Bypassing $wpdb)

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!"$wpdb supports transactions since WordPress 4.0
  • "I need prepared statements!"$wpdb->prepare() exists for exactly this
  • "PDO is better!"$wpdb uses 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 );

Annecdote

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.


12. Hook Abuse (Over-use and Under-use)

What it is: Either using hooks for everything (internal communication) or avoiding hooks entirely (not providing extensibility).

Over-use of Hooks (Internal Communication)

Why it's bad:

  • Hard to trace code flow - Logic jumps around via do_action() and apply_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 );
    }
}

Under-use of Hooks (No Extensibility)

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;
    }
}

The Balance

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.


Key Principles to Avoid Anti-Patterns

SOLID Principles

  • 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

Other Important Principles

  • 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 Context

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.

Detection

Ask yourself these questions:

  1. Can I easily write unit tests for this? (No → probably has anti-patterns)
  2. Do I know what this class depends on by looking at its constructor? (No → hidden dependencies)
  3. Can I understand this class without reading its parent classes? (No → inheritance issues)
  4. Would changing this break many other parts of the code? (Yes → tight coupling)
  5. Does this class have a single, clear responsibility? (No → God object or doing too much)

Further Reading

  • "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

Summary

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