Rationale behind Dependency Injection Container libraries

Ah, I see. The only problem with the code you posted, is that whatever is calling the archive method needs to either construct, use a DIC to construct, or already have a constructed db2 instance… essentially just moving the problem up a level.

So now I get your original problem: Should we get the DIC to call the controller action, and inject the arguments? I think that’s a very interesting question… which I’m trying to understand how that might work and it’s an interesting concept.

Perhaps like this:

$controller = $dic->create('ProductController');
$dic->call($controller, $action);

That’s a very interesting approach and I can’t see any immediate downsides, I think that could be a really nice way of calling actions.

edit: Actually this is already possible with Dice, although it’s slightly less dynamic

$controllerName = 'ProductController';
$dice->addRule($controllerName, ['call' => ['archive', []] ]);
$controller = $dic->create($controllerName);

Yes, that’s what I meant! I think most common usage in popular frameworks is to construct dependencies in the controllers out of a container, which is basically a service locator. I’m thinking about moving it up one level and automating constructing those dependencies. I’ll try it out when I have some time to experiment with creating a new framework :smile:

The more I think about this, the more I think it’s a great idea :slight_smile: I’m going to implement it and see how I get on.

2 Likes

Great, don’t forget to report back your observations !

Well sorry to bump this thread. I am thinking about using a dependency injection container myself as well, so I can inject required dependencies into my controller and repository classes easily. The idea is that, my controller classes usually depend on one or multiple repository objects, while certain repository objects may also depend on other repository objects(if theres a foreign key). Below is an example that demonstrate that(I only show the constructors since these are the only one that matter):


class UserRepository{

    public function __construct(PDO $database){
            $this->database = $pdo;
        }
}

class PostRepository{
    public function __construct(PDO $database, UserRepository $userRepository){
        $this->database = $database;
        $this->userRepository = $userRepository;
    }
}

class CommentRepository{

    public function __construct(PDO $database, UserRepository $userRepository, PostRepository $postRepository){
            $this->database = $database;
            $this->userRepository = $userRepository;
            $this->postRepository = $postRepository;
        }
}

class PostController{

    public function __construct(UserRepository $userRepository, PostRepository $postRepository, CommentRepository $commentRepository){
            $this->userRepository = $userRepository;
            $this->postRepository = $postRepository;
            $this->commentRepository = $commentRepository;
        }
}

As you see, the dependency graph is kinda strange, largely because a comment has foreign keys user(poster) and post(where the comments belong), and post has foreign key user(poster). For this reason, the PostRepository depends on UserRepository, and CommentRepository depend on User and Post repositories.

If I use a dependency injection container, such as Dice, I will need to configure the dependencies using rules. Since I want the PDO, UserRepository, and PostRepository injected into the Controller and CommentRepository to be the same instance, while by default all DICs will just create new instances. The question is, where do I put my DIC configuration logic? If my Dispatcher class is responsible for creating controllers, should I put such logic inside the dispatcher? But imagine I have like 20 controllers and 50 repositories, the configuration logic will be very very long, using a single method for this will make it a God class with long method with more than 1000 lines of code.

Another question is, since my controller class depends on CommentRepository, which depends on PostRepository, which in turn depends on UserRepository, may I just inject the CommentRepository and get the other dependencies from the CommentRepository. But does this violate good OO design principles? It feels like courier antipattern to me. What do you think?

By doing this you’d be making your controller code dependent on the implementation of CommentRepository. What happens if you refactor the repository and it no longer uses some of those dependencies?

The other problem would be that your controller’s dependencies would not all be explicitly declared in the constructor/method signatures. You’d be using your CommentRepository like a service locator.

This is a good question. Generally speaking it’s better to put the DIC logic in its own file (or files) and include it when required. You mentioned Dice, which supports simple loading of JSON files. Since it uses arrays, it’s very easy to define Dice’s configuration in its own JSON file and import it.

I see, thanks for the advice. And yeah, I definitely realize that I will practically make the CommentRepository a service locator this way. I dont need SL, I need DI.

yeah, you make a good point, and I was thinking about the same thing too. So I can write the rules in JSON and load it by PHP, then send it to Dice as $rule parameter? Do you have an example for this?

The real problem here is your repositories. They should not be dependent on each other. By making them depend on each other all you are really doing is to make one massive class disguised as individual classes. And the whole thing will blow up once you add a few more database tables.

