Dependency injection via service container

For a couple of projects now a service container helped me to quickly build fast and lightweight plugins. Often you have to access variables or data that is far away from the current scope, like you need the plugin config within some template or method. Read on if you think of a function `get_config()` or writing `global $_my_config;`, because this is just bad and I like to call it wrong in terms of OOP (object oriented programming).

What do we want to keep accessible at any time?

  • Simple strings/bool (like credentials or settings for the plugin).
  • Posts, pages or other (our own object pool).
  • Own instances of our plugin classes (to simplify OOP).

Usually you instantiate everything you need at once which costs much memory. Or you instantiate it way to late which makes the plugin not very flexible or maintainable.

A service container solves all this. Less memory usage, faster and simpler plugins. Easy to maintain and extend.

A simple service container

What we see here is some altered code of Daniel Hüsken (inventor of BackWPup), who were inspired by Pimple. It is a very simple service container more like an object pool with lazy instantiating:

class Service_Container {
  private $values = array();
  private $raw = array();

  function __construct( $values = array() ) {
    $this->values = $values;
  }

  function set( $id, $value ) {
    $this->values[ $id ] = $value;
  }

  function has( $id ) {
    return array_key_exists( $id, $this->values );
  }

  function get( $id ) {
    if ( ! $this->has( $id ) ) {
      throw new \NotFoundException( // as in PSR-11
        sprintf( 'Service %s does not exist.', $id )
      );
    }

    if ( isset( $this->raw[ $id ] ) ) {
      return $this->raw[ $id ];
    }

    $value = $this->values[ $id ];

    if ( is_callable( $value ) ) {
      $value = $value( $this );
    }

    return $this->raw[ $id ] = $value;
  }
}

The simplest of all service container I’ve seen so far. This is especially good in the WordPress context when you have no need for complex patterns, factories or scope for dependency injection.

Very handy in a plugin

Imagine this in a plugin:

$_someslug_container = new Service_Container( [
  'api_key' => 'abba1972',
  'foo'     => new \Someslug\Not_Lazy_Loaded_Class(),
  'api'     => function ( $container ) {
    return new \Someslug\Api( $container->get( 'api_key' ) );
  }
] );

First we store some credentials in the service container, then we store some kind of configuration which instantly costs run-time and memory. To avoid flooding the memory cellar we can lazy load things by wrapping them into a function (like “api” entry). This way the API will only be instantiated (and cost memory) when it is really used.

Using the services anywhere

We could add some function to access the container easily from anywhere:

function _someslug_container() {
  global $_someslug_container;

  return $_someslug_container;
}

Which is not mandatory but helps at any point of the plugin or theme like in functions, methods, templates and so on:

$api = _someslug_container()->get( 'api' );

Now, wherever we are, we can use things out of our container. Here the API class get instantiated for the first time. Before it didn’t do anything, took no run-time and very little memory which keeps the plugin/theme fast and lightweight.

What WordPress Core and PHP gives us

This blog is all about not reinventing the wheel. So lets find out how to solve this using WordPress classes. You may want to add a layer to fulfill the PSR-11 proposal for service container.

Extending \WP_Dependencies

The class WP_Dependencies sounds like a class that can be used as a service container. But instead of an object, function or some other primitive it wants to store the path or URL to a service. That’s not what we want. So unfortunately we need to fix a lot here:

class Service_Container extends \WP_Dependencies {
  function get( $handle ) {
    $queried = $this->query( $handle );

    if ( is_callable( $queried->src ) ) {
      // Lazy load service, so we resolve it.
      $queried->src = {$queried->src}();
    }

    return $queried->src;
  }
}

This may contain a lot of things we don’t need but we can now resolve services and reuse them anywhere:

$container = new Service_Container();

// Add service "stdClass" (lazy loaded by a function)
$container->add( 'foo', function () { return new \stdClass(); } );

// Access service (lazy load will be resolved)
$stdClass = $container->get( 'foo' );

A very simple service container or object pool more to say. There is surely more magic hidden in the WP_Dependencies class to work as service container.

Other possibilities

There is more to try out:

  • \WP_Hook and its ::apply_filter method.
  • \WP_Session_Tokens for its simplicity.
  • \SplObjectStorage given by PHP itself (since 5.3).

Leave a Reply

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