Lesson 4 - Wrong ways of passing dependencies - Static members

Software design Software architectures and dependency injection Wrong ways of passing dependencies - Static members

In the previous lesson, Three-tier architecture and other multi-tier architectures, we explained the three-tier architecture and how to divide our applications into components of 3 types in order to achieve maximum clarity and reusability. The components of our sample car application depend on the database. We passed the database manually through constructors. Even in such a short example, it was obvious that it was a lot of extra work. So how do we get dependencies, in our case, the connected database, into models?

Ways to pass dependencies

Once you start programming the object-oriented way, it's only a matter of time until you need to call a functionality beyond the responsibility of the current object. In other words, the current object won't have the function and that's correct. That's why you call it on another object that is responsible for this area of the application. We've already said that in the introductory lesson. The catch is, of course, that the other object can have a state and other dependencies and that's why we don't want or even can't create it directly. It's been already created and configured somewhere else and we need to get it from there. The database is a simple example. We set and connect its instance at the start and then we want all the work with the database to be done using this one instance.

Maybe you won't be surprised that programmers have thought up several ways over the years to pass dependencies. I allowed myself to categorize them into the following groups:

Wrong ways

  • Not passing dependencies at all
  • Static members
  • Singleton
  • Service locator
  • Variations of the above (locator is a Singleton and so on)

Labor-intensive ways

  • Passing dependencies manually

Right ways

  • IoC (Dependency Injection)
  • DI automation

We'll try each way during the course and find out what's wrong with it. Finally, we'll understand the Inverse of Control principle and the Dependency Injection pattern.

Dependencies - Doing it wrong

Wrong solutions are simple and that's why they're still there, even though they have been known as not ideal for a long time.

Mistake no.1 - Not passing dependencies at all

The first wrong solution is to not pass dependencies at all. Once we need a dependency, we just create its instance again. If we need 10 different managers per request and each of them needs a database, we create a new database instance every time in each manager again, i.e. 10 databases in total, establishing 10 separated database connections. It's clear that this method is not even usable in real apps and is rather a deterrent example. For the sake of completeness, here's an example.

Models/CarMana­ger.php

Does the model need the database? It'll create a new PDO instance and connect it.

class CarManager
{
    private $database;

    public function __construct()
    {
        $this->database = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
    }

    public function getCars()
    {
        return $this->database->query("SELECT * FROM cars")->fetchAll(PDO::FETCH_ASSOC);
    }

}

Controllers/Car­Controller.php

class CarController
{
    public function all()
    {
        $carManager = new CarManager();
        $cars = $carManager->getCars(); // A template variable
        require('Templates/cars.phtml'); // Loads the template
    }

    // Other actions like one($id), remove($id), ...
}

Note that the CarManager no longer requires the database in the constructor because it creates a new one itself.

We could "improve" the approach by creating the database in the controllers and then passing it to models manually, which would be a hybrid between this approach and a manual dependency passing. Having the sensitive database connection credentials in multiple places is another disadvantage of this approach.

Approach problems

Creating new instances again and again is:

  • a performance issue
  • a memory issue

We have more objects than we need. In the future, we might set some data to an object and then expect them to be in a completely different instance of the same class elsewhere. The data wouldn't be there, of course, because it would be a different object.

We simply want to share dependencies, we need only one instance of each dependency and we need to access it from different parts of the application.

Mistake no.2 - Static members

Using static members to pass dependencies is probably the best of the wrong solutions. Static members are also the basis of Singleton about which we are going to talk about in the next lesson. If you have absolved local courses, you know that I use static members for these purposes in them. Writing your own DI container is a job for expert programmers and using complex third-party frameworks right from the start is not a great choice as well. We have to start with something, but, at the same time, we shouldn't stick with static members for too long.

We know that static attributes belong to the class, not an instance. And classes are is globally visible. So if we store something in static attributes, we can always access it from anywhere we want. Although it's not exactly what we need, we'll get to the static dependencies from our models. There are many ways how to pass dependencies using static members. The best implementation is probably:

  • Creating new managers every time
  • Once a manager needs a dependency, it'll access another class statically. We can write key dependencies, such as the database, as static classes, we'll show it in a moment. Minor dependencies, e.g. the instance of the currently logged user, can be stored in a static attribute, e.g. in the UserManager class, so that it's also accessible to other classes.

Models/Database.php

We'll create a static wrapper for the PDO PHP database class. That's how we can use it anywhere freely and share only 1 connection. The source code of the class would look like this:

class Database
{
    private static $pdo;

    public static connect($db, $name, $password)
    {
        self::$pdo = new PDO("mysql:host=localhost;dbname=$db;charset=utf8", $name, $password);
    }

    public static function queryAll($query)// We won't deal with parameters to keep things simple
    {
        return self::$pdo->query($query)->fetchAll(PDO::FETCH_ASSOC);
    }
}

Note that all the class methods and attributes representing key dependencies are static. So we wouldn't need an instance at all won't have to pass it.

index.php

We should connect the database somewhere at the beginning of the app, in index.php:

// Autoloader
// ...


Db::connect('testdb', 'root', '');

// Calling the router that instantiates the appropriate controller
// ...

The connected PDO instance is stored in a static attribute on the Database class.

Models/CarMana­ger.php

Now we can call the Database::queryAll() method from anywhere. It wraps the query() method on the connected PDO instance:

class CarManager
{

    public function getCars()
    {
        return Database::queryAll("SELECT * FROM cars");
    }

}

The CarManager model can access the database.

Controllers/Car­Controller.php

And what about the controller? There again, we don't have to deal with anything. We'll just create a new CarManager instance.

class CarController
{
    public function all()
    {
        $carManager = new CarManager();
        $cars = $carManager->getCars(); // A template variable
        require('Templates/cars.phtml'); // Loads the template
    }
}

Through the whole application, we share a single connected PDO database instance thanks to the fact that we've wrapped it with the static Database class that has a static attribute.

Approach problems

What's wrong then?

  • The database is accessible from anywhere, i.e. from controllers, but also, for example, from templates! From any PHP file that does not even need it. This is not very safe and it leads to poor class design and Low Coupling pattern violations.
  • The database will be difficult to mock (to replace it with testing data), which will make testing uncomfortable when the application becomes larger.
  • The application "lies" about its dependencies, at first sight, it's not obvious which classes are used by which classes. To find out, we need to go through the entire source code and look for static accesses.
  • A big problem might be using static members in multi-threaded applications. With PHP, you most likely won't get into this situation, but in other languages, ​​you'll enter race condition.

Static members are remarkably close to global variables which are anti-patterns, and modern languages don't even support them anymore. To avoid misunderstanding, let me repeat that we use static members to share dependencies here in our courses because it's simple. In the more advanced courses, we work with frameworks that solve this issue better.

That's all for today's lesson. Next time, in the lesson Wrong ways of passing dependencies - Singleton and SL, we'll show the problems of Singleton and Service Locator, completing the wrong approaches of passing dependencies. We'll be able to get to DI :)


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Activities (5)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!