# Duplicate Jobs

# Overview

It can often be useful to have multiple instances of the same job to a workflow. For example, you might want to encode your podcasts as both mp3 and wav. You could add a EncodePodcast job to your workflow which takes the target output format as a parameter and have them run in parallel.

# Adding multiple instances of the same job

When adding more than one instance of the same job to a workflow, you need to provide an explicit id for each of these jobs. You can do this by passing the id parameter to the workflow's `addJob' method.

Workflow::define('Publish podcast')
    ->addJob(new ProcessPodcast($this->podcast))
    ->addJob(
        new EncodePodcast('mp3', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-mp3'
    )
    ->addJob(
        new EncodePodcast('wav', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-wav'
    );

These ids have to be unique only within the workflow definition. Venture will throw a DuplicateJobException when you try adding a job with an id that already exists in the workflow.

Workflow::define('Publish podcast')
    ->addJob(new ProcessPodcast($this->podcast))
    ->addJob(
        new EncodePodcast('mp3', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-podcast'
    )
    // Throws a `DuplicateJobException` because a job with
    // the id `encode-podcast` already exists in this workflow.
    ->addJob(
        new EncodePodcast('wav', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-podcast'
    );

You don't have to provide explicit ids for every job in a workflow, only jobs that are instances of the same class. This is because Venture will use the FQCN of the job class as the id by default.

# Depending on specific jobs

Things become more interesting when some branch of your workflow depends on only one of those jobs.

In this case, we don't want to depend only on the job that encodes our podcast as flac. Since we have to provide explicit ids for all EncodePodcast job anyways, this becomes a cinch. All we have to do is provide the id of the job as the dependency.

Workflow::define('Publish podcast')
    ->addJob(new ProcessPodcast($this->podcast))
    ->addJob(
        new EncodePodcast('mp3', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-mp3'
    )
    ->addJob(
        new EncodePodcast('wav', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-wav'
    )
    ->addJob(
        new EncodePodcast('flac', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-flac'
    )
    ->addJob(
        new NotifyAudiophileMailingList($this->podcast),
        dependencies: ['encode-flac'] // Use the id as the dependency
    )

Note

Venture always depends on the id of a job internally, even if you don't explicitly provide one. In these cases, the FCQN of the class will be used as the id. So this really isn't any different than depending on regular, non-duplicate job. All we changed is that we provided an explicit id ourselves.

# Duplicate jobs in nested workflows

Venture provides an option to add nested workflows to a workflow. This is useful if you have a collection of jobs that you want to reuse across multiple workflows or want to treat them as their own logical unit.

Venture is smart enough to automatically prefix the ids of all jobs in a nested workflow, so you don't have to worry about accidentally introducing a conflict. You could, for example, extract all jobs related to the flac version of your podcast into a separate workflow.

class FlacPodcastWorkflow extends AbstractWorkflow
{
    public function __construct(private Podcast) {}

    public function definition(): WorkflowDefinition
    {
        return Workflow::define('Flac Workflow')
            ->addJob(new EncodePodcast('flac', $this->podcast))
            ->addJob(
                new NotifyAudiophileMailingList($this->podcast),
                dependencies: [EncodePodcast::class],
            );
    }
}

Note how we didn't need provide an explicit id for the EncodePodcast job because it's the only one of its kind in this workflow. We can still embed this workflow inside another workflow that also contains EncodePodcast jobs.

Workflow::define('Publish podcast')
    ->addJob(new ProcessPodcast($this->podcast))
    ->addJob(
        new EncodePodcast('mp3', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-mp3'
    )
    ->addJob(
        new EncodePodcast('wav', $this->podcast),
        dependencies: [ProcessPodcast::class],
        id: 'encode-wav'
    )
    ->addWorkflow(
        new FlacPodcastWorkflow($this->podcast),
        dependencies: [ProcessPodcast::class],
    );

This will not introduce a conflict because the id of the EncodePodcast job inside the FlacPodcastWorkflow will get namespaced to the inner workflow.

The above example would produce a workflow like this.

Note

While it is technically possible to depend on a job from a nested workflow, you should always depend on the workflow instead. Depending on a job inside a workflow not only breaks encapsulation, it could potentially change the structure of the workflow.

If you find yourself continuously needing to depend on a nested job, it might be a sign that you extract this dependency into its own workflow.