Using Modern PHP with WordPress: Part 2 – Dependency Injection and Containers

In Part 1 of this series, I have explained the purposes for this project. I hope you are able to pick up concepts and approaches from this series.

One of the core principles I use in PHP development is Dependency Injection (DI). That can be a scary concept for developers not used to using the pattern.

At its core, DI is the concept that each class should be self-contained and testable on its own. When a class is decoupled from other classes, it makes for more reusable code. DI operates on the core principle that if a class uses another class for its functionality, the required class is injected into the class, usually via the constructor.

<?php

class Person {

  private $job;  

  public function __construct( \Job $job ) {
    $this->job = $job;
  }

  public function get_job() {
    return $this->job->my_job();
  }
}

class Job {
  public function my_job() {
    return 'PHP Developer';
  }
}

$person = new Person();
echo $person->get_job();
// returns PHP Developer

This is obviously a simple example. There is a class Person and, via the constructor, we inject the Job class into it. We then assign the private property $job with the injected object. Thereafter, the Person class can now access the Job class without having to instantiate an object inside any other methods in the class.

In my example plugin, Photogrammapy, I use this approach in my Map class.

class Map {

	private $params;

	public function __construct( Defaults $defaults ) {
		$this->params = $defaults;
	}
}

In this case, I’ve defined a class, Defaults, that carries a bunch of default values for my Google Maps integration. Instead of instantiating the Defaults class in the constructor, I inject it into the class and then assign it to the private property $params. Many developers wrongly do this:

class Map {

    private $params;

    public function __construct() {
        $this->params = new Default();
    }
}

Here’s the rub. the latter example is problematic. It makes the Map class untestable unless the Defaults class is also available. DI solves this for us. Now we can write a test that defines our own decoupled Defaults class in the context of a test and inject that into our testable Map class.

Containers

Part of the Dependency Injection pattern involves containers. Containers, simply, will allow a class to be instantiated on demand. That means, on a page load, we aren’t hooking a bunch of classes into things and then leaving them unused. This is for performance and memory management.

For this project, I have used the PHP-DI library, installed via Composer. More on Composer in later parts of this series.

I have an Init class which is loaded via the init() callback on plugins_loaded. This class, as the name suggests, handles all the bootstrapping of my containers making them available to the rest of the application.

class Init {
	
	protected $providers = [];

	public function __construct() {
		Bootstrap::bootstrap();
	}

	public function init() {
		$this->register_providers();
	}

	public function register_providers() {
		$this->providers[ Photographs::NAME ] = new Photographs();
		$this->providers[ Settings::NAME ] = new Settings();
		$this->providers[ Map::NAME ] = new Map( new Defaults() );
	}
}

So that’s DI in a nutshell. Of course, that just scratches the surface, and there’s plenty of information about PHP dependency injection for further study.

In the next post, I’ll discuss Composer as a dependency manager for a project.

I am a professional PHP developer and am looking for my next job. Hire me!

  • Category: PHP