Dependency Injection (DI) refers to the practice of passing on dependencies to classes that need them through the __construct method — also known as composition — it is generally a more flexible approach than relying on inheritance.
When employing inheritance to design an application, the problem usually is that classes will be very tightly coupled, and we will not be able to easily re-use a given part of the application in another context. If we rely on composition instead, however, then we can easily pick and choose the parts that we need for another project.
It also offers benefits when not planning to distribute or re-use code outside of the project, since we can more adequately organize our code into small, self-contained, objects with dependencies.
Composition over inheritance
Dependency injection is related to the design principle known as composition over inheritance When using inheritance, we design our applications around what they are; but when using composition, we design them around what they do. The beauty of compositing is that we do not have to refactor our entire application if we change the functionality later down the road.
For example, with inheritance, we would be extending a parent class to add functionality; but, this also means that we are now forced to instantiate the object from the child class. We can probably still instantiate the parent as a stand-alone class, but we will not be able to instantiate the child class without also instantiating the parent. Another problem with this configuration is that if something in the application changes, we might be forced to re-organize the entire class family tree.
If we use composition and dependency injection instead, then we can easily switch or remove dependencies. For example, if suddenly a class no longer needs a dependency, all we have to do is to remove it from the __construct method and refactor the class as needed. If we had instead relied on inheritance, then we would potentially be forced to change all of the classes in the entire family tree.
Inheritance might still be useful for very specific things, so this does not mean that we should completely ignore it and just use DI for everything; but composition does offer clear benefits, and by using it properly, we can avoid cornering ourselves into refactoring hell.
When we use composition with dependency injection, we must have a place where we can instantiate the various objects that our application needs. For all intends and purposes, this is usually the index.php file (or whatever you have chosen to name it in your case).
But, it might also depend on the code library or framework that you are using. For example, there can indeed be multiple composition roots. A framework might have its own "composition root" where it "assembles itself", while it might offer a different place for developers to assemble their own code and interact with the framework.
In the case of a framework, a good example would be when having a router, also known as a url-mapper, that "maps" certain request paths to specific files or "composition roots" where a developer can then place code to handle HTTP requests. In this case, the framework will call the developers code depending on the URL (path) that was requested.
Note. A path must not be empty, so there should always, at least, be a forward slash "/" in the path of a request. It is possible to send a malformed HTTP request without path, but it should result in a error like 400 Bad Request or similar.