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

MThomas's avatar

How to use a constructor in a Model (if it is even possible)

I'd like to use a repository in my model.

For example I have a RoleRepository with an findByName() method to fetch a a given role from the chached roles collection or from the database.

In the addRoles() method in my User Model I would like to use the findByName() method from the repository to grab the given role and assign this to the user.

But when I inject the RoleRepository in the model constructor I get an 'must be an instance of RoleRepository' error.

I'm not even sure it is possible to use a constructor in a model, in that case how could I work arround that?

0 likes
16 replies
nolros's avatar

@MThomas I ran into the exact same issue. You are required to use parent:: and inject the attributes

function __construct( RoleRepository $repo, $attributes = array())
    {
        parent::__construct($attributes);

        $this->repo = $repo;
    }
2 likes
thepsion5's avatar

I know it's not an answer to your question, I don't think it's a good idea to inject your repository directly into your model, especially an Eloquent model. You're creating a weird doubling of the active record pattern. When you save the model, the path it takes to actually perform the save are model -> repository -> model -> database instead of just model -> database. If I was going to do something like that I'd just use query scopes instead as it cuts out the extra steps.

3 likes
MThomas's avatar

@nolros thanks I will try that!

@thepsion5, I get your point, but how would you use a query scope to add a role or roles array? I get how I can use them to retreive a user or a role, but how to use them when you want to insert a related model.

nolros's avatar

@thepsion5 as you say on Eloquent, but with Doctrine you tend to handle it like any standard class, right? but then with Doctrine you don't need to do the parent:: injection.

MThomas's avatar

@nolros, using parent will still result in an exception:

Argument 1 passed to MartijnThomas\Authenticate\Models\User::__construct() must be an instance of MartijnThomas\Authenticate\Contracts\Roles\RoleRepository, none given, called in /home/vagrant/Code/Acme/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php on line 122 and defined
JarekTkaczyk's avatar

@MThomas It is pretty much impossible in Eloquent. There are so many places where Eloquent calls new $instance, new $related etc, that you would end up with impossible to use models.

And, injecting a repository to the model is definitely bad idea. It works the other way around.

In the User model, addRoles method, just rely on the Eloquent features, that's all. Or show the real example of what you try to achieve.

2 likes
thepsion5's avatar

@nolros Doctrine uses the Data Mapper pattern instead of the Active Record pattern, so it's kind of a different animal.

@MThomas There are a bunch of relationship methods you could use to do things of that nature, for example $user->roles()->save(new UserRole); or $user->roles()->associate($existingRole); Alternatively, you can always just define your own methods on the model for something more complex. I'd still rather use a repository though.

nolros's avatar

@JarekTkaczyk this is the Eloquent constructor so I think it is possible to inject a class?

public function __construct(array $attributes = array())
{
    if ( ! isset(static::$booted[get_class($this)]))
    {
        static::boot();

        static::$booted[get_class($this)] = true;
    }

    $this->fill($attributes);
}
thepsion5's avatar

You'd have to override the constructor and use the App facade to create the repo in said constructor, but you won't be able to specify it as a constructor param, because there are plenty of places in Laravel's source code where it creates new model instances without using the DI container.

MThomas's avatar

@JarekTkaczyk, @thepsion5 and @nolros: Here is my RoleRepository. As you can see I cache the availible roles array so it won't make an db request each time and probably the roles and permissions table won't be updated that often.

<?php namespace MartijnThomas\Authenticate\Repositories\Eloquent;

use Illuminate\Contracts\Cache\Repository as Cache;
use MartijnThomas\Authenticate\Contracts\Roles\RoleRepository as RoleRepositoryInterface;
use MartijnThomas\Authenticate\Exceptions\Roles\RoleNotFoundException;
use MartijnThomas\Authenticate\Models\Role;

class RoleRepository implements RoleRepositoryInterface {

    /**
     * The Cache Implementation
     * 
     * @var \Illuminate\Contracts\Cache\Repository
     */
    protected $cache;

