Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Cronin's avatar

Understanding Method Injection

Right now I have an interface called FriendRepository that only has one implementation: EloquentFriendRepository. A method of that class (which extends Model) is getFriendsOfUser($id).

I want to be able to reference a different model, EloquentUserRepository in order to get the details of each of the friends of the user with $id. The problem is that if I do something like this:

public function getFriendsOfUser($id, UserRepository $user)

...I get errors because the interface only expects one argument.

I thought the point of this was that it wasn't an actual argument, it was an injected dependency that would be handled by the container. In this video Jeffrey even mentions that it doesn't matter what order you put them in. So why is mine being treated as an argument rather than an injected dependency?

Edit: Is my issue just that you can't use method injection when dealing with Models? If so, why is that?

0 likes
8 replies
milewski's avatar

you rather write the dependency on the __construct

public $user;
public $friend;

public function __construct(UserRepository $user, FriendRepository $friend){
    $this->user = $user;
    $this->friend = $friend;
}

then

public function getFriendsOfUser($id){
    $this->user->find($id)......
}

if you dont do like this every time you call getFriendsOfUser you will be forced to pass the UserRepository along...

Cronin's avatar

Except it doesn't seem to work at all. If this is my EloquentFriendRepository:

class EloquentFriendRepository extends Model implements FriendRepository{

    protected $user;

    public function __construct(UserRepository $user){
        $this->user = $user;
    }

}

I will get this error: Argument 1 passed to App...\Friend\EloquentFriendRepository::__construct() must implement interface App...\Auth\UserRepository, none given, called in /home/vagrant/Code/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php

It would appear to me that this isn't working with reflection and that Model.php is trying to make a new instance of this repository with no arguments (which seems correct to me). I don't really understand why this doesn't work.

MarkRedeman's avatar

Why are you extending an eloquent model in your EloquentFriendRepository? Also how do you instantiate your repository?

Cronin's avatar

Good question, which is why I refactored my code to get away from that. Now I have my EloquentFriendRepository just return the collection and that method gets called by a method in my EloquentUserRepository which then looks up all the user details for each of the friends in the collection. This seems like a better separation of concerns anyway.

In general would you say it's bad practice for models to make use of other models? The issue I see with my new code is that I'm making two DB queries where one with a join would have sufficed. Method Injection works perfectly in my controllers so I think it's intentional that it doesn't work with models.

MarkRedeman's avatar

In general yes. More importantly I think using repositories in combination with Eloquent is not useful. There was a discussion on repositories a while back which you might find interesting.

I was asking about how you're instantiating your repositories because I suspect that you do not yet fully understand why people use dependency injection and how it works. With dependency injection you can automatically instantiate classes and inject the dependencies of its constructor, for example:

<?php
class DependencyB {};
class DependencyC {};

class SomeClassWithDependencies {
    public function __construct(DependencyB $b, DependencyC $c)
    {
        // do stuff with $b and $c
    }
}

// plain old php:
$a = new SomeClassWithDependencies(new DependencyB, new DependencyC);

// Using Laravel's Service Container to automatically inject dependencies
$a = App::make('SomeClassWithDependencies');

So instead of being explicit on what to inject we can use Laravel's service container to automatically inject them. However it is important to note that you can't just do $a = new SomeClassWithDependencies since php does not know how to inject the dependencies.

Cronin's avatar

I'll look into that discussion, thanks for posting that.

I'm by no means an expert, but I do already have a binding in the service provider that returns EloquentFriendRepository when something requests an instance of UserRepository. I think the trouble is that I watched some Laracasts about Repositories and Dependency Injection and I tried to use them early on in a new project rather than refactoring an existing project to make use of them where it makes the most sense.

I highly doubt I'll be switching from MySQL to Mongo (for example) but I can see how writing tests would be easier. I guess I'll just have to keep learning it until I have enough of a grasp to decide when I should and shouldn't use repositories.

So if it's bad practice to work with another model inside a model, how do you work with more complex data? Even something relatively simple, like getting the user's details and appending their list of friends seems strange to me. Should I just be doing a join on the Friends table inside my User model? It seems like the FriendRepository should be my gateway to interacting with that table.

jekinney's avatar

I think of a repo as a translation of database logic to business logic. Yes, many times your just wrapping eloquent into the same method name ( like a create function uses eloquent create on a model so the same name if you will) but the benefit comes in mainly as you can keep your controllers to one or two lines of code and for example your user repo can (if you wish) create a new user, set up profile table and fire off email.

So as many stated, small apps or just learning, don't worry about. Do what's easy for you. You can always refactor later.

JarekTkaczyk's avatar
Level 53

@Cronin Injection in general means that there's some helper class, that will do the job for you.

In Laravel that class is Illuminate\Container\Container (Application extends it), so the only thing you gain from using (mind that it's pretty BIG thing) is capability to do this:

// define dependency in TheClass:
__construct(SomeInterface $dependency)

// let the container know what it should inject:
$container->bind('SomeInterface', 'SomeClassToInject'); // App::bind does this

// then whenver you need the TheClass object just use the container:
$obj = $container->make('TheClass')

// instead of doing it by yourself
$obj = new TheClass(new SomeClassToInject)

This lets you replace SomeInterface with different implementation without any hassle. You just change the bind('SomeInterface', 'AnotherClassToInject') and you're done. This way you don't have to change your whole codebase, wherever you called new TheClass(new SomeClassToInject) to new TheClass(new AnotherClassToInject).


Now, since you asked about the method injection. It is exactly the same as above, so in order to let the Container do the job for you, you need to call:

// TheClass
public function doThis(SomeInterface $dependency)
$container->call('TheClass@doThis')

And this is what happens in the Laravel controllers automatically. That's why you can simply drop some dependency in the constructor or any method (handling the routes), and it will be injected for you.

2 likes

Please or to participate in this conversation.