A golden rule of WordPress development is “always use a WordPress API when possible.” We want to use classes and functions provided by WordPress to communicate with the database to ensure the right hooks are fired, and our code is as protected against changes made in future versions of WordPress.
This also means we need to create a database abstraction on top of WordPress APIs. Doing so early on in a WordPress plugin, theme, or site will lead to less copy pasting and make it easier to fix changes later on.
In this article, I’m going to talk about why a database abstraction is important and share some insights into the process of creating one. Over the next few weeks, I will share practical advice on making database abstractions for different types of data.
Using WordPress Database APIs Is Not Enough
The “always use a WordPress API when possible” rule is why we don’t use a PHP provided API such as PDO to write to the database. We always use WordPress’ database API, the WPDB, class instead.
Not only should you be using a WordPress API in your projects, you should use the highest level of API provided by WordPress. This speaks to why we use wp_insert_post(), to add a post, instead of using WPDB to insert data into the posts table directly. Yes, wp_insert_post() uses WPDB, but we use the highest level API we can. This is to make sure that all the hooks that should fire when a post is inserted are run.
By using high-level APIs we make the lower level APIs invisible. When we do that, we’re not just scoring points in the proper principles of software design game. We get tangible benefits in terms of forward-compatibility — if the lower-level APIs change it won’t affect the high-level. We also get to take advantage of the extensibility of WordPress fully, by not bypassing the hooks that run throughout these APIs.
The further we stay above WPDB the more generic our code is. You might find yourself using a non-standard replacement for WPDB and not even know it.
Following these rules means your plugins or themes should always use functions like get_post_meta(), updat_option(), or update_theme_mods(). Or if custom tables were being used, then a method of the WPDB, class, called on the instance of WPDB stored in the global $wpdb variable.
We’re Not High Enough Yet!
It is essential in a plugin or theme to build your own abstraction between your code and the WordPress database APIs. Not just when you’re using custom tables, but as wrappers around post, taxonomy, user, comment, and options data.
When you create an abstracted API, you are now piping all data through one location. Adding one additional layer of abstraction effectively makes WordPress invisible.
As a result, any changes to your data structure only require changing your abstraction. No more hunting through your code base, or if you’re a plugin developer, your add-ons, to make a change.
Database abstractions keep your code DRY and minimize the pain of refactoring. When done right, they make it easier for other developers to understand your code.
Ease of understanding is very important. It’s easy to create an ad-hoc system or a system that is not enforced on a code level, but is expected to be followed.
For example, expecting that every time data is stored as a post type that you use, post_status will have one of three meaningful values, and certain meta keys will be set with specific types of data.
As long as no one working on or with your code violates that agreement, it’s fine. But nothing, besides a written or unwritten rule prevents breaking that system. Database abstractions provide a code-level definition for the rules of how your data works.
Violations can result in failed unit tests or exceptions being thrown.
My rule is that unless the responsibility of a class or function is to interact with the database, then that class or function should not use any time of class or function that acts as a WordPress database API.
You might think I’m just updating one post meta. No, you’re making refactoring harder, you’re inviting forward-compatibility breakages, and defeating the whole purpose.
Even if you use posts to store your data, which is OK, you should have a method in your CRUD classes for interacting with that meta.
An Important Note On Sanitization And Validation
Sanitization and validation are an essential part of the design of a database abstraction. It’s tempting to put the sanitization right into the write functions and validation right into the read functions.
When wrapping standard data types, like posts options, there is no guarantee that another developer is going to respect your abstraction. That is why sanitization should still follow standard WordPress methods.
Use register_meta() for post meta, and register_setting() for options. OK register_setting is annoying, I use presave_option filters instead.
Abstract All The Things!
Building REST APIs into our projects means that we need a new way to read and write data, but this new way probably doesn’t make the old ways obsolete. If we’re not careful, that can lead to writing code in violation of the Do Not Repeat Yourself (DRY) principle.
But, when we have good database abstractions in place, we can reuse the same CRUD functions in our REST API endpoints as we do everywhere else in our codebase.
I hope this article has shown you the importance of abstracting all the things. It’s not just good design, it makes code easier to work with and maintain.
If you’re on the “abstract all the things” bus, keep an eye out for my next few article, which will provide some good patterns for implementing this philosophy.