jmagaro88's avatar

Dependency Injection in Self-Handling Jobs

After watching Jeffrey's awesome command-oriented architecture videos, I'm inspired to try implementing synchronous Jobs for my eCommerce project (AddItemToCart, RemoveItemFromCart, etc.). But one thing I'm trying to wrap my head around is dependency injection within jobs.

From the Laravel 5.1 documentation in the Serve Container chapter, it seems that dependencies can be injected into the constructor for the job like this:

namespace App\Jobs;

use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;

class PurchasePodcast implements SelfHandling
{
    /**
     * The mailer implementation.
     */
    protected $mailer;

    /**
     * Create a new instance.
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    /**
     * Purchase a podcast.
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

But if I create a dummy job, add to a controller method, and try to dispatch the job with $this->dispatch(new Foo), I get the following exception:

Argument 1 passed to App\Jobs\Foo::__construct() must be an instance of Illuminate\Contracts\Mail\Mailer, none given, called in {directory} and defined

However, as soon as I use method injection to add that dependency into the handle() method, it works. In fact, when you look at the Queue chapter of the documentation, a similar example is used and the Mailer is injected into the handle() method instead of in the constructor.

<?php

namespace App\Jobs;

use App\User;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendReminderEmail extends Job implements SelfHandling, ShouldQueue
{
    use InteractsWithQueue, SerializesModels;

    protected $user;

    /**
     * Create a new job instance.
     *
     * @param  User  $user
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Execute the job.
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function handle(Mailer $mailer)
    {
        $mailer->send('emails.reminder', ['user' => $this->user], function ($m) {
            //
        });

        $this->user->reminders()->create(...);
    }
}

Am I missing something, or is the DI example in the Service Container chapter incorrect?

0 likes
3 replies
Corez64's avatar

The thing to remember with the Commands and Domain Events lessons were recorded using the Laravel 5 structure which changed slightly when 5.1 was released. In L5 you had a command and a handler for the command, you created the command as a glorified value object which contained all the data you need for a command to run, the command handler class would then have the code to do what ever action the command required with dependencies injected into the constructors and the command passed into the handle method.

In L5.1 the they were renamed to Jobs and by default they are self handling. This inverts it slightly as they are still command objects where you pass the data into the constructor so it looks the same from where you are firing the command. However, Laravel will check the handle method and inject your dependencies in then.

You should be able to go back to the Command and CommandHandler (or Job and JobHandler with the new name) but I haven't tried to do it so I couldn't tell you how.

1 like
pmall's avatar

But one thing I'm trying to wrap my head around is dependency injection within jobs.

With a handler class, injection is done in the constructor. With a self handling job, injection is done in the handle method.

2 likes
normkatz's avatar

In the 5.3 documentation for the "self-handling" job at https://laravel.com/docs/5.3/queues#class-structure we see that there's a parameter for the constructor, which comes from the dispatch call. This allows you to set any state before the job instance is serialized and put onto the queue. When the handler called, its object is the deserialized job so it has the original state that you set in the constructor.

I understand that Laravel takes care of injecting the dependency specified in the handler's type hinted parameter. But I haven't found a clear indication of where the "object" comes from that is injected. From the example, we know it's an instance of an AudioProcessor. public function handle(AudioProcessor $processor)

But it's unclear where you specify "which" instance and where it is retrieved from. I assume it's not on the queue since that should be an instance of the job class. So there's probably a line in a Laravel config file where you configure a relationship between the job class and the dependent object that gets passed to the handler.

Can you help clarify where this relationship is set?

Please or to participate in this conversation.