Using A Config To Write Reusable Code – Part 3

This is the third part of a series of articles. ( Part 1, Part 2 )

So far, we’ve found out that we need to have a clear separation between reusable code and project-specific code, and we identified the Config file as being a promising tool to map data from one side of this separation to the other. In this third article, we’ll examine what our Settings page example looks like if we do indeed make use of such a Config file.

Code Against Interfaces

First, we’ll want an interface that we can code against. This is important, as we don’t want all of the rest of our code to be tightly coupled to one very specific implementation of the Config mechanism. Instead, we want to only couple our code to such an interface that provides the basic methods to access the data, and easily allow better and more feature-complete Config implementations to be injected later on.

interface ConfigInterface {

   /**
    * Check whether the Config has a specific key.
    *
    * @param  string $key The key to check the existence for.
    * @return bool        Whether the specified key exists.
    */
   public function has_key( $key );

   /**
    * Get the value of a specific key.
    *
    * @param  string $key The key to get the value for.
    * @return mixed       Value of the requested key.
    */
   public function get_key( $key );

   /**
    * Get an array with all the keys.
    *
    * @return array Array of config keys.
    */
   public function get_keys();
}

This is a very basic interface and it will be flexible enough for everything we need to do in these examples.

Injecting The Config Object

The basic principle now is that each reusable class will need a Config file with the configuration data that contains the project-specific bits to be injected. Our Plugin class that is responsible for instantiating these objects will inject the matching Config into the reusable class’ constructor.

Here’s how that would look like for the method in our Plugin class that initializes the Settings page and registers it with WordPress:

public function init_settings_page() {
   // Load configuration for the settings page.
   $config = new Config( AS_BETTER_SETTINGS_1_DIR . 'config/settings-page.php' );

   // Initialize settings page.
   $settings_page = new SettingsPage( $config );

   // Register the settings page with WordPress.
   add_action( 'init', [ $settings_page, 'register' ] );
}

We instantiate our basic Config implementation (see it on GitHub) by telling it where to find the Config file to load, and the inject that Config into the SettingsPage class.

This is still a very naïve implementation, as we need to create a separate file for each object we want to configure, and we are still directly coupling our Plugin class to a specific Config implementation here. We’ll revisit this bit of code in a later article to further refine both the way we instantiate a Config file as well as the way we decide what subset of the configuration data to use.

Okay, that was all still very easy, now comes the hard part…

Structuring Our Config Data

Our goal should be to create a reusable class that can transform a properly laid-out Config file into a collection of admin pages and subpages of arbitrary complexity. We don’t want to have a separate class for each page or a separate class for each section. We want to be able to just tell our SettingsPage class: “Here, this is what I need, go build it!”.

The way we can achieve this is be destructuring the hierarchy that goes into building such a Settings page and allow our class to iterate over that hierarchy, looping over multiple elements wherever it makes sense. So, let’s stick with our example we had in the previous articles and build a single page with a single section with two fields. This is what our hierarchical structure will look like:

admin page
settings
 \--section
     |--field 1
     \--field 2

The admin pages and the settings are only indirectly connected to each other, as you can have any number of pages without ever using settings. This is why we have these two distinct hierarchical roots.

At each level in this hierarchy, we need to decide whether it makes sense to loop over multiple elements, or whether there’s only ever one possible element at that level. And as it turns out, each level of this structure can indeed be used multiple times. We can have multiple (sub)pages, we can have multiple sets of settings, we can have multiple sections per set of settings and we can have multiple fields per section.

What we’ll do in our reusable class then is to have one method per level to add a single element for that level. This then allows us to iterate over our Config file and just loop through multiple elements at the same level and pass each individual item to one of these methods.

admin page          =>  add_page()
settings            =>  add_setting()
 \--section         =>  add_section()
     |--field 1     =>  add_field()
     \--field 2       > add_field()

As the entire structure then is recursive, we can simply iterate over our entire Config and pass the entire sub-array of the current level to one of these methods.

Here’s our Config file:

