Essay
Using A Config To Write Reusable Code - Part 2
This is the second part of a series of articles. ( Part 1 , Part 3 )
This is the second part of a series of articles. ( Part 1, Part 3 )
In the first part of this series, we’ve seen a simple “OOP” implementation of a SettingsPage class that adds a basic settings page to the WordPress admin backend using the Settings API. We’ve also seen that the simple fact of writing OOP code does not in and of itself make the code reusable.
The goal we identified is that we want to separate project-specific code from reusable code. To be able to put the reusable code into a reusable form, it mustn’t contain any project-specific logic whatsoever.
Extracting The Views
A logical next step would be to extract the views out of theSettingsPage class, so that the latter only contains code related to the assembly of the settings page, while removing the code related to showing our project-specific fields and labels.
GitHub Repository: Example Code: Settings Page - Broken Implementation v2
Our second iteration now has a very basic View class that can take a filename of a PHP view and render it when requested to do so. This way, we could add a mechanism to provide a path to custom views, and these custom views would be rendered instead of the default ones.
The settings page still works as expected, but the actual text fields and labels that are being rendered come from separate view files, not from the SettingsPage class we want to make reusable. This is an improvement over our first iteration. However, it still falls short of our primary goal:
- The
SettingsPageclass still decides on the number and types of fields to provide. - The
SettingsPageclass still decides what data to store and where to store it. - The
SettingsPageclass still decides what menu entry to create.
When we consider going that route, though, we quickly realize that we’ll end up writing more code creating custom models, views and controllers to make use of the “reusable” SettingsPage class than we originally needed for our first, non-reusable iteration. It seems as if, in this particular instance, the MVC pattern is not a good fit and will lead to a scenario where rewriting from scratch will be faster than reusing. Back to the drawing board then!
Back To The Drawing Board
To create a reusableSettingsPage , we don't want to create a complete architecture around it. At its most basic, we want to pass the business-specific code into the SettingsPage , and it should then combine it with the reusable code to provide what we need...
How about injecting the business-specific code through the constructor?
Conceptually, this would fit our needs. We could keep the two types of codes separate, and one type would then be injected into the other type at runtime. However, a practical implementation would quickly show that this is not a good method either. We’d have a huge list of constructor arguments, which will make it very complicated to actually use our reusable class.
class SettingsPage {
// Probably not worth exploring this concept further...
public function __construct(
$field_type_1,
$filed_name_1,
$field_label_1,
$field_description_1,
// ...
) {
// Store the huge number of constructor arguments.
}
}
Well, how about injecting an associative array into the constructor then?
This would indeed alleviate the problem of the growing argument list, and is something that is often used in procedural code bases like WordPress (see the hundred-and-one uses of the $args argument). The associative array can be injected through the constructor and stored as a property that will then be available for the rest of the code to consult.
class SettingsPage {
/**
* @var array
*/
protected $args;
/**
* @param array $args Associative array of arguments.
*/
public function __construct( array $args = [] ) {
$this->args = $args;
}
}
This associative array can contain labels, option names, section descriptions, etc… Also, as it can be multi-dimensional, we could provide an arbitrary number of fields or sections, and the reusable code could then adapt to the actual number the array provided.
However, using such an array is not without issues:
- If we type-hint against an array, we can't pass in an object implementing
ArrayAccess. - If we don't type-hint against an array, we have no sanity check whatsoever.
- There's no convention on how to assemble the array.
- We cannot "extend" an array to provide validation and/or convenience functionality.
- We cannot easily inject an array through an auto-wiring injector.
- We cannot inject additional functionality into the array itself (like injecting a logger to log changes).
So, in essence, we’d need something like an associative array, but as a real object, right?
Yes! Such an object would be injectable trough the reusable class’ constructor, and offer all the benefits of an associative array, while getting rid of the aforementioned issues that come with a pure array. Such an object is commonly called a Config file/object/class. In case this should not be obvious, this is the short form for Configuration, and it is called this way because it allows you to configure the parts of the reusable code that are not fixed. The fixed parts are hard-coded because there’s no “business-specific” decision to be made about their implementation.
class SettingsPage {
/**
* @var ConfigInterface
*/
protected $config;
/**
* @param ConfigInterface $config Configuration object.
*/
public function __construct( ConfigInterface $config ) {
$this->config = $config;
}
}
When we inject such a Config object, the Config object itself can contain all sorts of logic, like for example providing means to validate its contents. Also, we can provide an extended, more specialized Config object whenever there’s a need for additional functionality.
We Are On The Right Track
The Config object seems to unite the advantages of an associative array with all the benefits of a real OOP approach. We'll examine such a Config object in detail in part 3 of this series. In the mean time, feel free to share your thoughts in the comments!This is the second part of a series of articles. ( Part 1, Part 3 )