Inversion of Control, is a pattern that allows us to unit test our PHP code easier. It enables easier Dependency Injection; for those who aren’t familiar with this concept we’ll go over it below.

This section will cover a few advanced concepts in one, only because they are so interconnected.

If you’re already a PHP IoC expert, please feel free to skip to the Ruby section.

There are a number of Inversion of Control Containers, IoC Containers for short, out in the wild jungles of open source software.

I’ll show you just how difficult unit testing without IoC can be. In this example we’ll be using the excellent GuzzleHttp Packages as our client to make HTTP calls to the Open Weather API.

The goal of this class is to expose a method weatherIn() that accepts an argument of a city name. It will retrieve the current weather in that city from a free Weather API and return the response.

Let’s go ahead and create a simple, but untestable PHP version of this Weather Forecaster:

use GuzzleHttp\Client;

class Forecaster {
  private $guzzle;

  public function __construct() {
    $this->guzzle = new GuzzleHttp\Client([
      'base_uri' => 'https://api.openweathermap.org/data/2.5/weather',
    ]);
  }

  public function weatherIn($city) {
    $response = $this->guzzle->get([
      'query' => [
        'q' => $city,
        'appId' => 'INSERT YOUR APP ID HERE',
      ]
    ]);


    return json_decode($response->getBody()->getContents());
  }
}

This simple class will allow us to get the current weather of any city listed in the OpenWeatherApi:

$forecaster = new Forecaster;
$weather = $forecaster->weatherIn('London');

// echo'ing out the JSON of the weather in London
echo(json_encode($weather));

=> {"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":300,"main":"Drizzle","description":"light intensity drizzle","icon":"09d"}],"base":"stations","main":{"temp":280.32,"pressure":1012,"humidity":81,"temp_min":279.15,"temp_max":281.15},"visibility":10000,"wind":{"speed":4.1,"deg":80},"clouds":{"all":90},"dt":1485789600,"sys":{"type":1,"id":5091,"message":0.0103,"country":"GB","sunrise":1485762037,"sunset":1485794875},"id":2643743,"name":"London","cod":200} 

However if we wanted to unit test this class, we’ll quickly find out that we will be making an API call to the OpenWeatherApi every single time we run our test suite.

One way to mitigate this problem is by mocking the GuzzleHttp Client and returning the response.

However, because the GuzzleClient is initialized in the constructor we’re unable to mock it.

The Solution

In general, the less your classes know to function the better. This lesson touches on the concept of the Single Responsiblity Principle which basically states:

A class should be good at one thing.

Our Forecaster class is doing a so-so job at doing 2 things:

  1. Create and store a GuzzleHttp client with a specific base URL
  2. Send an API request with the stored GuzzleHttp client and return the decoded JSON results

Our Forecaster class should only be concerned with weather things right? Intializing and configuring an HTTP client shouldn’t be one of them. The creation and configuration of dependencies is were Inversion of Control Containers shine.

Refactoring Using an Inversion of Control Container

All major PHP frameworks include an IoC Container. It’s not a construct of the language but it’s a pattern that’s used to help us separate the creation and configuration of dependencies.

Whew that’s a lot of big words so I’ll just show you with a reasonable example.

Because we’re not swearing to any PHP framework in this book - I’ll turn to some of the highest quality PHP packages available. I’m speaking of the League of Extraordinary Packages.

These packages are awesome because they’re held to a very high standard. A few of the requirements to have a package listed:

  1. Write unit tests. Aim for at least 80% coverage in version 1.
  2. Use Semantic Versioning to manage version numbers.
  3. Use a vendor namespace (League in our case) for PSR-4 autoloading. Shove code in a src folder.

etc.

You can read the other quality standards at PHP League’s Quality page

One such package in this repository includes a package simply called Container

The Container package allows us to register and retrieve services:

$container = new League\Container\Container;

// add a service to the container
$container->add('service', 'Acme\Service\SomeService');

// retrieve the service from the container
$service = $container->get('service');

var_dump($service instanceof Acme\Service\SomeService); // true

Think of IoC Containers as just simple key => value stores. We store a class or Closure in the container and assign it a name so we can retrieve it later.

The benefits are threefold:

  1. Easier unit testing because we can actually mock dependencies
  2. Easier composition of classes
  3. Code reuse becomes possible

This will become apparent when we refactor our Forecaster

Injecting the GuzzleHttp Client as a Dependency

To refactor our Forecaster class to know less and still be able to function as expected, we’ll move the creation of the GuzzleHttp Client outside of the class.

First thing’s first, let’s pass the GuzzleHttp client as an constructor argument:

class Forecaster {
  private $guzzle;

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

  public function weatherIn($city) {
    $response = $this->guzzle->get([
      'query' => [
        'q' => $city,
        'appId' => 'INSERT YOUR APP ID HERE',
      ]
    ]);


    return json_decode($response->getBody()->getContents());
  }
}