return [

   // Admin Pages.
   'pages'    => [
      'as-settings-better-v1' => [
         'parent_slug' => MenuPageSlug::SETTINGS,
         'page_title'  => 'as-settings-better-v1',
         'menu_title'  => 'as-settings-better-v1',
         'capability'  => 'manage_options',
         'view'        => 'views/options-page.php',
      ],
   ],

   // Settings API Settings Group.
   'settings' => [
      'assb1_settings' => [

         // Settings API Sections.
         'sections'          => [
            'assb1_settings_section' => [
               'view'   => 'views/section-description.php',

               // Settings API Fields.
               'fields' => [
                  'assb1_text_field_first_name' => [
                     'view'  => 'views/first-name-field.php',
                  ],
                  'assb1_text_field_last_name'  => [
                     'view'  => 'views/last-name-field.php',
                  ],
               ],
            ],
         ],
      ],
   ],
];

This is a simplified version of the Config file to make the structure obvious. You can also take a look at the complete Config file with comments.

I added inline comments to show where our indentation levels map to our overall Settings API hierarchy.

It should be rather obvious that assembling a Settings page through such a Config file, where we have adapted the way we submit the data to best fit the conceptual structure, is much easier than fooling around with several different WordPress functions, each of which needs a different callback to be able to complete its work.

Implementing The Settings Page

To make this all work, we will parse this Config file from within our SettingsPage class and pass all the relevant bits to the corresponding WordPress functions.

WordPress normally gets a callback for each of its Settings API functions, which is responsible for rendering the corresponding element. However, as we don’t want to deal with random callbacks and markup inside of functions, we map each of these callbacks to a View using a closure.

Here’s the example of a field being registered with WordPress:

protected function add_field( $data, $name, $args ) {
   // Prepare the rendering callback.
   $render_callback = function () use ( $data, $args ) {
      if ( ! array_key_exists( 'view', $data ) ) {
         return;
      }

      // Fetch $options to pass into view.
      $options = get_option( $args['setting_name'] );
      $this->render_view( $data['view'], [ 'options' => $options ] );
   };

   add_settings_field(
      $name,
      $data['title'],
      $render_callback,
      $args['page'],
      $args['section']
   );
}

The $data and $name arguments are passed in by array_walk() as part of its traversal. We’ve also added a third argument $args which is an associative array with additional data we need to pass around from level to level.

We then use these arguments to build a closure called $render_callback. The closure basically a) makes sure the Config has provided a view to render, b) builds the contextual data for that view and then c) renders the view from within the correct context.

Then, we call the WordPress function add_settings_field() with the closure we just built as the callback.

The developer adding a new Settings page does not need to care about all of this. The only thing that (s)he needs to add is a path to a view (or an instance of one).

Yay, Free Code!

You can browse or download the source code for this iteration of the plugin from its GitHub repository. It should contain lots of comments or hopefully descriptive method names. But if all else fails, do not hesitate to ask questions in the comments section below! This refactoring round added an entire level of abstraction, so I’m not entirely sure my attempts at explaining these changes will be satisfying.

In the next article of this series (which will be out in the coming days or months), we will completely isolate the different components we’ve now refactored and put them in entirely separate packages.

