SpartnerNL / Laravel-Excel

🚀 Supercharged Excel exports and imports in Laravel
https://laravel-excel.com
MIT License
12.26k stars 1.91k forks source link

Can't Excel::import() after HeadingRowImport #1838

Closed zanozik closed 5 years ago

zanozik commented 5 years ago

When using HeadingRowImport for file validation, as suggested here it's impossible to run Excel::import() with the same uploaded file, since it delete it.

ghost commented 5 years ago

Thanks for submitting the ticket. Unfortunately the information you provided is incomplete. We need to know which version you use and how to reproduce it. Please include code examples. Before we can pick it up, please check (https://github.com/Maatwebsite/Laravel-Excel/blob/3.0/.github/ISSUE_TEMPLATE.md) and add the missing information. To make processing of this ticket a lot easier, please make sure to check (https://laravel-excel.maatwebsite.nl/docs/3.0/getting-started/contributing) and double-check if you have filled in the issue template correctly. This will allow us to pick up your ticket more efficiently. Issues that follow the guidelines correctly will get priority over other issues.

GlennM commented 5 years ago

Please provide more information according to the issue template.

The following should work:

$headings = (new HeadingRowImport)->toArray('users.xlsx');

/**
* Perform whatever validation on the headings here
*/

Excel::import(new UsersImport, 'users.xlsx');

return redirect('/')->with('success', 'All good!');
jhonja93 commented 5 years ago

Not work when you let user upload the document, and you want validate $request->file, because the file once was read by HeadingRowImport is remove from temporary space.

You need to store the file and then validate.

patrickbrouwers commented 5 years ago

In this situation I'd suggest storing the file to a disk, and using that file instead of using the uploaded file twice.

zanozik commented 5 years ago

Thank you for answers! Since moving a file to storage was extremely inconvenient for the project, I have designed the workflow differently by subscribing with Reader::listen globally (validation by header used project-wide). Could share my code if anyone interested.

patrickbrouwers commented 5 years ago

@zanozik sounds like a nice solution as well :) Feel free to share! Can definitely help someone!

zanozik commented 5 years ago

So this is the most cleanest project-wide way I could come up with:

Create regular import file:

namespace App\Imports;

use App\Models\Transports\GasStations\GasStation;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;

class GasStationImport implements ToModel, WithHeadingRow
{
    public $model = GasStation::class; // Only needed for globalization purpose

   // Excel file header    
   public $header = [
        'name',  'country', 'city','address', 'postal', 'zone', 'latitude', 'longitude'
    ];

    public $verifyHeader = true; // Header verification toggle

    public $truncate = true; // We want to truncate table before the import

    /**
    * @param array $row
    *
    * @return \Illuminate\Database\Eloquent\Model|null
    */
    public function model(array $row)
    {
        return new $this->model($row); // We have matching table fields, so we can just do that
        // return new GasStation($row); // If you don't need globalization
    }

}

And in app\Providers\AppServiceProvider.php:

namespace App\Providers;

use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Events\BeforeImport;
use Maatwebsite\Excel\Imports\HeadingRowExtractor;
use Maatwebsite\Excel\Reader;

class AppServiceProvider extends ServiceProvider
{

    public function register()
    {
        Reader::listen(BeforeImport::class, function (BeforeImport $event) {

            $concernable = $event->getConcernable();

            if ($concernable->verifyHeader) {

                //Extracting and "truncating" header from Excel's active worksheet (assuming import is only single worksheet)
                $header = array_filter(HeadingRowExtractor::extract($event->getDelegate()->getDelegate()->getActiveSheet(), $concernable));

               //Checking if headers match and throwing ValidationException otherwise
                throw_unless($header === $concernable->header, ValidationException::withMessages(['message' => trans('validation.unknown_file_template')]));
            }

            //This is where GasStationImport's $truncate and $model come into play. You can put your logic here. 
            if($concernable->truncate) {
                $concernable->model::truncate();
            }
        });
    }
}

In your controller, a method could look like this:

    public function import(Request $request)
    {
        $this->validate($request, [
            'file' => 'required|file|max:1024|mimes:xls,xlsx'
        ]);

        \Excel::import(new GasStationImport(), $request->file('file'));

        return response()->json(['message' => trans('app.import_successful')]);
    }

Typos edited

patrickbrouwers commented 5 years ago

Awesome, looks good @zanozik

freexe commented 6 months ago

This doesn't appear to work anymore but I got it working with:

BeforeSheet::class => function(BeforeSheet $event) {
                $concernable = $event->getConcernable();

                if (@$concernable->verifyHeadings) {
                    //Extracting and "truncating" header from Excel's  worksheet
                    $headings = array_filter(HeadingRowExtractor::extract($event->getSheet()->getDelegate(), $concernable));

                    array_splice($headings, count($concernable->expectedHeadings));

                    //Checking if headers match and throwing ValidationException otherwise
                    throw_unless($headings === $concernable->expectedHeadings, ValidationException::withMessages(['message' => trans('validation.invalid_headings')]));
                }
            },