madprabh's avatar

Ensure file uploads before processing further

Hey Folks,

I have a situation where I want to ensure that files get uploaded completely before a batch of jobs have to run. Something like this

foreach ($files as $file) {
                $projectData = $projectService->handleFileUpload($project, $file);

                if (strtolower($file->getClientOriginalExtension()) === 'csv') {
                    $projectData->csv_text_table_name = $csvFileService->getTextTableNameFromCsvName($file, $projectData->id);
                    $projectData->csv_data_type_table_name = $csvFileService->getDataTypeTableNameFromCsvName($file, $projectData->id);
                    $projectData->save();
                }
                //dispatch a job to process csv file
                Bus::batch([
                    [
                        new CreateCsvTextTable($projectData),
                        new AddRecordsCsvTextTable($projectData)
                    ],

                    
                ])->then(function (Batch $batch) {
                    // All jobs completed successfully...log the success in a separate table with batch id and project data id

                })->dispatch();
            }

But, I think before the file gets uploaded the jobs are dispatched and that causes problems since the jobs depends on reading the csv files if they are not uploaded completely there is no way for the jobs to run successfully.

0 likes
3 replies
LaryAI's avatar
Level 58

Certainly! The problem here is that the job(s) to process the CSV are being dispatched before confirming that the file upload has been fully written to disk/storage. In PHP/Laravel, by the time the loop in your code is hit (e.g. in a controller method), uploaded files should already be fully transferred — unless you are copying large files manually as part of handleFileUpload.

If your handleFileUpload method is truly synchronous and returns only after the file is stored, then you can safely dispatch jobs after calling it.
However, if it uses an async method, streams the file, or returns before the file is fully uploaded, you need to guarantee it’s finished before queuing jobs.

Here’s how to ensure you only dispatch jobs after the file is in place:

Assuming Synchronous Uploads If $projectService->handleFileUpload completes only after the file is stored (say, with $file->store() or $file->move()), you’re safe:

foreach ($files as $file) {
    // This must _fully_ save the file before returning
    $projectData = $projectService->handleFileUpload($project, $file);

    if (strtolower($file->getClientOriginalExtension()) === 'csv') {
        $projectData->csv_text_table_name = $csvFileService->getTextTableNameFromCsvName($file, $projectData->id);
        $projectData->csv_data_type_table_name = $csvFileService->getDataTypeTableNameFromCsvName($file, $projectData->id);
        $projectData->save();
    }

    // Only run these after file is available
    Bus::batch([
        new CreateCsvTextTable($projectData),
        new AddRecordsCsvTextTable($projectData)
    ])
    ->then(function (Batch $batch) use ($projectData) {
        // Log success with $batch->id, $projectData->id
    })
    ->dispatch();
}

If handleFileUpload is Asynchronous / Streams Data Let’s say you upload large files by streaming, chunking, or async backend. In that case, you must delay job dispatch until the upload/copy completes.

Option 1: Make Upload Synchronous Change your handleFileUpload so that it is blocking. Use $file->store() or $file->storeAs() and don't return until done.

public function handleFileUpload($project, $file)
{
    // Blocking file storage
    $filePath = $file->storeAs('projects/'.$project->id, $file->getClientOriginalName());

    // Save path on projectData etc.
    // ...your logic

    return $projectData;
}

Option 2: Dispatch jobs on File Stored Event If your upload is async (user uploads in browser, then your server gets a webhook, etc), trigger the job inside the upload completion handler/event.

Event::listen(FileUploaded::class, function ($event) {
    $file = $event->file;
    $projectData = ...; // Find or create by $file/$event info

    // Process CSV logic as above...

    Bus::batch([
        new CreateCsvTextTable($projectData),
        new AddRecordsCsvTextTable($projectData)
    ])->dispatch();
});

Good Practices:

  • Double-check that path-to-file is correct before dispatching jobs.
  • In jobs, always validate that file exists before reading:
    if (!Storage::exists($filePath)) {
        // Retry, fail, or alert
    }
    

Summary:

  • If your upload is synchronous, you’re okay.
  • If there’s even the slightest risk the file is not yet stored when jobs run, make the file upload blocking/synchronous, or dispatch the jobs only after the file is guaranteed present (e.g. on an upload-complete event).

Let me know if you need a tailored solution for an async scenario!

Glukinho's avatar

How do you handle upload? Can you show route and controller? I'm pretty sure a controller starts only after a request is fully received, all files included.

martinbean's avatar

@madprabh If you’re uploading files in a loop like that, then assign the jobs to an array, and only dispatch that batch of jobs after the loop:

$jobs = [];

foreach ($files as $file) {
    $projectData = $projectService->handleFileUpload($project, $file);

    if (strtolower($file->getClientOriginalExtension()) === 'csv') {
        // Snipped...
    }

    $jobs[] = [
        new CreateCsvTextTable($projectData),
        new AddRecordsCsvTextTable($projectData),
    ];
}

Bus::batch($jobs);

However, this code looks a bit “verbose” and inefficient. I imagine there’s a better way to do what you’re trying to do if you explain what that is.

  • Why are you uploading multiple files?
  • What is “project data”?
  • Why does it look like non-CSV files can be uploaded if you’re then dispatching jobs to create a CSV files and add records to them?
  • It also makes no sense you’re allowing a CSV file to be uploaded… and then creating a CSV file from that CSV file.

Please or to participate in this conversation.