Many Related Questions About DI and Integrating DI libraries


#1

I do not know a lot about DI or DI containers and their applications or theories, but I am trying to integrate and use it because I have the need for a single instance (singleton) of certain classes (namely PDO) and understand it is best to avoid doing that and instead use DI.

I use DI elsewhere (without a container and through simple construction injection) but doing this with a class that should behave like a singleton is just extraordinarily tedious, and a singleton’s appeal is becoming hard to deny here because I am constantly passing the pseudo-singleton through various class for no reason other than to pass it to the actual class that needs it, which can be several levels down.

But I can’t seem to figure out how to integrate a DI library into an existing MVC app and also figure out how it actually has clear advantages.

The example I am having difficulty with is with a typical MVC app, where a controller calls a model, but the model doesn't require the DB, but some of the classes the model calls do require the DB.

class DBPassThroughClass
{
	protected $db;
	
	public function __construct(PDO $db)
	{
		$this->db = $db;
	}

	public function doSomething()
	{
		// often there are many "pass through" classes before I actually get to a class that will require the DB
$AClassThatActuallyRequiresTheDB = new AClassThatActuallyRequiresTheDB($this->db);
	}
}


class Controller
{
	protected $db;

        public function __construct(PDO $db)
	{
		$this->$db = $db;


        }

       public function doControllerLogic()
      {
         // do some controller stuff
        
        // call the pass through class and pass in the DB
        $dbPassThroughClass = new DBPassThroughClass($this->db);    
     }
}

With just normal DI I would be passing the DB connection on down from the controller into the model and into anything else it calls. This is pretty tedious, but I can understand reasons for doing it. So it seems like I am passing the DB through countless classes only to pass it again to the next class, whereas there may be a chain of classes (very often) that do not have anything to do with the DB.

(A separate concern is that the constructor is going to sort of explode with basic class arguments like needing to pass a database, flash messages, logger(s) and more around.)

I could instantiate the class right before I call any class that requires the DB, but there are thousands of instances of this, and they would all have to be changed anyway if I changed the DB’s options.

$db = new DB($db_options);
$objectThatRequiresDB = new ObjectThatRequiresDB($db);

It seems like it would be much easier to implement, and even simpler to change (which is the big point of DI) to just use a singleton to retrieve the DB instance in classes that require the DB.

class IActuallyNeedTheDB
{
	protected $db;
	
	public function __construct()
	{
	    $this->db = PDOSingleton::getInstance();
	}
}