I suspect you are basically trying to share mapping information. Take a look at Doctrine 2’s design. Store the mapping information in a manager class which can then be accessed by individual repositories.

And do use an application configuration file for defing your services. Here is a yaml example for the Symfony dic:

  # =====================================================
  # Exporter
  cerad_project_tournament_export_command:
    class: Cerad\ProjectTournament\Export\ExportCommand
    tags:  [{ name: console.command }]
    arguments:
      - '@cerad_project_tournament_exporter_excel'

  cerad_project_tournament_export_action:
    class: Cerad\ProjectTournament\Export\ExportAction
    calls: [[setContainer, ['@service_container']]]
    arguments:
      - '@cerad_project_tournament_exporter_excel'
      - '@cerad_project_tournament_export_form'

  cerad_project_tournament_exporter_excel:
    class: Cerad\ProjectTournament\Export\ExporterExcel
    arguments:
      - '@cerad_project_tournament_export_repository'

  cerad_project_tournament_export_repository:
    class: Cerad\ProjectTournament\Export\ExportRepository
    arguments:
      - '@cerad_project_tournament_connection_dbal'

   cerad_project_tournament_export_form:
    class: Cerad\ProjectTournament\Export\ExportForm
    arguments:
      - '@cerad_project_tournament_connection_dbal'

So you are saying that repositories should not depend on each other, I see your point. But in this case, how can I load any related domain models? Lets say I have a Post domain model, which has an Author that should be another domain model. It can be either lazy loaded or eager loaded depending on the configuration of my PostRepository class. The question now is, how can a PostRepository class create a Post domain model with loaded Author/User property field? The User domain model has to come from somewhere, and if the PostRepository class does not have a dependency on UserRepository, it means that the PostRepository will have to create the User object on its own. But doesnt this violate good OO design principle? Since now you can create both a Post and User domain model with this repository.

Sorry but I am still a bit confused, I dont see any other possible solutions other than supply a dependency of UserRepository to the constructor of PostRepository. Can you elaborate a bit more? Thanks.

If you could post the link to your source code them I might be able to help out.

As it stands, I don’t understand the problem.

$blog = $blogRepository->find($blogId);
$author = $blog->getAuthor();

The above code should be simple to implement without having to inject a bunch of cross-dependencies.

From a big picture point of view, having class A depend on class B and class B depend on class A will often lead to problems.

Well in the above code, you are expecting that $blog->getAuthor() will work properly, but how? If BlogRepository does not contain a dependency of UserRepository(Author should be an instance of domain model User), then how is it supposed to create a User domain model just by calling $blogRepository->find($blogID)? Do you mean, the BlogRepository should be able to create a User domain model on its own? Like, writing a JOIN query and manually create the User object for $blog->author property? This is a part of the code in my PostRepository that generates a complete Post object, the loadModel($object) is called inside each findByXYZ method for repository to load the model:

// $object is a stdclass object generated from PDO::fetchObject(), which contains only data and is used only during model creation.
protected function loadModel($object){
    $post = new Post;
    $post->setID($object->id);
    $post->setUser($this->userRepository->find($object->user))
    $post->setTitle($object->title);
    $post->setContent($object->content);
    $post->setDatePosted(new DateTime($object->dateposted));
    $post->setStatus($object->status);
    return $post;
}

Actually I looked up an example with foreign key mapping in Martin Fowler’s book ‘Patterns of Enterprise Application Architecture’. This is how it is done in his example:

protected DomainObject doLoad(long id, ResultSet rs) throws SQLException{
    string title = rs.getString(2);
    long artistID = rs.getLong(3);
    Artist artist = MapperRegistry.artist().find(artistID);
    Album result = new Album(id, title, artist);
}

As you can see, my repository actually mirrors the mapper in Martin Fowler’s book. The differences are:

  1. ResultSet in Fowler’s book is specific to Java, while in my book I get a stdclass object to hold temporary data. But anyway, they both are just temporary data structure to transfer data from DB to model.
  2. Fowler’s Album class is simple enough so he passes the three fields to Album constructor, while I use setters since there are a lot more fields(dont want to have a constructor with 5+ parameters).
  3. Fowler uses Registry to fetch ArtistMapper for AlbumMapper to use, while I use dependency injection to push an instance of UserRepository to PostRepository, thus removing the need for Registry.

