Pragmatism in the real world

Dependency Injection with OpenWhisk PHP

Any non-trivial PHP applications use various components to do its work, from PDO though to classes from Packagist. It’s fairly common in a standard PHP application to use Dependency Injection to configure and load these classes when necessary. How do we do this in a serverless environment such as OpenWhisk?

This question comes up because we do not have a single entry point into our application, instead we have one entry point per action. If we’re using Serverless to write an API, then we probably have a set of actions for reading, creating, updating and deleting resources all within the same project.

Consider a project that uses PDO to communicate with the database. The PDO object will need to be instantiated with a DSN string containing the host name, database, credentials etc. Its likely that these will be stored in the parameters array that’s passed to the action and set up either as package parameters or service bindings if you’re using IBM Cloud Functions.

For this example, I have an ElephantSQL database set up in IBM ‘s OpenWhisk service and I used Lorna Mitchell’s rather helpful Bind Services to OpenWhisk Packages article to make the credentials available to my OpenWhisk actions.

Using a PDO instance within an action

Consider this action which return a list of todo items from the database. Firstly we instantiate and configure the PDO instance and then create a mapper object that can fetch the todo items:

function main(array $args) : array
{
    if (!isset($args['__bx_creds']['elephantsql']['uri'])) {
        throw new Exception("ElephantSQL instance has not been bound");
    }
    $credentials = parse_url($args['__bx_creds']['elephantsql']['uri']);

    $host = $credentials['host'];
    $port = $credentials['port'];
    $dbName = trim($credentials['path'], '/');
    $user = $credentials['user'];
    $password = $credentials['pass'];

    $dsn = "pgsql:host=$host;port=$port;dbname=$dbName;user=$user;password=$password";

    $pdo = new PDO($dsn);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // now we can use $pdo to interact with our PostgreSQL database via a mapper
    $mapper = new TodoMapper($pdo);
    $todos = $mapper->fetchAll();

    return [
        'statusCode' => 200,
        'body' => $todos, 
    ]; 
}

That’s quite a lot of set up code that clearly doesn’t belong here, especially as we need to do the same thing in every action in the project that connects to the database. We are also going to probably put our database access code in a mapper class that takes the PDO instance as a dependency, so to my mind, it makes sense to use a DI container in our project.

I chose to use the Pimple DI container, because it’s nice, simple and fast.

To use it, I extended Pimple\Container and added my factory to the constructor:

<?php 
namespace App;

use InvalidArgumentException;
use PDO;
use Pimple\Container;

class AppContainer extends Container
{
    public function __construct(array $args)
    {
        if (!isset($args['__bx_creds']['elephantsql']['uri'])) {
            throw new InvalidArgumentException("ElephantSQL instance has not been bound");
        }
        $credentials = parse_url($args['__bx_creds']['elephantsql']['uri']);

        /**
         * Factory to create a PDO instance
         */
        $configuration[PDO::class] = function (Container $c) use ($credentials) {
            $host = $credentials['host'];
            $port = $credentials['port'];
            $dbName = trim($credentials['path'], '/');
            $user = $credentials['user'];
            $password = $credentials['pass'];

            $dsn = "pgsql:host=$host;port=$port;dbname=$dbName;user=$user;password=$password";

            $pdo = new PDO($dsn);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            return $pdo;
        };

        /**
         * Factory to create a TodoMapper instance
         */
        $configuration[TodoMapper::class] = function (Container $c) : TodoMapper {
            return new TodoMapper($c[PDO::class]);
        };

        parent::__construct($configuration);
    }

}

In this code, we create two factories: one to create the PDO instance and one to create a mapper class called TodoMapper. The TodoMapper class composes a PDO instance via its constructor, so in the factory for the TodoMapper, we retrieve the PDO instance from the container.

This nice thing about doing it this way is that if an action doesn’t use a TodoMapper, then the connection to the database doesn’t get made as it’s not needed.

Using the DIC in an action

The code in our action gets much cleaner and easier to read now:

function main(array $args) : array
{
    $container = new App\AppContainer($args);

    $mapper = $container[App\TodoMapper::class];
    $todos = $mapper->fetchAll();

    return [
        'statusCode' => 200,
        'body' => $todos, 
    ]; 
}

In fact, it now looks much more like a controller action that you’d see in a standard PHP framework-based app.

Summary

Even in serverless applications, a clean software design is required. All the code does not belong in the action’s entry function and should be separated appropriately. Using a Dependency Injection container is one way to handle this and helps ensure that your serverless application can be easily maintained.