views:

372

answers:

2

Hello,

My Dispatcher is "choosing" correct Controller; then creating Controller's instance (DependencyInjectionContainer is passed to Controller constructor); then calling some Controller's method...

class UserController extends Controller
{

  public function __construct(DependencyInjectionContainer $injection) {
    $this->container = $injection;
  }

  public function detailsAction() {
    ...
  }

}

DependencyInjectionContainer contains DB adapter object, Config object etc. Now let's see what detailsAction() method contains...

public function detailsAction() {

  $model = new UserModel();
  $model->getDetails(12345);

}

As you see I'm creating new instance of UserModel and calling getDetails methods. Model's getDetails() method should connect to db to get information about user. To connect to DB UserModel should be able to access DB adapter.

What is the right way to pass DependencyInjectionContainer to the UserModel? I think that this way is wrong...

public function detailsAction() {

  $model = new UserModel($this->container);
  $model->getDetails(12345);

}
A: 

I'd use a singleton object for all config parameters : You set it up in your bootstrap, then choose to use it directly or pass it as parameter in your objects.

The idea being to have one method all around to retrieve your config data.

You may then provide an abstract class for db manipulation which uses your config. singleton. DependancyInjection can still be used to override your default data.

The above link in the comment (possible 'duplicate') concludes on using constructor injection : this is close to your current method.

However if I try to figure how your model works, I guess you will have many other model classes other than "userModel". Thus an abstract class using a config singleton might be a good solution : all your next model classes will just extend this abstract class, and you don't have to worry about your config.

On the other hand, your solution is good to me as long as your dependanceInjectionContainer changes often.

Rodolphe
Frankly speaking, objects in my dependanceInjectionContainer are not changing often. I'm storing DB Adapter, Memcache Adapter, Mail Adapter and Config in dependanceInjectionContainer. So, probably, some kind of Registry is ok for me. I can't even imagine situation when I should change anything in dependanceInjectionContainer (any real life situation samles?)
Kirzilla
As far as my humble experience can speak, I have not yet seen any use of DI for a controller.Maybe if you need to juggle with multiple databases ? or if you pass some user specific parameters (providing a theme for exemple)
Rodolphe
By the way, I can make dependanceInjectionContainer a Singleton and get it's instance in Controller or Model. It will help me to avoid passing dependanceInjectionContainer into constructor. So, let's wait for Mr. Mark Seemann answer to see what is the key....
Kirzilla
Kirzilla
Just found pretty nice article about Dependency Injection in ZendFramework http://www.developertutorials.com/tutorials/php/the-zend-framework-dependency-injection-and-zend-di-8-02-07/page1.html
Kirzilla
+1  A: 

Instead of injecting the entire DI Container into your classes, you should inject only the dependencies you need.

Your UserController requires a DB Adapter (let's call this interface IDBAdapter). In C# this might look like this:

public class UserController
{
    private readonly IDBAdapter db;

    public UserController(IDBAdapter db)
    {
        if (db == null)
        {
            throw new ArgumentNullException("db");
        }

        this.db = db;
    }

    public void DetailsAction()
    {
        var model = new UserModel(this.db);
        model.GetDetails(12345);
    }
}

In this case we are injectiing the dependency into the UserModel. In most cases, however, I would tend to consider it a DI smell if the UserController only takes a dependency to pass it on, so a better approach might be for the UserController to take a dependency on an Abstract Factory like this one:

public interface IUserModelFactory
{
    UserModel Create();
}

In this variation, the UserController might look like this:

public class UserController
{
    private readonly IUserModelFactory factory;

    public UserController(IUserModelFactory factory)
    {
        if (factory == null)
        {
            throw new ArgumentNullException("factory");
        }

        this.factory = factory;
    }

    public void DetailsAction()
    {
        var model = this.factory.Create();
        model.GetDetails(12345);
    }
}

and you could define a concrete UserModelFactory that takes a dependency on IDBAdapter:

public class UserModelFactory : IUserModelFactory
{
    private readonly IDBAdapter db;

    public UserModelFactory(IDBAdapter db)
    {
        if (db == null)
        {
            throw new ArgumentNullException("db");
        }

        this.db = db;
    }

    public UserModel Create()
    {
        return new UserModel(this.db);
    }
}

This gives you better separation of concerns.

If you need more than one dependency, you just inject them through the constructor. When you start to get too many, it's a sign that you are violating the Single Responsibility Principle, and it's time to refactor to Aggregate Services.

Mark Seemann
@Mark Seemann, thank your for your anwer! I will read it some times to understand the advantages.
Kirzilla
I see the only disadvantage of your method (imho). What if I'm calling UserModel method which don't need any connection to DB? All Dependency Injection Objects passed to UserModel constructor are already being created in UserModelFactory. (This all is about performance). What do you think of this realization of DIContainer? http://twittee.org/
Kirzilla
If the UserModel doesn't use the DB in all cases you might want to consider whether it should take a dependency on it at all. In any case, you don't solve any issues by injecting the container instead of specific dependencies, and I generally don't buy the argument about performance, because expensive services (like the DB adapter) tend to be shared amongst many consumers, and even if this isn't the case, there are ways to deal with that: http://blog.ploeh.dk/2010/01/20/RebuttalConstructorOverinjectionAntipattern.aspx
Mark Seemann
Thank you so much!
Kirzilla
By the way, using your method I can encapsulate any DIContainer Objects. It is impossible to do if I will inject whole DIContainer. Thank you!
Kirzilla