12 comments

  1. Dylan Kuhn says:

    I’m enjoying playing with this approach, thanks! Two questions come to my mind.

    Perhaps for clarity there isn’t a way to provide a default value for fields in the config file. The example name settings don’t really require one, but would adding an optional default value for fields in the config file be valuable?

    Getting a little more meta, the option saved in the database by SettingsPage could be a kind of configuration data also, supplied by the user instead of the developer. I would be tempted to create an implementation of ConfigInterface from the data returned by get_option( 'assb_settings' );, which I could inject into objects which would be blissfully unaware of whether their configuration came from a file or an option. Do you see any drawbacks to that idea?Report

    • Alain Schlesser says:

      Hey Dylan,

      Great, hope you’re having fun!

      The way I’ve set it up is that I have layered Config files. I have a defaults.php Config file for every plugin, and any of its keys can be overridden by site-specific or environment-specific Config files.

      Defining default values in the Config file itself does not make much sense, as the Config’s purpose is to provide customized values. In the cases where I want to make keys optional (so that the Config can just omit that key if it is not needed), I provide a way a method like $this->config->getKeyWithFallback( 'key', $fallbackValue );. This gets rid of cumbersome existence-checks and if/then code. You can see an example of how such an override can look here: https://speakerdeck.com/schlessera/the-secret-sauce-for-writing-reusable-code?slide=33

      As to your idea with the options via Config… I would recommend against that, because it blurs the lines between concepts that are technically similar, but serve entirely different purposes. I personally prefer to keep stuff that is conceptually different in completely separate implementations, even if they share 99% of the code. I just like to keep very clear boundaries, and don’t want to make it too easy for myself to mess up my architecture later on.

      The thing is that the Config files are meant for configuration that the developer does. It sets up the basic components, it adapts the classes to the current runtime context, etc…

      Options, on the other hand, are meant for the user to adapt the software to his specific needs within the constraints the developer has set. These are changed after you’ve shipped/deployed/distributed your code by external users.

      So, apart from the fact that these two serve very different purposes, they also have different requirements. For the Config files, for example, you’ll want to be able to optimize them as best as possible for production use. Many frameworks come with compiler systems, that take all of the active Config files and compile them into one big chunk optimized for fast runtime parsing. For the Options, the software needs to be able to work without them on first run, and they need to be easily changed, preferably by not needing write access to areas that might alter the system as a whole.

      Mixing these two will make it very difficult to keep a clear distinction and do major changes or optimizations later on.

      Hence, I think it would be easily done to wrap options into a ConfigFile, but I would recommend against it. You can however create a different implementation that you can inject into your class, like an OptionStore or similar.

      Cheers,
      AlainReport

      • Dylan Kuhn says:

        You make a lot of good points, thanks so much. I still can’t shake a little fogginess on how to handle default Options values, though. In the better settings example, let’s say that until the user saves their name settings the plugin should use ‘Foo’ as the default first name and ‘Bar’ as last name. Where should those default values live? Since they are specific to the application, we don’t want them in a reusable OptionStore, right?Report

        • Alain Schlesser says:

          Hey Dylan,

          Ah, sorry, re-reading what I wrote, I think my answer was a bit lacking regarding the default value. What I wrote applies to custom logic code, but not in a case like we have here where the code that iterates over the Config is reusable.

          It is actually pretty straight-forward to add default values to the Config format for the Settings page. I’ve done so for the example plugin, to demonstrate this.

          For each field, we first add a 'default' key that holds the default value for that settings field: first name default & last name default.

          Then, we add some minor processing into our SettingsPage class to add the default value to the loaded $options array if it is missing that key: initializing an option.

          To see this in actions, you’ll need to delete the already stored option key (assb1_settings) from your wp_options table first.

          I think I managed to better respond to your actual question now. If not, just let me know! :)Report

          • Dylan Kuhn says:

            Thanks Alain,

            My question could have been clearer too, but this is exactly what I was considering. I do see a drawback though. The settings page will now show our default option values, but other parts of the plugin that make use of the option values would have difficulty getting the defaults when no option values have yet been saved, and get_option( 'assb1_settings' ) returns false.

            I’m afraid solving this would muddy up your very clear example. My instinct is that the UI-related configuration data required by the Settings API should be separated from configuration regarding the structure and validation of the option data. Maybe I’ll play with this idea some more.Report

          • Alain Schlesser says:

            Hey Dylan,

            Your instinct is right. The example is simplified, in that one class is responsible both for the persistence of the data as well as its representation.

            I had planned to keep this series of examples concentrated on the Config aspect, as they are complex enough as they are. But as I myself hate oversimplified examples that just skip the problematic parts altogether, I will think about doing that proper separation first before moving everything into Composer packages.

            Thanks for keeping me accountable!
            AlainReport

  2. Hi Alain,
    I like the idea to use a class to handle configuration files, I made a plugin that use 2 config file, one for settings initialization and one for settings fields (but I want to improve the code because 2 file maybe are too many :-)), for example with Config class I could better manage options, theme_mods and default of what I do now, I’ll try this approach, if you want to give a look at what I’ve done (and I will appreciate it) here is the link https://github.com/overclokk/italystrap-extended/tree/Dev, in the admin/config/ there are the config files and, for now, in the src/settings/src there are the settings API classes that I’ve made (in future I will create a separate repository for this kind of stuff in case of utilizing in other project with composer), for now the implementation is very ugly and is here admin/Settings.php.
    I’ll wait for your next article :-)
    Thanks for what you do.Report

    • Alain Schlesser says:

      Hi Enea,

      If you want to play around with a Config component, here’s the one I use: https://github.com/brightnucleus/config . It is not feature-complete yet, but I already use it on large-ish production sites.

      The main goal was to have an easy way of having one single large Config file per plugin, but still having an elegant way of only injecting into the classes what they need. The individual classes should not need to know anything about the overall file layout or Config/project structure. That’s why you can split the Config into a SubConfig at any point in the hierarchy: $subConfig = $config->getSubConfig( 'Some\Key\Deep\Within' );. So, each class only gets the specific SubConfig that it is meant to understand. This is done hierarchically: The site passes the plugin subConfig to the plugin, the plugin passes the module subConfig to the modules, the modules pass the class subConfig to the classes, …

      Most of the time, the Config structure I then use is directly mirroring the namespaces I use, which is very convenient: ( new ServiceProvider( $config->getSubConfig( __NAMESPACE__ ) )->register(); :)

      Your code already looks good, but you should work on splitting it up into more manageable chunks. I’ve experienced this multiple times before: such large code collections tend to become obsolete all at once, because you cannot easily pick only the parts out of it that would still be useful. So, chances are that, despite the fact you’ve built up such a large foundation, you’ll restart from scratch at one point in the future. Keeping your projects smaller and more modular let’s you swap out the pieces that need an overhaul while still keeping what is still good.

      Keep up the good OOP-work!
      AlainReport

      • Hi Alain,
        I will, it’s very interesting your Config API.

        Yes, I know, but when I start to write code I don’t know all the things I need in future so I put (almost) all the similar stuff together, when the project becomes mature or I need some functionality in other classes I start to split the things to get more modularity, for example now I started to write a class for manage the plugin and widget settings updating because they do the same things when they update settings :-).Report

    • Hi Tim,

      First of all, the code in this post is not yet the final iteration. The series will still continue and further refactor our Settings page.

      As a teaser, you can peek into the following packages: OptionsStore, AdminPages. Very generally, the approach I want to take is this (and it might be more than one blog post until that is done):
      – Have an OptionsStore that is responsible for managing the actual option values. It abstracts away the persistence of these options, so that, for example, you can mix site-specific and user-specific options without needing to care in the consuming code.
      – To make this OptionsStore work, have a set of structured Option objects that are able to validate, sanitize and escape themselves. So, an EmailOption object will know how to validate its own content to make sure it is actually a real email, and it know how sanitize it to store it in the database.
      – Have a set of FormField objects that are built around the specific Option objects and a Form container object, so that you can fill a View with a list of such form fields. Each field object knows how to render itself as an HTML input, and it gets its associated Option object through the OptionsStore that was injected into the View.

      Each of these types of objects can of course be extended/overridden for all the more complex needs. But the default setup should allow you to build a Settings page with practically no HTML and no escaping/sanitization/validation code, this should all be taken care of by the semantic objects you’re using.

      In the unit tests of the brightnucleus/options-store package, you can see how easy it is to define such a semantic set of options (keep in mind I have only implemented two very basic object types yet): https://github.com/brightnucleus/options-store/blob/master/tests/ConfigurableOptionsStoreTest.php#L63-L69

      So, what does that have to do with your question? Well, with the above approach, you only need to provide “per-field views” if you actually want to override the default input rendering of such a semantic FormField. So, in 95% of the use cases, you’ll just have 1 view for the form, and that’s it, the rest is taken care of automatically.

      What you have proposed in the pull request has a severe drawback:
      You only needed an additional “name” attribute now, but it had you change the method signatures of several methods. Amongst these were methods that would, in a more fully fleshed out code base, declare implementing a specific interface, like the render( array $context = [] ); method, for example. If you start adding additional arguments to your methods after identifying a new problem, this will very probably break your existing code, and hints at a design issue. So, either your (my) current design is broken, or you’re looking in the wrong place for the solution.

      Of course, we could have added the 'name' to the $context argument. After all, the $context argument is meant to collect everything that the view will later need. For this simple example, that would have actually worked out just fine. However, think about adding a select field, where you need to define the available options in your config file. Or think about calendar element that shows available and already booked time slots… Do you want to pass all this random data as a non-structured ad-hoc array from method to method?

      This is why I want to build this the other way around. In your config, you define the actual option together with all the data that it needs. This is a very structured, and most of all, scalable approach. If an Option or the associated FormField knows everything it needs to know to render itself, you only ever needs to pass a single Option around per option… or, even a single OptionCollection for all options.

      I’m not sure I could explain the principle well enough in this single comment, so feel free to ask additional questions if something is not clear.

      Cheers,
      AlainReport

      • Tim says:

        Thanks for the feedback, Alain. The logic you presented definitely makes more sense than the backwards approach I had in mind. Very glad to hear that the series will continue!Report

Leave a Reply

Your email address will not be published. Required fields are marked *