Now, our Forecaster has no idea what $guzzle is, it only knows that there is a get() method defined on it. Congratulations! We can now unit test our Forecaster class because we can pass in a mocked version of Guzzle:

class ForecasterTest extends \PHPUnit_Framework_TestCase {
  public function testGuzzleGetIsCalled() {
    $guzzle = Mockery::mock('GuzzleHttp\Client');
    $guzzle->shouldReceive('get')->with([
      'query' => [
        'q' => $city,
        'appId' => 'INSERT YOUR APP ID HERE',
      ]
    ]);

    $forecaster = new Forecaster($guzzle);

    $forecaster->weatherIn('Akron');

    // Now you can actually mock HTTP responses and test your code!!
  }
}

That’s great and all, but that means whenever we want to use this WeatherApi class in our code, we have to copy and paste the GuzzleHttp creation & configuration first right?

// our silly example:

$guzzle = new GuzzleHttp\Client([
    'base_uri' => 'https://api.openweathermap.org/data/2.5/weather',
]);

$api = new Forecaster($guzzle);

That’s just inefficent and not DRY. In the next section I’ll show you the final piece to understanding how to use the IoC Container to clean up this mess.

How to use an IoC Container to Leverage Dependency Injection

Now for the final piece - using a real IoC Container to abstract away the dependency injection required so we can unit test our PHP code.

Whew say that 5 times fast. Anyway, it’s a lot of terminology but I assure you it’s not as complicated as it sounds.

As you read before, the IoC Container is just a key => store container. You can think of it as an array of services. A service is just a piece of functionality that your application will use to perform some business logic.

In this example we’ll turn our WeatherApi into a service stored in a Container.

Let’s leverage the Container’s FactoryClosures which act identically to Laravel’s IoC service definitions.

// let's create our container:
$container = new League\Container\Container;

// now let's add a weather_api service that encapsulates how to create and configure a WeatherApi instance:
$container->add('forecaster', function() {
  $guzzle = new GuzzleHttp\Client([
      'base_uri' => 'https://api.openweathermap.org/data/2.5/weather',
  ]);

  $api = new Forecaster($guzzle);
});

// now we can retrieve our WeatherApi anywhere!
$forcaster = $container->get('forecaster');

$weather = $forecaster->weatherIn('Billings, MT');

IoC Containers in Ruby

Welp, I hate to break it to you. IoC Containers aren’t really practiced in Ruby. I tried searching and searching for a definitive reason why, it bothered me for the longest time.

Because of the rigidness of static languages like PHP and Java, the IoC is pivotal. Without it we we would have much more difficulty unit testing our code.

Let’s start with a Ruby-ified version of our first naive Forecaster implementation, without the Dependency Injection.

First major difference is that Ruby doesn’t have a GuzzleHttp gem persay. They have a few swell HTTP Clients. For simplicity I’ll use the rest-client gem.


require 'json'

class Forecaster
  attr_accessor :client

  def initialize
    @client = RestClient::Resource.new('https://api.openweathermap.org/data/2.5/weather')
  end

  def weather_in(city)
    response = @client.get(params: { 
                                     q: city, 
                                     appId: 'INSERT YOUR APP ID HERE'
                                   })

    JSON.parse(response)
  end
end

forecaster = Forecaster.new
weather = forecaster.weather_in('West Palm Beach, FL')

# re-encoding the JSON so we can see it in it's glorious string format
puts JSON.stringify(weather)

=> {"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":300,"main":"Drizzle","description":"light intensity drizzle","icon":"09d"}],"base":"stations","main":{"temp":280.32,"pressure":1012,"humidity":81,"temp_min":279.15,"temp_max":281.15},"visibility":10000,"wind":{"speed":4.1,"deg":80},"clouds":{"all":90},"dt":1485789600,"sys":{"type":1,"id":5091,"message":0.0103,"country":"GB","sunrise":1485762037,"sunset":1485794875},"id":2643743,"name":"London","cod":200} 

Voila, we have a simple version of the Weather API without an IoC Container performing Dependency Injection.

In Ruby, Everything is Testable

So finally we get to dive into the practical benefits of the flexibility Ruby gives us.

Remember in Ruby that we have the ability to override any public method? This special power includes the ability to override a constructor at whim. How does this help our situation?

Well, let’s take a look into the initialize function of our WeatherApi and how it sets up the rest-client instance:


# looking inside of the Forecaster class

def initialize
  @client = RestClient::Resource.new('https://api.openweathermap.org/data/2.5/weather')
end

In PHP, we were forced to pass the HTTP client as an argument to the constructor. But in Ruby we can just override that particular constructor:


# Creating a mock "double" which is essentially the same thing as a Mockery::mock() object
mock = double(RestClient::Resource)

# set the exceptation that the RestClient::Resource will be initialized and override the returning value
allow(RestClient::Resource).to(
  receive(:new).with('https://api.openweathermap.org/data/2.5/weather').and_return(mock))

Volia, we have mocked RestClient::Resource without any special Container needed.

This is a sneak peak of the testing section