In some cases, creating classes that produce objects for working with a specific item, like an order in an eCommerce store, are perfect. You create one class and instantiate a new object every time you need it. Conversely, some data, like a plugin’s options, really only need one “store.” This is a perfect use for a class with all of the static methods.
In this article, I will show you how to build a database abstraction for options used in a plugin or theme and provide an example of when a class with all static methods is useful and explain late static bindings.
Creating a class where all methods are static can solve some issues in PHP. For example, a class that validates or formats data. Why instantiate a new object for each set of data to treat?
These “static classes” are similar to a group of functions, but they can take advantage of visibility and inheritance. Also, static methods can share data using static properties.
Before we do that, let’s look at a simple example of a class used for validation. It illustrates how these types of classes can be useful in your plugins, as they are globally available. This kind of validation can be used both in an option store class like I will show below, or for validating HTTP input in a REST API or likewise.
Validation Example
Let’s imagine you have a plugin that can work with one or more related services, such as email marketing services. You’re going to want, in several places, to validate that a given service is allowed.
The easiest way to do that is to check if a given service is in an array of allowed services, like this:
if ( in_array( $service, [ 'mailchimp', 'convertkit', 'aweber' ] ) ) { //is good! }
Now this simple solution works, but it’s not portable. If you need to reuse it, you’d have to cut and paste it. And then if the list of services changed, you’d have to change it in every place. That’s bad design.
Instead, let’s create a small class that can be accessed easily throughout the plugin to do this. By using all static methods calling this class anywhere in the plugin will be simple.
The first method will be called get_services(). It will have an array of services and return them through a filter. Now we have a filter to modify our list, but don’t have to worry about it being skipped.
class services { public static function get_services( $with_labels = false ){ $services = apply_filters( 'slug_get_services', [ 'mailchimp' => 'MailChimp', 'convertkit' => 'ConvertKit', 'aweber' => 'Aweber' ]); if( false == $with_labels ){ return array_keys( $services ); } return $services; } }
Our second method is called is_allowed_service(). This is our actual validator. We pass it the name of a service and it uses in_array() to check if the service is allowed or not.
public static function is_allowed_service( $service ){ if ( is_string( $service ) ) { return in_array( $service, self::get_services() ); } return false; }
This it. It’s a simple class, but it does one thing and one thing well. Yes, we have more code than we started with, but this new code is portable and easy to maintain. If we want to add another service we can add it to the list, or use the filter and all uses of this class change.
As An Options Container
In my last article, I wrote about the need to build APIs on top of WordPress APIs to control our data access.
This means building our own database abstraction. Let’s look at a simple container for data stored in an option. I like making these types of classes for options as they create a single location that defines the name of the option, and how it is saved and validated.
The base class will be declared abstract and have five methods and 1 property, all of which will be declared static.
The property defines the name of the option. This will have to be overridden in the subclass. The get and save methods are wrappers for get_option() and set_option() from WordPress core. There is are other methods which will be used for sanitization and validation, which we will look at shortly:
abstract class setting { protected static $key_option = ''; public static function get(){ return get_option( static::$key_option, '' ); } public static function save( $value ){ return update_option( static::$key_option, $value ); } public static function clean( $value, $option, $old_value ){ if ( $option == static::$key_option ) { if( ! static::validate( $value ) ){ return $old_value; } return static::sanitize( $value ); } return $value; } protected static function sanitize( $value ){ //must add sanitatization in sub class } protected static function validate( $value ){ //must add validation in sub class } }
Notice that the property for the option name is addressed in the methods using the static keyword instead of the self keyword. This is an example of late static bindings.
The self keyword does not traverse the hierarchy when extending a class. If we had used self, and then made a class like this, we would not get the expected results:
class api_key extends setting { protected static $key_option = '_my_api_key'; }
This is because when get() is called it would use the definition of the $key_option property in the class that the get() function was declared in. So we would get the empty property from the parent class. By using the keyword static, we tell PHP to use the value of the property from the current class. The static keyword enables late static bindings and behaves much more like $this does in non-static methods.
The third method is going to be used for sanitization. It would be tempting to put sanitization in the save method, but that doesn’t protect us when update _option() is called directly. It shouldn’t be, but we can’t stop other developers from doing it.
By using the presave_option filter, we ensure that all saves on this option will be sanitized the same, whether they use our database abstraction or not. Here is a complete example making use of the validator we built in the last section:
add_filter( 'pre_update_option', [ 'api_key', 'clean' ], 10, 3 ); class api_key extends setting { protected static $key_option = '_my_api_keys'; protected static function validate( $value ) { if ( is_array( $value ) ) { foreach ( $value as $service => $key ) { if ( ! services::is_allowed_service( $service ) ) { return false; } } } return true; } protected static function sanitize( $value ){ if( is_array( $value ) ) { foreach ( $value as $service => $key ) { if( services::is_allowed_service( $service ) && is_string( $key ) ){ $value[ $service ] = strip_tags( $key ); }else{ unset( $value[ $service ] ); } } } return $value; } }
What I like about this is we have a globally available database abstraction and a globally available validator. And we didn’t use a single global or singleton in the process.
Some people may be looking at this class and worry that every time you call the get() method it has to call get_option() and that seems inefficient. One solution would be to store the data in a property.
This is not needed because it’s redundant to WordPress’ object cache. Most likely your option value was already placed in the object cache when autoloader option was queried. If this is not the first time this option is called, it will be placed there.
This Is One Approach
I wouldn’t have used that kind of pattern when working with data that has multiple versions of same type of thing. For example, if you had an eCommerce system, you would want to instantiate an object for each order you were dealing with. I’ll cover that in my next article in this series.
Static classes are great for data where there is only one instance of that data. In a way, they are like classes that use the singleton pattern, but simpler. I hope you found this simple example useful. In my next article, I’m going to talk about the weakness of this approach and present an alternative solution for the problem. The best option, of course, always depends on your needs.
No Comments