<

[Last Call] Learn about multicloud storage options and how to improve your company's cloud strategy. Register Now

x

PHP Design - Avoiding Globals with Dependency Injection

Published on
20,333 Points
2,133 Views
2 Endorsements
Last Modified:
Introduction
This question got me thinking...

Why shouldn't we use Globals?
This is a simple question without a simple answer.  How do you explain these concepts to a programmer who is coming from a background in procedural programming? 

There is a lot of deep background thinking that you need to understand in order to know why globals are considered to be a poor design choice today.  Singletons are just as bad as globals in many ways.  Often procedural programmers have looked at the global keyword as an expedient solution - a global declaration made inside your functions provided a simple reference to an external database connection.  Tightly-coupled code sets were the norm in programming just a few years ago, and the global keyword implemented tight coupling in a way that irrevocably bound certain objects into the fabric of the application.   If you're working alone on a standalone project, you can sometimes get away with using global objects, but if you want to come into the modern world of application design, and especially if you want to collaborate on anything important, you need to understand why global elements are considered harmful, and what the alternatives offer.

Global Variables Break Encapsulation
Encapsulation is a good thing because it means that your functions and variable names cannot collide with other variables in the script.  Consider a script that has a function called fetch_object().  There can be only one such function name; an attempt to define a duplicate function name will cause a PHP parse error in the script.  Consider a script that has a variable named $x.  If you have two definitions for $x in the same script, PHP will not complain but your programming logic may find you using one value for $x when you think you have another value for $x.  These sorts of confusions create the stuff of nightmares for experienced developers.  To get around this problem of confused and conflicting names, we use encapsulation by wrapping our functions and variables inside of classes.  We call class member functions methods and class member variables properties.  With appropriate encapsulation we can have two programmers working on the same application at the same time and we can be sure that they will not step on each others toes by using duplicate names for functions and variables.  PHP has two good ways of producing encapsulation: Classes and Namespaces.  Global declarations break them both.

You Cannot Test Anything That is Global
Today, formalized testing is the norm in application development.  In fact, many of the best teachers of software development recommend building your tests before you build any of the software!  Testing is so important that most open-source projects require unit tests before you make a pull request.  So why can't we test global objects?  Let's look at some testing tools and see how they work.  Look up PHPUnit and Mockery, get an idea of their concepts, then come back to this article.

By "test", we mean you cannot substitute a mock object for a global variable, because if you substitute it in one place, you've also substituted it in every other place.  It's global, and that means it is a single instance that is available in every scope or namespace.  Now go back and consider the $db variable from the original question.  If you create the $db in the initialization code and pass it into your methods and functions, you can use automated test tools to create a mock object for the $db, and you can mock it in each place it is used separately without affecting the other code or functionality of the site.  Your script depends on the $db variable, so we call it a "dependency."  This is the correct way to think about dependencies -- if you must depend on something you want to be able to substitute something else for the dependency.  The substitution facilitates automated testing; it also makes your application design more flexible.

With the global, you must test on your live database (WTF?!), or you must use a mirror database.  With dependency injection you can isolate the $db on a case-by-case basis.

You Cannot Remove a Dependency on a Global
It's global, after all - how do you unset() a global without causing "global" disruption in other parts of your application?  It may be no problem if you're the only programmer on a project, at least not until you make a mistake in a global element of the application.  But if there is more than one programmer working on a project (and anything of value will have more than one programmer) the project is at risk.  If the programmers have come to depend on a global, any change to the global means all the programmers must rerun all of their tests and perhaps remediate their code.  They may even be forced to rewrite their tests. 

Why might you want to unset() or nullify a dependency?  That is how you close a PDO connection.

Class Members (Properties and Methods) Are Confusing Enough
But at least there is a hierarchy of visibility among class members, and even when inheritance or traits seems to be distracting, you can find your way to the class members and change them secure in the knowledge that you will not clobber something outside of your scope and namespace. 