    /**
     * Create a new role repository instance
     * 
     * @param Cache
     * @return void
     */
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }

    /**
     * Create a new Role
     * 
     * @param  string  $name
     * @param  string  $description
     * @return \MartijnThomas\Authenticate\Models\Role
     */
    public function create($name, $description = null)
    {
        $role = new Role;
        $role->name = $name;
        $role->description = $description;
        $role->save();

        $this->cache->forget('roles');

        return $role;
    }

    /**
     * Retrieve a role by its name
     * 
     * @param  string $name
     * @return \MartijnThomas\Authenticate\Models\Role
     */
    public function findByName($name)
    {
        $roles = $this->all();

        $role = $roles->where('name', $name)->first();

        if(is_null($role)) throw new RoleNotFoundException('Role "'.$name.'" not found');

        return $role;
    }

    /**
     * Return all roles
     *
     * Optional: Do not save the roles collection to cache
     * 
     * @param  boolean  $cache 
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function all($cache = true)
    {
        if(! $cache) return Role::all();

        return $this->cache->rememberForever('roles', function()
        {
            return Role::all();
        });
    }

}

And I would like to have the ability to add a role/permission to a user in a fairly readible manner. So I hoped to use someting like this in my User model:

/**
     * Add roles to the user
     * 
     * @param  mixed $permissions
     * @return void
     */
    public function addRoles($roles)
    {
        if(! is_null($roles))
        {
            if(! is_array($roles) ) $roles = [ $roles ];

            foreach($roles as $role)
            {
                $this->roles()->attach($this->roleRepo->findByName($role));
            }
        }
    }

Updated this post

If any of you have a better aproach I am all ear ;) I showed the code above to explain why I attempted to inject a repository in the constructor, It is not a must to use a repository but it felt more logical).

1 like
JarekTkaczyk's avatar

@nolros You got me wrong - I didn't say it's technically impossible, but you end up with impossible to use models. There are too many calls, like I mentioned, that will throw error if you override the constructor without default values.

And obviously, you won't be able to use something like User::all(), User::firstOrFail etc, but you need $user = App::make('My\Space\User') in order to resolve the dependency from the IoC.

2 likes
thepsion5's avatar

If I have relationships to deal with, I'll typically use a repository method to manage that, even if it's simple enough that I just end up calling eloquent's methods. Something like this:

//SomeUserRepo.php
function addRoles(User $user, $roles)
{
    foreach($roles as $role) {
        $this->addRule($user, $role);
    }
}

function addRole(User $user, UserRole $role)
{
    $user->roles()->associate($role);
    return $user;
}
1 like
MThomas's avatar

@thepsion5 that I used at first, but it felt a bit counter intuative to inject a repository in a class everywhere I need to add a role or something similar. But I think I will stick with it :)

nolros's avatar

@MThomas something very rough and I mean very rough for you to play with if you want to use this approach (not implemented just a direction ):

Create a very basic RoleInterface. Keep your RoleRepositoryInterface seperate, just a RoleUserInterface that you use for binding purposes only.

Keep it simple, some sort of basic mutual methods example getRole, hasRole and if you want addRole, although that seems not to be a great fit for User, but your call.

Bind RoleInterface to Role(model) in a Service provider

Then implement the RoleInterface into your User model vs. construct inject RoleInterface

In User model:

protected static $rolesModel = 'Role'; // your model

// setup relationship
public function roles()
{
return $this->belongsToMany(static::Role, 'users_roles', 'user_id', 'role_id')->withTimestamps();
}
// 
public function getRoles()
{
    return $this->roles;
}

public function hasRole($role)
{
    // you would need to set $instance, but you might be able to inject ::class, don't know play around 
    $role = array_first($this->roles, function($idx, $instance) use ($role)
    {
        if ($role instanceof RoleInterface)
        {
            return ($instance->getRoleId() === $role->getRoleId());
        }

           return false;
    });

    return $role, // whatever you want to
}
thepsion5's avatar

A more elegant solution, I think:

public function hasRole($role)
{
    //eager load the roles if we haven't already
    $this->load('roles');
    //either the role is a numeric ID or a user role instance
    $roleId = is_int($role) ? $role->id;
    return $this->roles->reduce(function($userRole, $roleFound) use($roleId)
    {
        return ($userRole->id == $roleId) || $roleFound;
    }, false);
}

You can add an instanceof check if you want to make sure only integers or role instances can be used.

1 like
nolros's avatar

@thepsion5 very nice sir! The reason I approach it with instanceof, and tell me if I am over complicating the process. I would not create the add method in User, to your point, addRole inside of User is bleeding logic into the user model, but rather leave it in Role and pass a $role model object to User and then check if the role model key exists ( hasRole() ) and if so invoke the model method. instanceof would check that it is a role object, then check if method exists, then execute. I think I've gone to the dark side of OOP :) I suppose my question and thinking is how to you execute a role method without inserting role logic into the User model, but at the same time limit it so you are not making the entire role model available, but only expose methods that are user related.

Please or to participate in this conversation.