Initialize a plugin with a configuration object

The WordPress plugin API, based on actions and filters called in a certain order, forces plugin authors to follow a simple rule to guarantee interoperability: Whatever you do, offer a way to turn it off.

If your plugin uses a code like this …

add_action( 'wp_loaded', 'my_callback' );

… other plugins or a theme can turn it off with …

remove_action( 'wp_loaded', 'my_callback' );

This won’t work with objects:

add_action( 
    'wp_loaded', 
    array ( new Plugin_Object, 'setup' ) 
);

Another plugin has no access to the object, so it will be very difficult to remove this callback.

There are two solutions for this problem: static access to the instance of the main class and a filter for a configuration object.

Static access to plugin instance

An example for the first solution can be found in this gist. Basically, we don’t use new Plugin_Object, but new Plugin_Object::get_instance() to create the object.

public static function get_instance()
{
	NULL === self::$instance and self::$instance = new self;

	return self::$instance;
}

Initialization works like this now:

add_action(
	'wp_loaded',
	array ( Plugin_Object::get_instance(), 'setup' )
);

This works, and it has become quite popular. But I never liked the static method. The only benefit is an on/off option, the ability to stop a plugin. But what if we want to change just some part of the plugin without disabling it completely? We had to add some actions and filters, more and more probably. I think this is not very elegant.
That’s why I prefer a configuration object filter now.

Configuration object filter

In this structure, the main controller object will be created always. But, as the term controller already suggests, it is just a light-weight object with one job: sticking other objects together.

namespace Wpkrauts;

class Controller
{
    protected $plugin_file;

    public function __construct( $file )
    {
        $this->plugin_file = $file;

        \add_action( 'wp_loaded', array ( $this, 'plugin_setup' ) );
    }

    public function plugin_setup()
    {
        $data = new \stdClass;
        $this->set_plugin_data( $data );

        // class names
        $data->model   = __NAMESPACE__ . '\Plugin_Log_Data';
        $data->view    = __NAMESPACE__ . '\Console_Live_Logger';

        /* You can change the class names here, the new classes have just to
         * implement the interfaces.
         * Or set "$data->stop" to TRUE in the filter to stop further processing.
         */
        \apply_filters( 'wpkrauts_base_plugin_data', $data );

        if ( ! empty ( $data->stop ) )
            return;

        $model = new $data->model( $data );
        $view  = new $data->view( $model );

        \add_action(
            'wp_print_footer_scripts',
            array ( $view, 'show' )
        );

        \do_action(
            'wpkrauts_base_plugin_loaded',
            $model,
            $view,
            $data
        );
    }

    protected function set_plugin_data( $data )
    {
        $data->url     = \plugins_url( '', $this->plugin_file );
        $data->dir     = \plugin_dir_path( $this->plugin_file );
        $plugin_info   = \get_file_data(
            $this->plugin_file,
            array (
                'name'    => 'Plugin Name',
                'version' => 'Version'
            )
        );
        $data->name    = $plugin_info['name'];
        $data->version = $plugin_info['version'];
    }
}

As you can see I separated the plugin code by following a MVC structure. Plugin_Log_Data and Console_Live_Logger are the model and the view. Both implement simple interfaces:

interface Log_Data
{
    public function __construct( \stdClass $data );

    public function get_value( $name );
}

interface Live_Logger
{
    public function __construct( Log_Data $model );

    public function show();
}

The actual implementation in my sample case logs the plugin data to the browser’s JavaScript console.

Screenshot of the result in a console

class Plugin_Log_Data implements Log_Data
{
    protected $data;

    public function __construct( \stdClass $data )
    {
        $this->data = $data;
    }

    public function get_value( $name )
    {
        if ( isset ( $this->data->$name ) )
            return $this->data->$name;

        return new \WP_Error(
            '1',
            "Invalid name: $name",
            debug_backtrace()
        );
    }
}

class Console_Live_Logger implements Live_Logger
{
    protected $model;

    public function __construct( Log_Data $model )
    {
        $this->model = $model;
    }

    public function show()
    {
        printf(
            '<script>console.log( \'%1$s\nVersion: %2$s\n%3$s\' );</script>',
            $this->get_js_var( 'name' ),
            $this->get_js_var( 'version' ),
            $this->get_js_var( 'bogus' ) // error demo
        );
    }

    protected function get_js_var( $var )
    {
        $text = $this->model->get_value( $var );

        if ( is_wp_error( $text ) )
            return "ERROR: " . esc_js( $text->get_error_message() );

        return esc_js( $text );
    }
}

This might not be the most useful example, but I hope it is easy to understand.

Now let’s go back to the controller. There is an important line:

\apply_filters( 'wpkrauts_base_plugin_data', $data );

Since object variables are passed as an identifier in PHP we don’t even need a return value from the filter. You can change it in a custom additional function, and these changes will be applied immediately.

To stop further execution of my plugin you can do this:

\add_filter( 'wpkrauts_base_plugin_data', function( $data )
{
    $data->stop = TRUE;
});

Dead simple, almost too trivial.

But you can do much more:

All of this and probably more is possible with just one single filter. No static methods are needed anymore.

The configuration object I have used here is a simple stdClass. This is not ideal, and when you need a copy it might become expensive. I will show an alternative in a later article.

You can find all code examples for this article on GitHub.

Author:

Web developer since 1998, moderator on WordPress Stack Exchange, author, manic reader.

Code is my drug.

Find Thomas Scholz on toscho.de⁠, ⁠, ⁠, ⁠, ⁠, ⁠, and .

7 Comments

  1. webaware29.07.2013 01:45

    From the other direction, if you need to remove hooks to objects that don’t have globals you can easily access, you can loop over $wp_filter and check for the objects to remove their filters. See function removeObjectFilters() on this blog post:

    http://snippets.webaware.com.au/snippets/conditionally-disable-pages-children-in-wordpress/

    Reply

  2. Leho Kraav (@lkraav)25.08.2013 09:38

    So the minimum PHP requirement for this approach is 5.3, yes?

    Reply

  3. David – 05.09.2013 14:06

    I guess i have to try this approach to find all pros and cons. What I see now is, that it might become a very time-consuming implementation whith larger plugins where you will have more than one model, one view and one controller.

    What is a proper way to make basic attributes (like VERSION) available to other objects? Dependency injection?

    Reply

Leave a Reply

Your email address will not be published.