3

Many times I heard to avoid static classes because they will insert dependencies that will render your code unusable in other projects, and will not allow to unit test it.

Let's say we have a typical class DB to access the Data Base, if such class is static we could call it wherever in our code:

DB::execQuery(...);

but this creates dependencies, so let's make the DB class NOT static, in such case we would have somewhere in our code:

$db = new DB();

and then we could call in our code

$db->execQuery(...);

But now when using the $db inside a function we need each time to first declare it like this

global $db;

Is there a way to workaround this?

One way could be to inject the $db object in the class that uses it, but I would have to inject it in all classes that use it, that's ridicolous, a static class would be much quicker to work with and less code to write. Am I missing something?!

Community
  • 1
  • 1
Marco Demaio
  • 33,578
  • 33
  • 128
  • 159

2 Answers2

5

$db could be injected upon instantiation into a property, then you would only need to access this property instead of passing it around to each method.

class MyClass {
  protected $_db; // DB Connection
  public function __construct($db) {
    $this->_db = $db;
  }

  public function foo() {
    $this->_db->query('...');
  }

}

Beyond that, you can look into having a service-container (aka dependency-injection container) that trys to act like a global variable but solves some of the testing issues. Take a look at some of these related questions

Having a DI container lets you use static methods in your classes like DI_Container::get('db'). It looks a lot like global or some of the other static calls.. but in this case DI_Container contains special methods that allow for extra actions to be taken during testing and other circumstances.. eliminating some of the 'evilness' of global.

Community
  • 1
  • 1
Mike B
  • 31,886
  • 13
  • 87
  • 111
  • 1
    Evilness of global. Hmm. I can override my global variables in testing as well. It's probably a bit better to replace many static functions and global variables with one, but I have the feeling it's not the right healing of the disease. – hakre May 07 '12 at 17:22
  • @Mike B: well if I have to call `ServiceContainer::$db->execQuery(...)`, I'd rather prefer to go on calling `DB::execQuery(...)` it's shorter. – Marco Demaio May 07 '12 at 17:42
  • @MarcoDemaio With the service container you have the option to swap out an instance of `DB` with a testable version before the test executes. Without the container, you'd have to have this logic in the `DB` class which can lead to a high cyclomatic complexity score. I'm not saying DI is the best answer for your situation, just one of the tools developer's sometimes reach for in this case. All frameworks deal with this on some level.. everything needs access to db, config, registry objects at all layers of code - There's not a end-all method of dealing with objects in your global namespace. – Mike B May 07 '12 at 19:05
  • @MikeB: i agree and I understand. I was hoping there was another way, but apparently there is not. – Marco Demaio May 08 '12 at 12:48
1

In addition to Mike B's answer, I would point that the wrong design in your code is : « we could call it wherever in our code ».

Actually, database should only be used by your Model, or the small part of your application that has to know about the database. So these classes should know there is a database, and use it as a dependency (passed through the constructor as Mike B said).

But the rest of your application should not care about a database, its should only care about the Model. Focus on refactoring and gathering all the code that access the database into Model classes.

This way, your application will have a Model layer that has a dependency : the database object/connection. And the rest of your application will use the Model, whatever happens in the Model in none of the Controller/View business.

Enjoy refactoring.

Thibault
  • 1,566
  • 15
  • 22