Unfortunately, globals are all-or-nothing, and once your code is exposed to a global, you're at the mercy of every bit of programming that runs as a part of the app, whether you wrote it or not.  In other words, if you use my global, you have to trust my global, whether you like it or not.  And it is possible that I'm doing things with my global that you may never encounter until you experience a run time failure, because I did not initialize my global correctly or mutated my global as a result of a run-time condition you did not expect.  

In contrast, if you use your own class members, whether by direct reference or dependency injection, you do not have to trust my programming -- your work can be decoupled and tested separately, and changes in my work do not affect your work.

But What About PHP Superglobal Variables?
Yes, superglobal variables are every bit as bad as the global variables you create yourself.  They are an artifact of PHP's long history, and we can't get rid of them without disrupting the design of existing web applications.  In retrospect, it is apparent that the early authors of PHP did not know very much about computer science.  If they had, we would never have created  something as unfortunate as Register Globals.  There is one saving grace, however.  We know the superglobals by their names and where they live, so we can deal with them carefully.

But Dependency Injection is Complicated
We all have to deal with the question of "I have a lot of dependencies and it's hard to remember how to write my function calls because of all the things I have to inject into the functions."  That can be a real problem because PHP function arguments are positional parameters, so you have to remember them in sequential order.  If you have a class method that needs half-a-dozen dependencies (also called function arguments) it's easy to confuse the order.  So here's an article that teaches a design pattern to simplify this problem.  It lets you use that uses type hinting and a parameter object containing named parameters which can be easier to remember.  In this design the sequential order of your arguments does not matter.
http://www.experts-exchange.com/articles/18409/Using-Named-Parameters-in-PHP-Function-Calls.html

But Dependency Injection is Complicated
Not really.  Every time you write a function call, even something as simple as $x = substr($y,0,2) you are using dependency injection.  To assign the value of the $x variable, your script depends on $y.  You're injecting $y into the substr() function.  That's dependency injection.  That's all there is to it.

This is a very simplified code example, but it will serve to illustrate the essential moving parts of a database dependency injection, showing how the $mysqli database object is not used as a global, but is injected into the function.  Injection into object methods is exactly the same as injection into inline functions.  You just name the thing you want to inject among the variables that are listed in the function definition, and use the thing inside the function or method.  You've been doing this all along!

Notice in our definition of the run_a_query() function we use a type hint to tell PHP that the only acceptable argument for $db is a MySQLi object.  Little things like that can make our programming easier to read, understand, and debug.
<?php // demo/mysqli_di_example.php

/**
 * Demonstrate Dependency Injection with a MySQLi Object
 *
 * Man Page References:
 * http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration
 * http://php.net/manual/en/mysqli.overview.php
 * http://php.net/manual/en/class.mysqli.php
 * http://php.net/manual/en/class.mysqli-stmt.php
 * http://php.net/manual/en/class.mysqli-result.php
 * http://php.net/manual/en/class.mysqli-warning.php
 */

// RAISE THE ERROR REPORTING LEVEL TO THE HIGHEST POSSIBLE SETTING
ini_set('display_errors', TRUE);
error_reporting(E_ALL);
echo '<pre>';

// DATABASE CONNECTION AND SELECTION VARIABLES - GET THESE FROM YOUR HOSTING COMPANY
$db_host = "localhost"; // PROBABLY THIS IS OK
$db_name = "??";
$db_user = "??";
$db_word = "??";

// OPEN A CONNECTION TO THE DATA BASE SERVER AND SELECT THE DB
$mysqli = new mysqli($db_host, $db_user, $db_word, $db_name);

// DID THE CONNECT/SELECT WORK OR FAIL?
if ($mysqli->connect_errno)
{
    $err
    = "CONNECT FAIL: "
    . $mysqli->connect_errno
    . ' '
    . $mysqli->connect_error
    ;
    trigger_error($err, E_USER_ERROR);
}

// ACTIVATE THIS TO SHOW WHAT THE DB CONNECTION OBJECT LOOKS LIKE
// var_dump($mysqli);


