Framework: Job chaining does not allow constructor arguments on first job in chain

Created on 22 Sep 2017  路  16Comments  路  Source: laravel/framework

  • Laravel Version: 5.5.12
  • PHP Version: 7.1.8
  • Database Driver & Version: MySQL (MariaDB 10.2.8)

Description:

When chaining queue jobs, the first job in the cain (the job where the chain is glued to) can not have any required constructor parameters.

Steps To Reproduce:

  1. Create a chain as mentioned in https://laravel.com/docs/5.5/queues#job-chaining
  2. Change the definition of the first in chain job (ProcessPodcast) to have a required paramter
  3. Run the code
  4. Type error: Too few arguments to function App\JobsProcessPodcast::__construct(), 0 passed in .....

Most helpful comment

For those struggling, I managed to send differents constructor parameters like this:

getInvoiceDetails::dispatch($invoiceID1)->chain([
                    new getCurrentExchangeRate($invoiceID2),
                    new createInvoiceXML($invoiceID3),
                    new sendSignedInvoice($invoiceID4),
                    (new checkDocumentStatus($invoiceID5))->delay(3),
                ]);

All 16 comments

You need to pass the required arguments to the dispatch method:

ProcessPodcast::withChain([
    new OptimizePodcast,
    new ReleasePodcast
])->dispatch($requiredArgument);

@themsaid is this documented? What I mean is dispatch() method mentioned somewhere in docs that it accepts parameters?

https://laravel.com/docs/5.5/queues#job-chaining

@Kyslik it doesn't seem so, can you please PR the docs?

@themsaid sure I will take a look at it.

Ran in to this problem myself. It's by no means always practical to pass constructor arguments to dispatch(), as the withChain() method is specifically used to fire off a group of related jobs and they may well have entirely different constructors. Reinstantiating new objects as PendingChain currently does all with the same parameter set (and the same class) makes it substantially less useful as a feature.

Agreed. Ran into this issue today. This is very odd that every chained job is required to have the same parameters. Really limits this awesome feature.

As of today, i've no way to dispatch chained jobs due to this dependency problem. I tried to solve it by capturing payload on AppServiceProvider with Queue::after ... but the payload doesn't come updated, it's the same I inserted via constructor, even though i've done my testing and those are updated in DB and after performing changes on other objects not model inherited, checked with xDebug and also are updated. Just seems that retrieving payload is not a solution at all.
If it only were Model collections i could live with the "re-querying" the entire collection and get over before deployment estimated date, but there is also a custom collection of an internal library i've to work with and needs to be updated. This is how i'm doing it, but looks awful and wish to be able to dispatch in chain without touching payload on another class that i don't really need to.
code in AppServiceProvider::boot

Queue::after(function (JobProcessed $event) {
$job = unserialize($event->job->payload()["data"]["command"]);
if ($job instanceof QueueVouchers) {
// do meaningful stuff.
dd($job->vouchers);
}
});

EDIT:
i've dropped this idea since retrieving the payload will get you "a copy" of whatever $args you passed through that job constructor, but right before handle execution, since i'm listening to the queue and not the job.
For the moment, i coupled all the chained jobs and that's it. Hope to get a fix on this or maybe someone who really knows about queueing jobs has a better hack to share.

For those struggling, I managed to send differents constructor parameters like this:

getInvoiceDetails::dispatch($invoiceID1)->chain([
                    new getCurrentExchangeRate($invoiceID2),
                    new createInvoiceXML($invoiceID3),
                    new sendSignedInvoice($invoiceID4),
                    (new checkDocumentStatus($invoiceID5))->delay(3),
                ]);

@mrbarletta could you share one of the jobs code? This could be the answer I was looking for. Does the param has restrictions (like being primitive types for example)?

I must rescind my previous complaints about this. I was using the syntax that @mrbarletta was using (it was my first instinct); however, I was not running my queue worker, so my jobs weren't getting processed. If anyone else is struggling with the jobs actually processing, make sure you run the appropriate artisan command for starting your queue worker.

@rAfitiiixxx - nothing fancy, the $invoiceID is just a string

class getInvoiceDetails implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $tries = 5;
    public $delayInSeconds = 5 ;
    public $invoiceID;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($invoiceID)
    {
        $this->invoiceID = $invoiceID;
    }

@mrbarletta thanks for the input. It's odd to see public fields like that, what's the catch on this? since once the job is running you can't interact from outside classes and you only get access to the payload (which is a snapshot of the creation of the object).
I guess i run into trouble when I tried to pass an object which calls a closure at some point (custom lib, made by a former co-worker) and Job dispatcher doesn't allow to serialize closures.
So, i'm not sure but looks like as long as you're chaining jobs with primitive type args, arrays and POPOs (Plain Old Php Objects, basically domain and complex free ones) you will be okay, but if one of the objects passed through constructor has a closure (probably a callback, could be framework attached) Job dispatching will fail with no extra info.
I guess that if this is the case, the exception thrown could be a little more explanatory and the documentation should clarify on this.

@rAfitiiixxx - as you said the job queue stores an serialized object which contains the data at runtime, and I had the same issue with closures so I removed them.

I gues if you need the callback response to call the job, you will need to chain it after the callback job is run, store that info on the DB, and then call the job that needs that info. Or if you need to have the response sent to the other job constructor, you can dispatch it right inside the callback job, thats was the way to chain before this withChain method.

@mrbarletta: (...) Or if you need to have the response sent to the other job constructor, you can dispatch it right inside the callback job, thats was the way to chain before this withChain method.

Wish it could be done like that, are you refering to "callback job" as the handle() method on it? Having a Job calling another Job just doesn't work (it breaks with an exception, so i found this chaining thing):

/**
 * @throws \Exception
 * @throws \Throwable
 */
public function handle()
{
    Queue::push(new VouchersJob($this->stateStr, ...$this->vouchersCollection));
}

@rAfitiiixxx Check the commit where this withChain functionality was created, If I remember correctly thats were I read how was done before

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SachinAgarwal1337 picture SachinAgarwal1337  路  3Comments

felixsanz picture felixsanz  路  3Comments

Fuzzyma picture Fuzzyma  路  3Comments

shopblocks picture shopblocks  路  3Comments

kerbylav picture kerbylav  路  3Comments