5

I have a TimezoneTrait which is used in the User model. I also have an UserRepositoryInterface which is loaded through a service provider and works good across all classes so the binding should be fine:

public function register()
{
    $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
}

public function provides()
{
    return [
        UserRepositoryInterface::class,
    ];
}

Now the problem I have is that I have to use that repository in my trait, so I naturally did this:

private $userRepository;

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

But the dump shows that the repository is null. Can't traits be injected with dependencies?

Norgul
  • 4,613
  • 13
  • 61
  • 144

1 Answers1

8

Defining __constructor within trait is actually wrong. Or just a bad design. Constructors should be specific to a class to which they belong, not traits. Another issue then, you're importing trait in Model class, that means you should specifically follow its rule about how a trait in a model is loaded.

At the booting stage of a model, it searches imported traits recursively within class and automagically call a method that is using boot{TraitNameHere} naming convention, statically. That proves traits in model does not involved in Laravel's dependency injection cycle.

To make it happen, you can use Laravel global helper to load stored instance inside container, like the facade App::make(DefinedKeyHere). Then store the assigned instance into a static property to make it retained until the runtime ends and also because the recalling method is static.

trait TimezoneTrait
{
    protected static $userRepository;

    protected static function bootTimezoneTrait()
    {
        static::$userRepository = \App::make(UserRepositoryInterface::class);
    }
}

If you're currently trying to avoid using global helper, listening to the model booting event is also helpful. Example inside EventServiceProvider,

Event::listen('eloquent.booting:*', function (Model $model) {
    $model->setUserRepository($this->app[UserRepositoryInterface::class]);
});

Then the trait would be,

trait TimezoneTrait
{
    protected static $userRepository;

    public function static setUserRepository(UserRepositoryInterface $userRepository)
    {
        static::$userRepository = $userRepository;
    }
}

Take a note that I defined setUserRepository as static, but you can also make it non-static, too.

And to expand a little bit about model event, model has several events to fire whenever it's doing its related action.

Example events from Laravel 5.5,

public function getObservableEvents()
{
    return array_merge(
        [
            'creating', 'created', 'updating', 'updated',
            'deleting', 'deleted', 'saving', 'saved',
            'restoring', 'restored',
        ],
        $this->observables
    );
}

And other two default events that are fired when its instantiated (also unserialized) which are booting and booted. And the method which is use to fire the event, notice the event name.

protected function fireModelEvent($event, $halt = true)
{
    // ...

    return ! empty($result) ? $result : static::$dispatcher->{$method}(
        "eloquent.{$event}: ".static::class, $this
    );
}
Chay22
  • 2,834
  • 2
  • 17
  • 25
  • What is the difference between booting and constructing? Constructor is only called when class is instantiated, but boot is called initially after all service providers have been registered. Does that mean that boot will be called independently of whether the class is instantiated or not? – Norgul Dec 20 '17 at 07:01
  • That is different `boot`, what you're referencing is application `boot`strapping, but this is just `boot` thingy that happening only on model. Once a model is instantiated, this `boot` is just as stated above, calling whole traits method recursively, applying static events, and any other thing. Once `booted`, it'll store retained value which telling that `boot`ing stage was already done. So whenever you re-instantiate it again -- like thru `new Model`, `Model::create`, so forth -- those hard-consuming memory stage is avoided. – Chay22 Dec 20 '17 at 12:53