sumityadav's avatar

Container unable to resolve class in a Job class

Laravel Version

11.4.0

PHP Version

8.3.6

Database Driver & Version

No response

Description

In a new Laravel application, a singleton class registered within the AppServiceProvider is not resolved inside the constructor of a Job class. While the singleton resolves successfully in web.php, it fails within the job class.

Steps To Reproduce

Test class.

public function __construct($config = array())
{
    $this->config = $config;
}

Registering as singleton in app/Providers/AppServiceProvider.php

$this->app->singleton(Test::class, function () {
    return new Test(['foo' => 'bar']);
});

Testing the / route and it works in web.php.

Route::get('/', function (Test $test) {
    dump($test);
});

Now instead of dump, I dispatch the job and it gives the following exception:

Too few arguments to function App\Jobs\TestJob::__construct(), 0 passed in /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php on line 19 and exactly 1 expected

Route::get('/', function () {
    \App\Jobs\TestJob::dispatch();
});

app/Jobs/TestJob.php

public function __construct(Test $test)
{
    $this->test = $test;
}

Posted this as Bug on the larave/framework repository and was advised to get help from the forum. So posting it here. Ref: https://github.com/laravel/framework/issues/51182

0 likes
2 replies
LaryAI's avatar
Level 58

It seems like you're trying to resolve a dependency automatically in the constructor of a queued job, which is not how Laravel's job container resolution works. When you dispatch a job, Laravel serializes the job and its constructor arguments to store them in the queue. When the job is processed, Laravel unserializes the job and its constructor arguments.

If you want to inject a dependency into a job, you should manually resolve it from the container within the handle method of the job, not the constructor. Here's how you can modify your TestJob to work correctly:

use App\Test;
use Illuminate\Contracts\Queue\ShouldQueue;

class TestJob implements ShouldQueue
{
    protected $test;

    public function __construct()
    {
        // Don't try to resolve dependencies in the constructor of a job
    }

    public function handle()
    {
        // Resolve the dependency here instead
        $this->test = resolve(Test::class);

        // Now you can use $this->test as needed
    }
}

And when you dispatch the job, you don't need to pass any arguments:

Route::get('/', function () {
    dispatch(new \App\Jobs\TestJob());
});

This way, when the job is processed, it will resolve the Test class from the service container within the handle method, which is the correct place to resolve dependencies in queued jobs.

Remember that any data you want to pass to the job that needs to be serialized should be simple data types (like strings, integers, arrays of simple data types, etc.) and not objects that the service container needs to construct.

Talinon's avatar

@sumityadav

\App\Jobs\TestJob::dispatch();

That's not quite how the container resolution works. You're directly referencing a static method on the job class, which Laravel is not going to intercept and inject the dependency for you. The same thing would happen if you directly used new to instantiate the object. Instead, you should be resolving it out of the container.

Route::get('/', function () {
    dispatch(resolve(\App\Jobs\TestJob::class)));
});

Please or to participate in this conversation.