I thought autowiring might help me here, but I can’t figure out how to make it work at all.
(http://php-di.org/doc/autowiring.html )

I’ve tried numerous DI packages (not just PHP-DI) that claim to have autowiring but I can’t really get any of their autowiring functionality to work (possibly because I am just misinterpreting it).

class DBPassThroughClass
{
	protected $db;
	
	public function __construct(PDO $db)
	{
		$this->db = $db;
	}

	public function doSomething()
	{
		// often there are many “pass through” classes before I actually get to a class that will require the DB
$AClassThatActuallyRequiresTheDB = new AClassThatActuallyRequiresTheDB($this->db);
	}
}

$di_library = new DILibrary();
$db_pass_through_class = new DBPassThroughClass(); // DB is required and properly type hinted

But this just tells me I am missing the PDO argument for the DBPassThroughClass instead of the DILibrary auto-resolving the PDO dependency through reflection. (Do I need to register the DI library as an spl_autoloader?)

And even if it did work, unless I am placing the DI library in some pretty much super-parent global class or trait, it seems like I have to instantiate the DI library (or at least call it) every time which doesn’t seem much different from the example above:

$db = $diLibrary->get('PDO');
$objectThatRequiresDB = new ObjectThatRequiresDB($db);

Vs

$db = new DB($db_options);
$objectThatRequiresDB = new ObjectThatRequiresDB($db);

And integrating it would require making $diLibrary pretty much a global (because instantiating it anew would invalidate the DI library’s configurations and bindings), which I know is to be avoided.

Sorry if I am not getting my point across here, but in brief: I need to know how to integrate DI properly, how to integrate a DI library with autowiring properly, and the reasons (if any) why I should use DI here over a singleton in this instance.


#2

Hi there I'm not sure my solution would be fitting to your case but thought I would let you know anyway so as to give you an idea...
I ease the way of DI through a data object factory which handles the common injections for all common data object models.
All data models extend a common interface... now every time you instantiate a data model you call the factory to return you the requested object... the factory holds an instance of the database class and other common injections which it then passes to the requested class to make the instantiation...

$users = $dataFactory->get('users', $myCustomInjection);

#3

If you do this and construct new objects inside classes like you showed in your example then you are not really using DI properly. Using DI means you avoid new operators inside your classes. Object construction is an implementation detail and not something your classes should be concerned with. Using dependency injection means that you inject (constructed) objects to the objects that require them:

class Controller
{
        private $task;

        public function __construct(SomeLogicTask $task)
	{
                $this->task = $task;

        }

       public function doControllerLogic()
      {
         // do some controller stuff
         $this->task->doSomething();
     }
}

Edit: removed unneeded $db occurrences above.

This increases flexibility because now your Controller does not care if the task needs a database or not, or maybe it needs a set of other dependencies. Also, you can now require a task interface in the constructor and can accept many different task objects that adhere to the same interface but do something different. Whereas if you use new then your implementation becomes stiff because you are now tied to only this specific class and you need to pass a specific set of dependencies into it and then you are faced with the problem of having to pass dependencies a few levels down the chain. Read about the courier anti-pattern :slight_smile:

You are asking about DI libraries but really they are outside your problem. You can use DI container libraries or factories but they don't change the way you use DI itself. DI libraries are used only to instantiate objects in your upper application layers, generally. You don't, I mean you shouldn't, pass instances of DI containers into your classes, because then you change DI into Service Locator. Your main classes remain the same regardless of whether you use a DI container, a factory or you construct all objects by hand. In any case, object instantiation and new are kept outside your classes that use DI.


#4

"Pass through" classes as you call them, and have noted, are generally a bad idea. @Lemon_Juice already linked to my website with a description of the issues it causes.

The tl;dr though, is that what if AClassThatActuallyRequiresTheDB now needs a second dependency, a Config instance? You now need to change both AClassThatActuallyRequiresTheDB and DBPassThroughClass to take the new dependency. What if DBPassThroughClass is instantiating AnotherClass which needs Router and Dispatcher instances? You very quickly end up making your passthrough class a god class.

Classes should only have dependencies on classes they genuinely need so in your example, it should be expressed as:

class DBPassThroughClass
{
	protected $AClassThatActuallyRequiresTheDB;
	
	public function __construct(AClassThatActuallyRequiresTheDB $AClassThatActuallyRequiresTheDB)
	{
		$this->AClassThatActuallyRequiresTheDB = $AClassThatActuallyRequiresTheDB;
	}

	public function doSomething()
	{
		$this->AClassThatActuallyRequiresTheDB->whatever();
	}
}

This way, the implementation of the constructor for AClassThatActuallyRequiresTheDB can be changed and you don't need to amend DBPassThroughClass every time AClassThatActuallyRequiresTheDB's constructor changes.

The key to understanding DI, is a concept called Inversion of Control.

You build objects inside out. Instead of new DBPassThroughClass() then having DBPassThroughClass instantiate the objects it needs, you construct everything at the top level, passing objects into the constructors that need them:

new DBPassThroughClass(new AClassThatActuallyRequiresTheDB(new PDO()));

#5

I can see the courier antipattern is exactly one of the issues I am running into when trying to use DI (currently without a container). Actually, @TomB’s Dice container was the first was the first container I used because it was the easiest for me to understand and implement and learn DI with.

Everything you guys posted was very informative and helped to sharpen my view of DI. I spent the last two days trying to incorporate a DI container in some way into a basic MVC app but did not succeed. I don’t know the correct architecture to implement it, although I think I am beginning to understand the theory quite a bit more now.

I think the problem I am encountering is following the advice of this quote on @TomB’s anti-courier pattern page:

The real alternative is a proper Dependency Injection Container with factories that sits at a level above the business logic that allows a true separation of object creation and the usage of those objects.

I am not sure what sort of architecture is needed (within an MVC app) to take best advantage of a DI container.

I tried implementing it in the super-parent controller (a class that all controllers extend). But then it would only be available in controllers unless I passed it into models (starting the pass through class/courier problem ), and if I ever need to use two controllers, it seems I would be instantiating it twice. I tried the same for the model super-parent, but the same problem occurs as more models means duplicate instantiations of the container. So in trying to implement DI it looks like I am violating one of the purposes of it, or at least other good programing guidelines.

The best case I came up with was a single singleton containing the DI container in both the constructor of the super-parent controller and super parent Model, and I make add rules/bindings to the container in the app bootstrap.

All of this led me to latching onto this “autowiring” idea I saw in a few container libraries so I could be reaching in the dark here. I couldn't get past the PHP error given that the (type-hinted) constructor argument was not found although I could be entirely misinterpreting what "autowiring" is.

I guess the “magical” scenario I was hoping exists was an autoloader I could register so if a type-hinted constructor argument was not found, it would either instantiate the needed object automatically anew or get it from the DI container, so I wouldn’t have to pass certain dependencies through pass through classes at all (like a database connection) nor rely on singletons. I am not sure that is what “autowiring” actually is, but some of the examples sort of made it out to be something like that.


#6

Autoloading and autowiring are two very different things and by the sounds of it, mixing those is where your issue is.

I'm assuming you know this already, but just in case: Autoloading is used to load a php file that contains a class. For example, when you run the code:

new SomeClass();

If the class SomeClass has not been found, the autoloader will catch the error and run

require 'SomeClass.php';

which makes the class available. Whether you're using DI, a DI container, Singletons or have the new keyword throughout your code, the autoloader will work in exactly the same way.

Autowiring, on the other hand is a term used to describe containers that automatically work out what dependencies are needed

Assuming you have the following classes:

class A {
	
	public function __construct(B $b) {

	}
}


class B {
	
	public function __construct(C $c) {
	
	}
}

class C {
	
}

To instantiate this without a container, you would need to do this:

$a = new A(new B(new C));

When you use a container with autowiring, it does this for you:

$container->create('A');

All this does behind the scenes is construct the A instance effectively using the code new A(new B(new C));

I think the issue you're having, is that sometimes you want a new instance, sometimes you don't. Let's imagine that the C instance above, now needs a PDO database instance:

class C {
	public function __construct(PDO $pdo) {
	
	}
}

Each time you call $container->create('C') it will likely run the code new C(new PDO());For instances like the database class, you don't want the same instance each time, each time you callnew C` you want to give it the same database instance like this:

$db = new PDO();

$c1 = new C($db);
$c2 = new C($db);

The problem is, a DI container does not magically know whether you want to use the same instance each time so you have to tell it. Different containers default to providing the same instance each time, others default to providing a new instance. Dice, which you mentioned, defaults to creating a new instance for reasons that I don't go into here.

Using Dice, you need to flag the PDO instance as shared so that the same instance is passed to every class that requires it:

$dice = new \Dice\Dice('PDO', ['shared' => true]);

$c1 = $dice->create('C');
$c2 = $dice->create('C');

n.b. you'll also need to provide the constructor parameters for the PDO instance (see here: https://r.je/dice.html#example2-1 )

Doing the above, will make Dice pass the same database instance to any class which needs it.


#7

Hi @TomB, thanks for replying again.

Part of the work I previously did made it as far as you suggested in your posts and I began implementing Dice in a few of my classes. (I went through the tutorials you have for Dice on your website, and successfully made PDO a shared instance and tested that it was the case.) The problem I ran into was I think in integrating Dice into my app. For example, if I am something like 4 classes deep (controller->model->model->model, etc), and then didn’t have Dice (or any DI container) available and did not know how to add it in or make it accessible.

My first implementation was to just pass the fully configured Dice container downward through each class, but this led to the obvious problem of making the dependency container itself a dependency everywhere (maybe that is intended and I am missing the point - it really didn't seem to make things simpler, at least).

class Dependency1
{
	public function doDependencyLogic()
	{
		$dependency2 = $dice->create('Dependency2'); // Problem: How to get dice here so that I can use it? 
		$dependency2->doDependency2Logic();
	}
}

class Dependency2
{
	protected $pdo;
	
	public function __construct()
	{
		$dice->create('PDO'); // Problem: How to get dice here so that I can use it? 
	}
	
	public function doDependency2Logic()
	{
	    $this->pdo->prepare('query stuff'); 
	}
}


class Controller
{
	public $dependency1;
	
	public function __construct(Depedency1 $dependency1)
	{
		$this->dependency1 = $dependency1;
	}
	
	
	public function doControllerLogic()
	{
	    $this->dependency1->doDependencyLogic();
	}
}

$dice = new \Dice\Dice;
// add dice rules
$controller = $dice->create('Controller');
$controller->doControllerLogic();

Secondly I tried the super-parent approach, but it meant that while I did not need to pass Dice/DI container in, it was being instantiated multiple times - once for each model or controller.

class SuperParentModel
{
	public $dice;
	
	public function __construct()
	{
	    $this->dice = new \Dice\Dice;
	    
	    // add Dice rules here
	}
}


class Model extends SuperParentModel
{
	public function doModelLogic()
	{
	    $model2 = $this->dice->create('Model2'); // get Dice from the parent
	    $model2->doModel2Logic();
	}
}

And finally the current idea, put the DI container in a singleton. The big con is of course the singleton.

class SuperParentModel
{
	public $dice;
	
	public function __construct()
	{
		$this->dice = DiceSingletonWrapper::getInstance();
	}
}

I hope this makes the problem a bit clearer - I am clearing it up as I go myself.


#8

The Dice container is pretty terse when it comes to documentation which is why you may be having trouble understanding how it works. Perhaps you should be using a container with better documentation and community support like PHP League's container. Using software well supported within the ecosystem will make it easier to find info and have others help you.

http://container.thephpleague.com/


#9

@ZooKeeper if you find something missing from Dice's documentation, please post an issue over at github. There is a page here: https://r.je/dice.html which documents each feature of the container

@Torite if you find the need to pass a DI container into another object, you are not really using DI as it's intended. What you have is a service locator, which is a different pattern. Occasionally there are times when this may be preferable, but generally it's a bad idea. See http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/

Regardless of which DI container you use, you should avoid passing the container into other objects. What you should really have is this:

class Dependency1
{
	private $dependency2;

	public function __construct(Dependency2 $dependency2) 
    {
		$this->dependency2 = $dependency2;
	}
	public function doDependencyLogic()
	{

		$this->dependency2->doDependency2Logic();
	}
}

class Dependency2
{
	protected $pdo;
	
	public function __construct(PDO $pdo)
	{
		$this->pdo = $pdo;
	}
	
	public function doDependency2Logic()
	{
	    $this->pdo->prepare('query stuff'); 
	}
}


class Controller
{
	public $dependency1;
	
	public function __construct(Depedency1 $dependency1)
	{
		$this->dependency1 = $dependency1;
	}
	
	
	public function doControllerLogic()
	{
	    $this->dependency1->doDependencyLogic();
	}
}

$dice = new \Dice\Dice;
// add dice rules
$controller = $dice->create('Controller');
$controller->doControllerLogic();

The DI container will call the constructors of Dependency1 and Dependency2 and inject the relevant objects.


#10

I'm working with rails and angular these days but I'll keep that in mind. At my last job I used php league's container in a legacy CodeIgniter 1.7 project to modernize the architecture much like the poster is trying to do with his/her own. I think the only thing missing is tagging when compared to a more robust container like Laravels. I really love the way Laravel does things not to mention the option to use Doctrine instead of Eloquent.


#11

Lots of good advice on this thread but as you discovered, the DI container is just one piece of the application puzzle. What you might consider doing is to experiment with a framework (such as Symfony) which was designed from the ground up around a container. You may very well decide that Symfony itself is not for you but I suspect you will learn quite a bit.

You might also find this Create Your Own Framework article useful. The last section involves adding a container.


#12

My impression is that you are making things hard to learn for yourself by trying to understand and implement too many things at the same time. Try separating the issues and tackle one problem at a time. You see, DI and DI containers are two separate things - first learn and implement one and then the other, it will be much easier for you. There's a clear programmatic separation between DI and a DI container because you make your DI (I mean your classes that implement DI) independent of any DI containers. The classes that implement DI:

  1. do not require DI containers
  2. do not instantiate DI containers
  3. do not need DI containers
  4. do not use global variables and objects
  5. work the same whether you use DI containers, don't use them, switch them, etc.

So for now just forget about DI containers and implement proper DI in your classes. This idea is very simple in principle, just follow these rules:

  1. Require all dependencies through the constructor. Sometimes you may accept optional dependencies via setters.
  2. Do not instantiate any objects in your classes - avoid using the new operator. I think in rare cases it is okay to use new if you need an object of a built-in class in PHP, for example DateTimeZone. But not for user-defined classes.
  3. Do not pass containers to your classes. Sometimes a class may need a factory that specializes in creation of a specific kind of object - but that should not be a class that can create any kind of object out of your application.

Example of your classes that use DI:

class TaskController {
	private $logicTask;
	
	public function __construct(LogicTask $logicTask) {
		$this->logicTask = $logicTask;
	}

	public function doControllerLogic() {
	   // do some controller stuff
	   $this->logicTask->doSomething();
	}
}

class LogicTask {
	private $db;
	
	public function __construct(Db $db) {
		$this->db = $db;
	}
	
	public function doSomething() {
		// do something in the database...
		$this->db->exec('UPDATE log_visits SET visits=visits+1');
	}
}

Hint: for now try getting rid of the idea of extending one global controller - make you specific controllers like the one above, independent and only requiring needed objects with DI. This controller needs LogicTask to do something so it requires it. These two classes are pretty independent from one another but one can use the other if needed. And you avoid pass-though objects in this way - Db doesn't have to be passed through.

That's all there is to DI, really. Don't try to put too much into it. Containers are a separate beast.

Once you have your classes with DI you are left with the problem of object instantiation. For now, for simple cases you can instantiate your objects manually in the upper layer of the application. Or, you can use a most simple hand-made container that will instantiate your objects:

class Container {
	private $db;
	
	public function getTaskController() {
		return new TaskController($this->getLogicTask());
	}
	
	public function getLogicTask() {
		return new LogicTask($this->getDb());
	}
	
	public function getDb() {
		if (isset($this->db)) {
			return $this->db;
		}
		
		return $this->db = new Db;
	}
}

This container handles the problem of creating only a single instance of the Db object, no need for a singleton.

So somewhere at the initial stage of your application you want to initiate and do some real stuff, in this case this might be invoking a method on the controller. So you need to instantiate all the objects and run the method. The manual way would be like this:

$controller = new TaskController(new LogicTask(new Db()));
$controller->doControllerLogic();

Another way would be using the simple container above:

$container = new Container;
$controller = $container->getTaskController();
$controller->doControllerLogic();

If you use Dice then the code would be different as each container type will have its own syntax for object creation. But the point is that regardless of how you instantiate your objects those objects do not have to change. Make that separation and it will all become much easier.

Hint: in your application most often you will only need one instance of the container. So first, you instantiate the container and then according to some parameters like user input or routing you fetch a specific object from the container (like a controller) and run a method on it. When the application grows big you may consider using different containers for different modules but let's not go that far for now.

As a side note, I often use a simple container like I showed above and it doesn't affect the DI itself at all. It is not as automated as Dice or other full fledged containers but it gets the job done and even has an advantage of being fully prompted by IDE's and the fastest of all. Anyway, once I read an opinion of a person who said that object creation is not where the majority of your programming will be spent so manual writing a container method for a new object or modifying one when needed is not a lot of work. But writing this kind of a simple container on your own will get you to understand the role of the container better and then if you want you can move to a more automated container (with auto-wiring), whichever you happen to like most.


#13

This thread has been very helpful and has cleared up many issues I had. I am still working on implementations, but things are going a bit better now. A large thanks to @Lemon_Juice and @TomB for the detailed responses (and others, too, for suggestions).

On Dice's documentation, I actually started with Dice because the documentation was the most accessible to me, and it anticipated the exact scenario that I was trying to use it for (shared database access). I think my problems were more due to not having the best picture of DI (and DI implementation) in general than any specific documentation issues. I have tried at least 5 containers seriously so far (reading docs and implementing them in testing code, and a few not so seriously) and accomplished the most with Dice initially, so it helped me understand how other containers worked.

@Lemon_Juice
In your example, is the method getDb a singleton or does it not count as a singleton pattern because it is within a container?

class Container {
	private $db;
	
	public function getTaskController() {
		return new TaskController($this->getLogicTask());
	}
	
	public function getLogicTask() {
		return new LogicTask($this->getDb());
	}
	
	public function getDb() {
		if (isset($this->db)) {
			return $this->db;
		}
		
		return $this->db = new Db;
	}
}

#14

No, it's not a singleton because a singleton is a pattern where a class is responsible for controlling its instantiation and limits it to only one instance. In this case Db would be a singleton if it allowed only one instance of itself by having a private constructor, etc. To be honest, we don't know if Db is a singleton because I have not presented any code for the Db class but let's assume it is a regular class without a singleton pattern and allows to be instantiated as many times as you wish - just like the PDO class. Edit: actually, I think we know Db is not a singleton because a singleton blocks constructor access and using new is not possible. If new is possible then I think it is certain that the class is not a singleton...

This is a very important distinction, actually. No class tries to control how it's instantiated and how many times. It is none of its business really because how can it know? If you do that then you limit the usefulness of the class. One day you may want another instance for any reason and you find the class won't let you. Then you need to rewrite it and rewrite the code that uses it.

We leave this control to the outside code. In this case the container is the outside code that is responsible for Db instantiation so it does what is needed - it creates just one instance because that's what is required. But if you ever need you can change the container to create two, three or unlimited Db instances.

Here we have one functional difference between the container and singleton. While singleton will limit to only one instance everywhere globally, the container limits to one instance only within the same container instance. So if you have two instances of the container then you will be able to create two Db objects. If you want only one Db object in your application then just use one instance of the container. But if you ever need it, you can create another container and have another Db object, which can be useful, for example, if you want to run an application within an application as an independent module - a single Db object might cause conflicts because a database connection has some state, for example open transactions, session variables, etc., and so sharing the same connection will not give you full separation. That separation would not be possible with a singleton.


closed #15

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.