grozavule's avatar

File Upload

I have a form that is meant to upload multiple files via AJAX. The JSON response that is returned just says "The files.x failed to upload". Here is my controller that handles the request:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Quote;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;

class QuoteRequestController extends Controller
{
    public function index()
    {
        return view('quote.index', ['pageIdentifier' => 'quote']);
    }

    public function store(Request $request)
    {
        $validInput = Validator::make($request->all(), [
            'first_name' => ['required', 'string', 'max:35'],
            'last_name' => ['required', 'string', 'max:50'],
            'email_address' => ['required', 'email:rfc,dns', 'max:125'],
            'phone_number' => ['required', 'string', 'max:15', 'regex:/^[+]*[0-9]*\s*[(]*[0-9]{3}[)]*[\s\.\-]*[0-9]{3}[\.\-]*[0-9]{4}$/'],
            'quantity' => ['required', 'integer','min:1'],
            'material' => ['required', 'string', 'max:50'],
            'due_date' => ['required', 'after:today'],
            'company_name' => ['required', 'string', 'max:75'],
            'files.*' => ['required']
        ]);

        if($validInput->fails())
        {
            return Response::json($validInput->errors(), 400);
        }

        $quote = new Quote;
        $quote->first_name = $request->input('first_name');
        $quote->last_name = $request->input('last_name');
        $quote->email_address = $request->input('email_address');
        $quote->phone_number = $request->input('phone_number');
        $quote->quantity = $request->input('quantity');
        $quote->material = $request->input('material');
        $quote->due_date = $request->input('due_date');
        $quote->company_name = $request->input('company_name');
        $quote->save();

        if($request->hasfile('files'))
        {
            $counter = 0;
            foreach ($request->file('files') as $file) {
                $path = $file->storeAs('quote-requests', $request->input('company_name') . $counter . '.' . $file->extension());
                return Response::json(['path' => $path], 400);
                $quote->files()->create([
                    'quote_id' => $quote->id,
                    'file_uri' => $path
                ]);
            }
        } else
        {
            return Response::json(['files' => 'No files were received. Please try again'], 400);
        }

        return Response::json(['success' => 'Thank you. Your request has been received.'], 200);
    }
}

The error isn't one that I pass back, so Laravel must be automatically generating it. How can I work around this error?

0 likes
8 replies
neilstee's avatar

@grozavule have you check the following:

  • single file upload
  • small file upload (less than kb)
  • make sure your ajax request has a correct type request (FormData)
Tray2's avatar

What does your javascript look like?

Here is an example that I use with plain JS

function upload(e) {
	e.preventDefault();

	var xhttp = new XMLHttpRequest();
    var form = document.querySelector('#upload-form');
		var formData = new FormData(form);
	
   	xhttp.onreadystatechange = function() {
      if (xhttp.readyState === 4 && xhttp.status === 200) {
					show(document.querySelector('#upload-successfull'));
     	}
    }
    xhttp.open('POST', '/uploads');
    xhttp.send(formData);
	}
grozavule's avatar

Here is my JS

function clearForm() {
    let errorElement = document.querySelector('.form-alert');
    let fields = form.querySelectorAll('input');
    let containers = form.querySelectorAll('form-control-container');

    errorElement.classList.add('hidden');
    Array.prototype.forEach.call(fields, function (field) {
        field.classList.remove('is-invalid');
    });
    Array.prototype.forEach.call(containers, function (container) {
        let feedback = container.querySelector('.invalid-feedback');
        feedback.style.visibility = 'hidden';
        feedback.innerHTML = '';
    });
}

function displayError(element, message) {
    let alert = document.querySelector('.form-alert');
    let className = element.replace('_', '-').replace(/\.[0-9]+/g, '');
    let parent = document.querySelector('.' + className);
    let input = parent.querySelector('input');
    let feedback = parent.querySelector('.invalid-feedback');

    alert.classList.remove('hidden');
    input.classList.add('is-invalid');
    feedback.style.visibility = 'visible';
    feedback.style.display = 'inline-block';
    feedback.innerHTML = message;
}

function displaySuccess(message) {
    let formSuccess = document.querySelector('.form-alert');
    formSuccess.classList.remove('alert-danger').add('alert-success');
    formSuccess.innerHTML = message;
}

function scrollToFormTop() {
    document.documentElement.scrollTop = window.innerHeight;
}

function showFiles(files)
{
    label.textContent = files.length > 1 ? (input.getAttribute('data-multiple-caption') || '').replace('{count}', files.length) : files[0].name;
}

form = document.querySelector('#request');
var input = form.querySelector('input[type="file"]');
var label = form.querySelector('label[for="file"]');

input.style.background = '#cc0000';
input.addEventListener('change', function (e) {
    droppedFiles = e.target.files;
    console.log(droppedFiles);
    showFiles(e.target.files);
});

form.addEventListener('submit', function (e) {
    e.preventDefault();

    var ajax = new XMLHttpRequest();
    var data = new FormData(this);

    ajax.open(this.getAttribute('method'), this.getAttribute('action'), true);

    ajax.onload = function () {
        if (ajax.status >= 200 && ajax.status < 400) {
            var data = JSON.parse(ajax.responseText);
            scrollToFormTop();
            displaySuccess(data.success);
        } else {
            scrollToFormTop();
            let errors = JSON.parse(ajax.responseText);
            for (let key in errors) {
                displayError(key, errors[key]);
            }
        }
    };

    ajax.onerror = function () {
        alert('Error. Please, try again!');
    };

    ajax.send(data);
});
Tray2's avatar