As you see, Fowler’s approach is essentially the same with mine, and my repository works even better since I am using dependency injection rather than relying on a static registry. I understand that by the time Fowler wrote the book(2002-2003), the concept of DI aint widely adopted so singletons and registries were common at that time. If Fowler rewrites the book today, he would definitely use dependency injection. Thus passing ArtistMapper to AlbumMapper as a dependency, like how I am passing UserRepository to PostRepository as dependency.

So can you illustrate by an example of how you would approach this problem, if PostRepository is not dependent on UserRepository?

This is a question that I’m also interested in getting an answer to. If entities are completely decoupled from any storage and database access mechanisms then if we want to use dependency injection then I can’t see how this is going to work:

This code should be rewritten to something like this:

$blog = $blogRepository->find($blogId);
$author = $blog->getAuthor($userRepository);

Otherwise the entity object is not able to load Author on its own. And even if we inject UserRepository like this and the blog entity can use it to fetch the author then we are beginning to violate SRP because now the entity is dealing with retrieving data. This starts to resemble Active Record.

The problem can be avoided if BlogRepository eager-loads the author (e.g. with a JOIN) but if we prefer lazy loading then I can’t see a way out other than add the responsibility for fetching more data into the blog entity.

The basic problem with your approach is that you have database mapping information scattered across multiple repositories.

Instead, consider having a single array in which you store the mapping information for Blog and Author as well as the fact that there is a relation between Blog and Author. Each repository has access to this mapping information which allows the Blog repository to create and link an Author object as needed. Likewise the Author repository will have no problem creating and returning blog posts as needed.

Try to imagine a more complicated domain with a dozen or so inter-related entities. Do you really want to inject a half a dozen or more repositories into each related repository?

Once again, I would urge you to look at some of the more successful existing ORM solutions such as Doctrine or Propel. Its not about following abstract patterns, it’s about generating useful and working code.

Yeah you do make a point, if a domain model has like 5+ dependencies on other different domain models, I will need to supply its repository’s constructor with 5+ other repositories. This can soon go out of control, and configuration scattered across multiple files sure is another problem.

But your proposed alternative has an issue as well. Since this array for mapping information is shared across all repositories, I feel that it has become some kind of service locator for the persistence layer. I’d suppose this array is created somewhere and passed as constructor parameter to all repositories. A non-static service locator does the same thing if you inject it to a class’ constructor, just the syntax is different. Maybe I am confused and missing something here?

I think the thing you are missing is the desire to understand how existing software works. Doctrine 2 stores mapping information in a manager class. The manager class in turn acts as a repository factory. Not sure how you get service locator and static functions from this.

Take it for a test drive so you know how it works instead of guessing.

Or look at Eloquent’s active record approach.

1 Like

I see, sure I will look up Doctrine’s entity manager class and see how it handles things. Thanks.

It’s an implementation detail and there are many solutions. In Maphper I solved this by having each mapper attach child mappers to the entities dynamically so:

$blog->author is an instance of a class which wraps the mapper for authors and is injected on the fly. Take a look at https://github.com/Level-2/Maphper/blob/master/maphper/maphper.php line 105. The wrap function for a one to one relationship will call getdata on https://github.com/Level-2/Maphper/blob/master/maphper/relation/one.php which returns an instance of that class with the mapper already injected.

This allows you to use it like this:

$authorSource = new \Maphper\DataSource\Database($pdo, 'author', 'id');
$authors = new \Maphper\Maphper($authorSource);

$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id');
$blogs = new \Maphper\Maphper($blogSource);


$relation = new \Maphper\Relation\Many($blogs, 'id', 'authorId');
$authors->addRelation('blogs', $relation);


$relation = new \Maphper\Relation\One($authors, 'authorId', 'id');
$blogs->addRelation('author', $relation);

And once it’s set up, it’s managed internally and transparently so you can call:

foreach ($author->blogs as $blog) {
}

//or

echo $blog->author->name;

And it just works without needing to inject extra dependencies. Imho this is the cleanest way of doing it because none of the dependencies of the mappers are known outside the top level where they’re configured.

if you wanted to you could do this:

$author = $blog->author->blogs->item(0)->author->blogs->item(1)->author;

Which is a bit pointless, but more useful when you have:


$order->customer->address->address1;

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