// A FUNCTION OR METHOD THAT USES A TYPE HINT AND AN INJECTED DATABASE CONNECTION
function run_a_query(MySQLi $db, $query)
{
    if (!$res = $db->query($query))
    {
        $err
        = 'QUERY FAILURE:'
        . ' ERRNO: '
        . $mysqli->errno
        . ' ERROR: '
        . $mysqli->error
        . ' QUERY: '
        . $sql
        ;
        trigger_error($err, E_USER_ERROR);
    }
    $data = $res->fetch_object();
    return $data;
}


// USE THE FUNCTION SHOWING THE INJECTION OF THE DATABASE OBJECT
$set = run_a_query($mysqli, 'SELECT 2+2 AS the_answer');
var_dump($set);

Open in new window


What is a Singleton, Anyway?
A "singleton" refers to a class that can only be used to instantiate one object.  In our code example just above, we assigned the $mysqli variable to the database connection object, but we could just as easily have created other connection objects and assigned the other connections to other variables.  We might do this if we were using different databases at the same time.

With a singleton, we get the same problem we get with a global object.  Since you can't have more than one instance, you cannot create mock versions for testing.  The singleton design pattern is worth understanding since it is the 21st-century, object-oriented version of a global, and therefore is to be avoided. 

This code snippet shows a singleton database connection object.  By marking the properties and methods final and private we ensure that no unauthorized access or extension can occur.  We nullify the clone() and wakeup() methods to ensure that unwanted copies of the object cannot be created.  There is only one public method, getConnection(), and it must be called statically.  It is self-aware and will only return its own single database connection, hence the term, "singleton."

<?php // demo/oop_database_singleton.php

/**
 * A PHP example of a database class that illustrates the Singleton design pattern
 *
 * http://php.net/manual/en/language.oop5.php
 * http://php.net/manual/en/language.oop5.object-comparison.php
 */
error_reporting(E_ALL);


// SINGLETON DATA BASE CONNECTION CLASS
class Database
{
    // CLASS PROPERTIES ARE ALL PRIVATE
    private static $connection;
    private static $instance;

    // PUT YOUR CONNECTION VALUES HERE
    const DB_HOST = 'localhost';
    const DB_USER = '??';
    const DB_PASS = '??';
    const DB_NAME = '??';

    // NULLIFY THE CLONE AND THE WAKEUP
    final private function __clone() {}
    final private function __wakeup() {}

    // OUR ONLY PUBLIC METHOD RETURNS THE CONNECTION
    final public static function getConnection()
    {
        if (!self::$instance) self::$instance = new self();
        return self::$connection;
    }

    // CONSTRUCTOR CREATES THE CONNECTION
    final private function __construct()
    {
        self::$connection
        = new mysqli
        ( self::DB_HOST
        , self::DB_USER
        , self::DB_PASS
        , self::DB_NAME
        )
        ;
        if (self::$connection->connect_error)
        {
            trigger_error(self::$connection->connect_error, E_USER_ERROR);
        }
    }
}


// SHOW WHAT HAPPENS WHEN YOU TRY TO MAKE TWO CONNECTIONS
$dbc_1 = database::getConnection();
$dbc_2 = database::getConnection();


// PROVE THAT THESE VARIABLES POINT TO THE SAME OBJECT
if ($dbc_1 === $dbc_2) echo 'EQUAL! ';

// SHOW THE OBJECT
echo '<pre>';
var_dump($dbc_2);

Open in new window


Summary
As software development techniques mature we are better able to collaborate on projects.  We have learned about dependency injection and why it is wise to avoid global objects.  We have seen examples that show better, more flexible ways of accomplishing what we used to do with the global keyword.  We have seen how a singleton object has the same issues as a global variable.  We have learned a bit about the importance and techniques for unit testing.

Further reading on modern design patterns:
http://www.experts-exchange.com/articles/18329/SOLID-Design-in-PHP-Applications.html

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
2
Comment
Author:Ray Paseur
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 4
9 Comments
 
LVL 32