Have you set the enctype in your view?

enctype="multipart/form-data"
grozavule's avatar

Here is my view

@extends('layouts.base')

@section('title', 'Request A Quote For Your Next CNC Project')
@section('keywords', 'quote, cam project, cnc project, contract manufacturing, custom prototype')
@section('description', 'Let Buck\'s CNC Machining help you with your next CNC project. Request a quote today.')

@section('hero-content')
    <div class="hero-cta">
        <h1 class="hero-main-line">LET US</h1>
        <h4 class="hero-secondary-line">WIN YOUR BUSINESS</h4>
        <p>Several companies have put their trust in us and have been pleased with the result.</p>
        <p>We would love the chance to work with you. Please fill out the form below, and our skilled staff will be in contact with you shortly.</p>
    </div>
@endsection

@section('body-content')
    <div class="alert alert-danger hidden form-alert">
        Oops! There were some issues with the information you provided. Please check the feedback below.
    </div>
    <form id="request" method="post" action="{{ route('quote.store') }}" enctype="multipart/form-data">
        <fieldset>
            <legend>CONTACT INFORMATION</legend>
            <div class="first-name form-control-container">
                <label for="first_name">FIRST NAME</label>
                <input type="text" id="first_name" class="form-control" name="first_name" maxlength="35" value="{{ old('first_name') }}"/>
                <div class="invalid-feedback"></div>
            </div>
            <div class="last-name form-control-container">
                <label for="last_name">LAST NAME</label>
                <input type="text" id="last_name" class="form-control" name="last_name" maxlength="50" value="{{ old('last_name') }}"/>
                <div class="invalid-feedback"></div>
            </div>
            <div class="email-address form-control-container">
                <label for="email_address">EMAIL ADDRESS</label>
                <input type="email" id="email_address" class="form-control" name="email_address" maxlength="125" value="{{ old('email_address') }}"/>
                <div class="invalid-feedback"></div>
            </div>
            <div class="phone-number form-control-container">
                <label for="phone_number">PHONE NUMBER</label>
                <input type="tel" id="phone_number" class="form-control" name="phone_number" maxlength="15" value="{{ old('phone_number') }}"/>
                <div class="invalid-feedback"></div>
            </div>
        </fieldset>
        <fieldset>
            <legend>PROJECT INFORMATION</legend>
            <div class="quantity form-control-container">
                <label for="quantity">QUANTITY</label>
                <input type="number" id="quantity" class="form-control" name="quantity" maxlength="5" value="{{ old('quantity') ?? 1 }}"/>
                <div class="invalid-feedback"></div>
            </div>
            <div class="material form-control-container">
                <label for="material">MATERIAL</label>
                <input type="text" id="material" class="form-control" name="material" maxlength="50" value="{{ old('material') }}"/>
                <div class="invalid-feedback"></div>
            </div>
            <div class="due-date form-control-container">
                <label id="due_date">DUE DATE</label>
                <input type="date" id="due_date" class="form-control" name="due_date" maxlength="10" value="{{ old('due_date') }}"/>
                <div class="invalid-feedback"></div>
            </div>
            <div class="company-name form-control-container">
                <label for="company_name">COMPANY NAME</label>
                <input type="text" id="company_name" class="form-control" name="company_name" maxlength="75" value="{{ old('company_name') }}"/>
                <div class="invalid-feedback"></div>
            </div>
        </fieldset>
        <fieldset class="files">
            <legend>FILES</legend>
            <div class="alert alert-danger invalid-feedback"></div>
            <p class="text-left">Acceptable file types include *.3dm, *.asm, *.cam360, *.CATPart, *.CATProduct,
                *.dwg, *.dxf, *.f3d, *.fbx, *.g, .iam, *.ige, *.iges, *.igs, *.ipt, *.neu,
                *.obj, *.prt, *.sab, *.sat, *.skp, *.sldasm, *.sldprt, *.smb, *.smt, *.ste,
                *.step, *.stl, *.stp, *.wire, *.x_b, *.x_t, *.123dx</p>

            <div class="box js">
                <div class="box__input">
                    <input type="file" name="files[]" id="file" class="box__file" data-multiple-caption="{count} files selected" multiple />
                    <label for="file"><strong>Choose a file</strong><span class="box__dragndrop"> or drag it here</span>.</label>
                </div>
            </div>
        </fieldset>
        @csrf
        <input type="submit" class="button w-100 color-primary" value="SEND">
    </form>
@endsection

@section('js')
    @parent
    <script src="{{ asset('js/fileUpload.js') }}"></script>
@endsection
Tray2's avatar

What happens if you change this line in your validations?

 'files.*' => ['required']

to

 'file' => ['required']
grozavule's avatar

I changed my validation to both of the following:

'file' => ['required']
'files' => ['required']

The first returned an error that the file field was required. The second returned another error:

Symfony\Component\Mime\Exception\InvalidArgumentException: The "" file does not exist or is not readable. in file /mnt/c/Users/eric.drake/Documents/Sites/dev.buckscnc.com/vendor/symfony/mime/FileinfoMimeTypeGuesser.php on line 50

Please or to participate in this conversation.