PHP Dependency Injection – Injecting the Same Database Connection into Multiple Objects

dependency-injectiondesign-patternsobject-orientedPHP

Suppose that there are two classes that define objects of vastly different function such that in the datastore, the information they require is divided into two separate databases.

For example, the classes Employee and SteelExtruder may be placed in the databases `human_resources` and `capital` respectively. Rather than hard code the connections to their respective databases:

class Employee {
    protected $dbc;
    public function __construct() {
        $this->dbc = new mysqli('localhost', 'user', 'pass', 'human_resources');
    }
}

I can see the advantages in flexibility that come from injecting the database connection into the constructor:

class Employee {
    protected $dbc;
    public function __construct(mysqli $dbc) {
        $this->dbc = $dbc;
    }
}

However, I fail to see how to implement it properly. I have read that singletons and database class libraries are bad practice, since they merely mask the fact that they are really just global variables. On the other hand, the alternatives do not seem fantastic either:

A) Declare any necessary connections globally in a loader file. Functionally identical to a database library class, but smells worse:

loader.php:
$dbc_human_resources = new mysqli('localhost', 'user', 'pass', 'human_resources');
$dbc_capital = new mysqli('localhost', 'user', 'pass', 'capital');

index.php:
$employees = array(
    new Employee($dbc_human_resources),
    new Employee($dbc_human_resources),
    ...
    );

B) Create a new connection every time a new object is created. Unless there is some sort of optimization I am not aware of, this seems carelessly inefficient.

$employees = array(
    new Employee(new mysqli('localhost', 'user', 'pass', 'human_resources')),
    new Employee(new mysqli('localhost', 'user', 'pass', 'human_resources')),
    ...
);

C) Store all the objects into a library object that takes a single connection, which doesn't look so bad but feels wrong:

Employee.php:
class Employee {
    //non-database properties
    public function __construct($args = array()) {
        //non-database initialization
    }
}

EmployeeLibrary.php:
class EmployeeLibrary {
    protected $dbc, $employees;
    public function __construct(mysqli $dbc) {
        $this->dbc = $dbc;
    }
    public function add_new_employee($args) {
        array_push($this->employees, new Employee($args));
    }
    //do things with database connection
}

loader.php:
$employee_library = new EmployeeLibrary(new mysqli('localhost', 'user', 'pass', 'human_resources');

index.php:
$employee_library->add_new_employee(...);
$employee_library->add_new_employee(...);

I am sure this isn't a rare problem. Is there an accepted way to do this?

Best Answer

All three are wrong, because you are storing connection strings in source code. Source code is not the right place for configuration, because you are not expected to have to change your code (and so, do all the regression testing) every time your database moves or every time you move from development database to staging and to production database.

Instead:

  • Store the connection string in a configuration file.

  • Create a class which accesses configuration file options.

    The class should provide the callers with the simple interface to those options; callers shouldn't care where the configuration file is or how options are stored inside.

    This abstraction makes it possible later to switch from, for instance, a configuration stored in a JSON file, to a configuration stored in an XML file, or even move configuration to a database such as Redis.

  • Create an instance of this class every time you need to access the configuration options.

    You don't need a singleton here; if one day, performance becomes an issue (that is, the time needed to open and parse the configuration file becomes prohibitive), use caching.

    If you are absolutely sure (beware of YAGNI) that you'll have to move to a different storage mechanism soon (for example from JSON file to Redis), you may use Dependency Injection by creating the configuration object at the top of the stack, and then pass it to the callers. This will ensure that when moving to a different mechanism, you'll change no more than one line in your code base, replacing:

    $configuration = new JsonFileAppConfiguration();
    

    by:

    $configuration = new RedisAppConfiguration();
    

    but everything else will rely only on the interface IAppConfiguration that both those classes implement.

Related Topic