Expert Comment

by:DrDamnit
@Ray:

One way I have gotten around the use of globals is to implement a function that contains the data I need, whcih is returned on command. The most notable (and often used) example in my code is:

function gimmieDB() {
    $host = 'localhost';
    $user = 'foo';
    $pass = 'bar';
    $db = 'baz';
     $x = new mysqli($host,$user,$pass,$db);
    return $x;
}

Open in new window


This allows me to get a database connection from anywhere (inside functions, classes, anywhere).

Would you call this a dependency injection?
0
 
LVL 111

Author Comment

by:Ray Paseur
The gimmeDB() function will return a new instance of the MySQLI connection object each time it is called.  It's a convenience, but does not represent a dependency injection, since each new instance is still dependent on the same MySQLI database.  The database connection is created inside the function, not injected into the function.  That means that all database actions using the return from gimmeDB() are going to act on the same database.

One of the key value propositions in dependency injection is the ability to substitute a mock object in place of the dependency.  This means you can do automated testing, and your tests would run against a mockup of your database, not the "live" database.

A recognizable signature of a dependency injection is an input argument inside the parentheses of the function definition, for example, $x in this line of code.  $x is a "dependency" because the PHP function depends on $x to formulate the contents of $y.  $x is an "injection" because it is injected into the function call, rather than being created inside the function.
$y = someFunction($x);

Open in new window

0
 
LVL 32

Expert Comment

by:DrDamnit
Having given this some thought, is there any difference between "dependency injection" and "passing a (required) object as an argument"?

It would appear not? Just semantics?
0
Plesk WordPress Toolkit

Plesk's WordPress Toolkit allows server administrators, resellers and customers to manage their WordPress instances, enabling a variety of development workflows for WordPress admins of all skill levels, from beginners to pros.

See why 2/3 of Plesk servers use it.

 
LVL 111

Author Comment

