Configuration Values Are Dependencies, Too

As part of my consulting work, I get the opportunity to review lots of different codebases of varying modernity. One thing I’ve noticed with some otherwise-modern codebases is that they often “reach out” from inside a class to retrieve configuration values, instead of injecting those values into the class from the outside. That is, they use an equivalent of globals or service-location to read configuration, instead of using dependency injection.

Here is one generic example:

<?php
class Db
{
    // backend type, hostname, username, password, and database name
    protected $type, $host, $user, $pass, $name;

    public function __construct()
    {
        $this->type = getenv('DB_TYPE');
        $this->host = getenv('DB_HOST');
        $this->user = getenv('DB_USER');
        $this->pass = getenv('DB_PASS');
        $this->name = getenv('DB_NAME');
    }

    public function newConnection()
    {
        return new PDO(
            "{$this->type}:host={$this->host};dbname={$this->name}",
            $this->user,
            $this->pass
        );
    }
}
?>

Granted, the example follows the modern practice of keeping sensitive information as environment variables. Similar examples use $_ENV or $_SERVER keys instead of getenv(). The effect, though, is global-ish or service-locator-ish in nature: the class is reaching outside its own scope to retrieve values it needs for its own operation. Likewise, one cannot tell from the outside the class what configuration values it depends on.

Is the following any better?

<?php
class Db
{
    public function __construct()
    {
        $this->type = Config::get('db.type');
        $this->host = Config::get('db.host');
        $this->user = Config::get('db.user');
        $this->pass = Config::get('db.pass');
        $this->name = Config::get('db.name');
    }
}
?>

As far as I can tell, that’s a variation on the same theme. The generic Config object acts as a global singleton to carry configuration for every possible need; it is acting as a static service locator. While service location is inversion-of-control, it is in many ways inferior to dependency injection. As before, the class is reaching outside its own scope to retrieve values it depends on.

What if we inject the generic Config object like this?

<?php
class Db
{
    public function __construct(Config $config)
    {
        $this->type = $config->get('db.type');
        $this->host = $config->get('db.host');
        $this->user = $config->get('db.user');
        $this->pass = $config->get('db.pass');
        $this->name = $config->get('db.name');
    }
}
?>

This is a little better; at least now we can tell that the Db class needs configuration of some sort, though we still cannot tell exactly which values it needs. This is the same as injecting a service locator.

Having seen all these examples, and other similar ones, in real codebases, I conclude that configuration values should be treated as any other dependency, and injected via the constructor. I suggest this approach:

<?php
class Db
{
    public function __construct($type, $host, $user, $pass, $name)
    {
        $this->type = $type;
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->name = $name;
    }
}
?>

Simple, clear, obvious, and easy to test. If you use a dependency injection container of some sort, it should be trivial to have it read environment variables and pass them to the Db class at construction time. (If your DI container does not support that kind of thing, you may wish to consider using a more powerful container system.)

Alternatively, I think the following may be reasonable in some cases:

<?php
class DbConfig
{
    // backend type, hostname, username, password, and database name
    protected $type, $host, $user, $pass, $name;

    public function __construct($type, $host, $user, $pass, $name)
    {
        $this->type = $type;
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->name = $name;
    }

    public function getDsn()
    {
        return "{$this->type}:host={$this->host};dbname={$this->name}";
    }

    public function getUser()
    {
        return $this->user;
    }

    public function getPass()
    {
        return $this->pass;
    }
}

class Db
{
    protected $dbConfig;

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

    public function newConnection()
    {
        return new PDO(
            $this->dbConfig->getDsn(),
            $this->dbConfig->getUser(),
            $this->dbConfig->getPass()
        );
    }
}
?>

In that example, the DbConfig manages a set of injected configuration values so that the Db object treats its own configuration as a separate concern. However, that approach is just a little too indirect and open-to-abuse for my taste most of the time. The temptation is to start putting more and more inside the DbConfig object, and you end up with a mini-service-locator.

To sum up: Configuration values are dependencies; therefore, inject configuration values the way you would any other dependency.

UPDATE: Stephan Hochdörfer notes on Twitter: “I would probably re-phrase a bit: Configuration values should be treated like deps. Not sure if u can say that they are deps ;).” The point is well-taken, though it may be a distinction without a difference. If the class cannot operate properly without a particular value, whether that value is a scalar or an object, I think it’s fair to say the class is dependent on that value.

Are you stuck with a legacy PHP application? Subscribe to "Modernizing Legacy Applications in PHP" for tips, tools, and techniques that can help you improve your codebase and your work life!

Share This!Share on Google+Share on FacebookTweet about this on TwitterShare on RedditShare on LinkedIn

9 thoughts on “Configuration Values Are Dependencies, Too

  1. […] As part of my consulting work, I get the opportunity to review lots of different codebases of varying modernity. One thing I’ve noticed with some otherwise-modern codebases is that they often “reach out” from inside a class to retrieve configuration values, instead of injecting those values into the class from …read more […]

  2. Great post!
    I’ve always though about this depency too, but I tend to think that sometimes I keep over-engineering too much.

    As always, this will improve code maintenance and testing.

  3. I’ve been wanting to point this out myself, it’s great that you put this idea out there!

    Taken more generally, I would say that value types can also be dependencies – I think most people automatically think “objects” when they think about dependencies, we just have to broaden our minds a little.

    Taking the idea a bit further, in our product, we decided that anything that requires configuration, requires a configuration object as a constructor argument. This has panned out very well. For one, it refactors better when you have to add another configuration property.

    Taking the full consequence of that, we ditched popular configuration file formats (JSON, YAML, etc.) in favor of a plain PHP file, which gets exposed to our DI container, and is expected to inject configuration objects into it.

    This works out very nicely in practice. Like for one, there is IDE support for PHP configuration files, so you immediately know what is required for a proper DB configuration, etc. – and it removes the overhead and complexity of a configuration parser and loader. It simplifies testing as well. And it eases the learning curve – if you understand DI, and the DI container, you understand everything. Having the DI container as the true lowest layer, with nothing below it, eliminates a lot of questions and complexity.

Leave a Reply

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