Using A Config To Write Reusable Code – Part 2

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 the SettingsPage  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:

  1. The SettingsPage  class still decides on the number and types of fields to provide.
  2. The SettingsPage  class still decides what data to store and where to store it.
  3. The SettingsPage  class still decides what menu entry to create.

These three points represent business logic that we can’t easily fit within view files, so we need to find yet another method of splitting up the code. Points 1. & 2. could be solved by extracting a “Model” out of the class, analogous to the “View” we’ve already extracted. We seem to be on our way to completing the “MVC” (“Model-View-Controller”) architectural pattern for this single reusable object…

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 reusable SettingsPage , 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).

These issues are all more or less related to the fact that we are not injecting a real object here.

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 )

4 comments

  1. Dylan Kuhn says:

    Seems like a promising approach! The side trip towards an MVC solution is a great illustration of the way a pattern can look promising and end up burdensome. I couldn’t resist peeking at the brightnucleus components and look forward to giving them a try. Thanks for sharing all this quality stuff!Report

    • Alain Schlesser says:

      Hey Dylan,

      Thanks for stopping by!

      The third part of this article series will be published soon where I show how I currently tackle this problem, and it will have an improved version of one of my existing packages.

      If you intend to try using one of the brightnucleus components, don’t hesitate to hit me up if you have questions!

      Cheers,
      AlainReport

  2. Robert Gadon says:

    Hello Alain!

    As someone who is relatively new to the work of procedural coding in PHP and doing true development work in WordPress (as opposed to simply ‘site-building’), I find your discussion interesting. Some of what you say is bit beyond me. What’s beneficial is to read, listen and observe how you think about this problem, particularly in an OOP context.

    I’m a student of Tonya Mork at KnowTheCode.io and the WordPress Developer’s Club Slack channel. She incorporates your thinking on developing a configuration process into her tutorials on procedural coding. Interesting to see how those ideas get extrapolated to the world of OOP.Report

Leave a Reply

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