by:Ray Paseur
Not even a difference of semantics - these are equivalent concepts.  Here is the wrong way.  It is wrong because you cannot just swap out the MySQLi connection.
function db($sql){
$conn = new MySQLi(...);
// etc, etc

Open in new window

But if you use the MySQLi connection as an argument (aka parameter), you can substitute a mock object and this means you can test each call to the db() function independently.
0
 
LVL 32

Expert Comment

by:DrDamnit
OK. So far, I am all for it ... mostly because it's something I use a lot - but I didn't know there was this much too it.

But here's something else I am curious about dependency injection:

Let's say I have three classes that each abstract a different table of a database. We'll call them: Contact, Address, and Phone.

Now, because Contact can have more than one address, the Contact class has a property called addresses, which is an array of Address classes.

This allows you to access all a Contact's addresses with a loop:
<pseudocode>
foreach(Contact:addresses as Address) {
    echo Address::getCity();
}
</pseudocode>

Open in new window


Should I be passing the $db object through each object and sub-object all the way to the bottom? Regardless of how many levels there are?

Take for example Contact::addAddress(), which would instantiate a new Address() class, and thus need access to a MySQL object:


$c = new Contact();
$c->addAddress('123 Happy Street','Atlanta', 'GA', '30329');

Open in new window


the Contact class would have this:

class Contact {
    var $db        = NULL;
    var $addresses = array();

    function __construct(&$db) {
        $this->db = $db;
    }
        

    function addAddress($street,$city,$state,$zip) {
        $a = new Address($this->db);
        $a->setStreet = $street;
        $a->City   = $city;
        $a->State  = $state;
        $a->Zip    = $zip;

        //save to the database
        $a->save();
        // Add the new Address object to this contact's addresses.
        array_push($this->addresses,$a);
    }
}

Open in new window


But what now if I have a Zipcode object that does specialized work on Zipcode information in a database (like calculating the distance between one Zipcode and another using the great cirlce route in the same manner DateTime::diff() works?)

Now, my funciton looks like this:
class Contact {
    var $db        = NULL;
    var $addresses = array();

    function __construct(&$db) {
        $this->db = $db;
    }

    function addAddress($street,$city,$state,$zip) {
        $a = new Address($this->db);
        $a->Street = $street;
        $a->City   = $city;
        $a->State  = $state;

        $z = new ZipCode($this->db);
        $z->loadFromZipCode($zip);

        // Sets the zipcode to the ID of the zipcode in the database.
        $a->setZip($z);

        //save to the database
        $a->save();
        // Add the new Address object to this contact's addresses.
        array_push($this->addresses,$a);
    }
}

Open in new window

Is this how dependency injection is supposed to work?

In the same vein, what about a Config class that holds configuration variables? Suppose I have a class within a class within a class, and only the one at the bottom level requires the information inthe config object. With dependency injection, I have to pass the Config instance through two classes that don't use it just so I can use it at the bottom. This is not very different than passing a return value up those same three classes, but it seems to be inelegant.

Thoughts?
0
 
LVL 111

Author Comment

by:Ray Paseur
passing the $db object through each object and sub-object all the way to the bottom?
Maybe there is a different design idea that would be helpful.  In the SOLID design principles, the "S" stands for Separation of Concerns or Single Responsibility Principle.
http://www.experts-exchange.com/articles/18329/SOLID-Design-in-PHP-Applications.html

This suggests to me that a flatter design with more, smaller classes is probably better than a deep design.  One of the ideas I find useful is the CRC Card.  If you pass the database object into a class that subsequently passes it into another (and so on, deeper and deeper) you may be creating a design that cannot be tested effectively.  If each of the classes is decoupled from the others, then you can mock the database object selectively.
0
 
LVL 32

Expert Comment

by:DrDamnit
I must be missing something here.

Having a Contact, Phone, and Address class, which are independent abstractions of the database, and which use interfaces follows the principles of SOLID. Loading up your addresses as classes in the Contact class services a convenience function. (Like being able to loop through all a contact's addresses and access methods within those addresses... or, to take my example further, load the contact, his addresses, and their corresponding zip codes so I can get the distance between a contact's different addresses in a great circle route).

This all works, and because the classes themselves are indepdendent, it can (should) survive changes.The method of the Contacts class that specifically deals with looping through Addresses does, indeed, need access to the Address class, but as long as interfaces are defined, it shouldn't care how we come to the result.

As you state in your article: houses come with sockets.

But, what I am not getting is how do you resolve something like the latter example I gave? I Config class that is loaded when the script starts, but isn't used  until it is passed three levels deep? Level 1 and level 2 don't access it or don't care about it, but must manage it because level 3 needs it.

That gives rise to the irritation that you have to pass the config even when you are not going to go three levels deep. You end up having to pass the config over and over even when it's not needed - simply because it might be needed.

I can either use getConfig() in the same manner I use gimmieDB(), but that isn't dependency injection. It solves the problem, and occupies very little of the symbol table since it's just strings and integers. It also makes it indepdendent, and appears to follow SOLID principles.

Am I just way off base here?
0
 
LVL 111

Author Comment

by:Ray Paseur
I may have to sign off on this because I'm not following the part about passing an object three levels deep.  It looks like the $db variable is injected into the constructor of the Contact class and used in its methods.  That seems fine to me.  The granularity level for testing is the Contact class, since the $db is the same throughout all the methods.

A "config" class sounds like a singleton to me - similar to a global, but I would have to read the code that sets it up and uses it.  Maybe a consulting agreement would make sense here?
0
 
LVL 32

Expert Comment

by:DrDamnit
No, I'm satisfied with getConfig(). I was just trying to understand if there was a better way. But your feedback is appreciated.
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Join & Write a Comment

This tutorial will teach you the core code needed to finalize the addition of a watermark to your image. The viewer will use a small PHP class to learn and create a watermark.
Learn how to create flexible layouts using relative units in CSS.  New relative units added in CSS3 include vw(viewports width), vh(viewports height), vmin(minimum of viewports height and width), and vmax (maximum of viewports height and width).
Suggested Courses

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month