Learn to implement the Data Access Object (DAO) pattern in WordPress to decouple business logic from SQL, improve maintainability, and ensure database security.
Previously in this course, we explored Advanced Custom Database Tables to handle complex data storage. While those tables provide the storage layer, they often lead to "spaghetti code" if you execute $wpdb queries directly inside your controllers or service providers.
In this lesson, we introduce the Data Access Object (DAO) pattern—often implemented via the Repository pattern—to abstract database interactions. By centralizing SQL logic into dedicated classes, you shield your business logic from schema changes and ensure consistent security practices.
When you scatter global $wpdb; calls throughout your plugin, you create tight coupling. If you ever need to rename a table, change a column name, or switch to a different caching strategy, you are forced to hunt down every query in your codebase.
A DAO acts as a mediator. Your application logic requests data (e.g., "get the latest entry"), and the DAO handles the "how"—the SQL, the placeholders, and the result formatting.
| Feature | Direct $wpdb Usage | DAO/Repository Pattern |
|---|---|---|
| Maintainability | Low (SQL is everywhere) | High (SQL in one place) |
| Testability | Difficult (requires DB mocking) | Easy (mock the interface) |
| Security | Manual/Error-prone | Centralized/Enforced |
| Schema Changes | High impact | Low impact (update one class) |
Let's advance our Knowledge Base project by creating a KnowledgeEntryRepository. We will follow the Modern PHP Standards established earlier in this course.
First, define an interface to ensure your repositories remain interchangeable.
PHPnamespace KnowledgeBase\Repositories; interface EntryRepositoryInterface { public function findById(int $id): ?array; public function create(array $data): int; }
Now, implement the concrete class. We use Dependency Injection to pass the $wpdb instance, making the class easier to unit test later.
PHPnamespace KnowledgeBase\Repositories; class KnowledgeEntryRepository implements EntryRepositoryInterface { private \wpdb $db; private string $table; public function __construct(\wpdb $db) { $this->db = $db; $this->table = $db->prefix . 'kb_entries'; } public function findById(int $id): ?array { #6A9955">// Always use prepared statements for SQL security $query = $this->db->prepare( "SELECT * FROM {$this->table} WHERE id = %d LIMIT 1", $id ); $result = $this->db->get_row($query, ARRAY_A); return $result ?: null; } public function create(array $data): int { $this->db->insert($this->table, [ 'title' => sanitize_text_field($data['title']), 'content' => wp_kses_post($data['content']), ], ['%s', '%s']); return (int) $this->db->insert_id; } }
int, array, ?array) to define clear contracts for your data.$wpdb->prepare() for dynamic values. While $wpdb->insert() handles escaping internally, manual SELECT or UPDATE queries must be sanitized via prepare().Refactor one of your existing admin pages that fetches data from the custom table we created in the Service Providers lesson.
KnowledgeEntryRepository class.findByCategory($category_id) method.$wpdb directly.$wpdb->prepare: Never concatenate variables directly into a SQL string. Even if a value comes from a "trusted" source, always use prepare() to prevent SQL injection vulnerabilities.By implementing the DAO pattern, we’ve moved from scattered, hard-to-maintain database queries to a clean, injectable, and secure architecture. We’ve ensured that our data access is centralized, which sets the stage for our next challenge: implementing efficient caching.
Up next: Query Caching Strategies — we will learn how to wrap these DAO methods with the Transients API and Object Cache to minimize database load.
Master Conflict Resolution in WordPress by implementing strict namespacing, hook prefixing, and asset isolation to ensure your plugins remain robust and stable.
Read moreStop SQL Injection in its tracks. Learn how to master $wpdb->prepare, enforce strict type casting, and audit your custom queries for production-grade security.
Data Access Objects Pattern
Custom Hooks for React