Skip to content

Commit

Permalink
Raise a ImportFailed event when an import job has failed (#1944)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickbrouwers committed Apr 5, 2019
1 parent b26dcc6 commit 9da01af
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 21 deletions.
11 changes: 10 additions & 1 deletion src/ChunkReader.php
Expand Up @@ -18,6 +18,7 @@
use Maatwebsite\Excel\Concerns\WithChunkReading;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Imports\HeadingRowExtractor;
use Throwable;

class ChunkReader
{
Expand Down Expand Up @@ -50,6 +51,7 @@ public function read(WithChunkReading $import, Reader $reader, TemporaryFile $te

for ($currentRow = $startRow; $currentRow <= $totalRows[$name]; $currentRow += $chunkSize) {
$jobs->push(new ReadChunk(
$import,
$reader->getPhpSpreadsheetReader(),
$temporaryFile,
$name,
Expand All @@ -67,7 +69,14 @@ public function read(WithChunkReading $import, Reader $reader, TemporaryFile $te
}

$jobs->each(function ($job) {
dispatch_now($job);
try {
dispatch_now($job);
} catch (Throwable $e) {
if (method_exists($job, 'failed')) {
$job->failed($e);
}
throw $e;
}
});

if ($import instanceof WithProgressBar) {
Expand Down
29 changes: 29 additions & 0 deletions src/Events/ImportFailed.php
@@ -0,0 +1,29 @@
<?php

namespace Maatwebsite\Excel\Events;

use Throwable;

class ImportFailed
{
/**
* @var Throwable
*/
public $e;

/**
* @param Throwable $e
*/
public function __construct(Throwable $e)
{
$this->e = $e;
}

/**
* @return Throwable
*/
public function getException(): Throwable
{
return $this->e;
}
}
16 changes: 15 additions & 1 deletion src/Jobs/AfterImportJob.php
Expand Up @@ -2,13 +2,16 @@

namespace Maatwebsite\Excel\Jobs;

use Maatwebsite\Excel\Events\ImportFailed;
use Maatwebsite\Excel\HasEventBus;
use Maatwebsite\Excel\Reader;
use Illuminate\Bus\Queueable;
use Maatwebsite\Excel\Concerns\WithEvents;
use Throwable;

class AfterImportJob
{
use Queueable;
use Queueable, HasEventBus;

/**
* @var WithEvents
Expand Down Expand Up @@ -42,4 +45,15 @@ public function handle()

$this->reader->afterImport($this->import);
}

/**
* @param Throwable $e
*/
public function failed(Throwable $e)
{
if ($this->import instanceof WithEvents) {
$this->registerListeners($this->import->registerEvents());
$this->raise(new ImportFailed($e));
}
}
}
41 changes: 32 additions & 9 deletions src/Jobs/ReadChunk.php
Expand Up @@ -2,6 +2,10 @@

namespace Maatwebsite\Excel\Jobs;

use Maatwebsite\Excel\Concerns\WithChunkReading;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\ImportFailed;
use Maatwebsite\Excel\HasEventBus;
use Maatwebsite\Excel\Sheet;
use Illuminate\Bus\Queueable;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
Expand All @@ -12,10 +16,16 @@
use Maatwebsite\Excel\Imports\HeadingRowExtractor;
use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
use Maatwebsite\Excel\Transactions\TransactionHandler;
use Throwable;

class ReadChunk implements ShouldQueue
{
use Queueable;
use Queueable, HasEventBus;

/**
* @var WithChunkReading
*/
private $import;

/**
* @var IReader
Expand Down Expand Up @@ -48,15 +58,17 @@ class ReadChunk implements ShouldQueue
private $chunkSize;

/**
* @param IReader $reader
* @param TemporaryFile $temporaryFile
* @param string $sheetName
* @param object $sheetImport
* @param int $startRow
* @param int $chunkSize
* @param WithChunkReading $import
* @param IReader $reader
* @param TemporaryFile $temporaryFile
* @param string $sheetName
* @param object $sheetImport
* @param int $startRow
* @param int $chunkSize
*/
public function __construct(IReader $reader, TemporaryFile $temporaryFile, string $sheetName, $sheetImport, int $startRow, int $chunkSize)
public function __construct(WithChunkReading $import, IReader $reader, TemporaryFile $temporaryFile, string $sheetName, $sheetImport, int $startRow, int $chunkSize)
{
$this->import = $import;
$this->reader = $reader;
$this->temporaryFile = $temporaryFile;
$this->sheetName = $sheetName;
Expand All @@ -66,7 +78,7 @@ public function __construct(IReader $reader, TemporaryFile $temporaryFile, strin
}

/**
* @param TransactionHandler $transaction
* @param TransactionHandler $transaction
*
* @throws \Maatwebsite\Excel\Exceptions\SheetNotFoundException
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
Expand Down Expand Up @@ -114,4 +126,15 @@ public function handle(TransactionHandler $transaction)
$sheet->disconnect();
});
}

/**
* @param Throwable $e
*/
public function failed(Throwable $e)
{
if ($this->import instanceof WithEvents) {
$this->registerListeners($this->import->registerEvents());
$this->raise(new ImportFailed($e));
}
}
}
27 changes: 17 additions & 10 deletions src/Reader.php
Expand Up @@ -4,6 +4,7 @@

use InvalidArgumentException;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Events\ImportFailed;
use Maatwebsite\Excel\Transactions\TransactionManager;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use Maatwebsite\Excel\Events\AfterImport;
Expand All @@ -25,6 +26,7 @@
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Maatwebsite\Excel\Exceptions\SheetNotFoundException;
use Maatwebsite\Excel\Exceptions\NoTypeDetectedException;
use Throwable;

class Reader
{
Expand Down Expand Up @@ -101,18 +103,23 @@ public function read($import, $filePath, string $readerType = null, string $disk
return (new ChunkReader)->read($import, $this, $this->currentFile);
}

$this->loadSpreadsheet($import, $this->reader);

($this->transaction)(function () use ($import) {
foreach ($this->sheetImports as $index => $sheetImport) {
if ($sheet = $this->getSheet($import, $sheetImport, $index)) {
$sheet->import($sheetImport, $sheet->getStartRow($sheetImport));
$sheet->disconnect();
try {
$this->loadSpreadsheet($import, $this->reader);

($this->transaction)(function () use ($import) {
foreach ($this->sheetImports as $index => $sheetImport) {
if ($sheet = $this->getSheet($import, $sheetImport, $index)) {
$sheet->import($sheetImport, $sheet->getStartRow($sheetImport));
$sheet->disconnect();
}
}
}
});
});

$this->afterImport($import);
$this->afterImport($import);
} catch (Throwable $e) {
$this->raise(new ImportFailed($e));
throw $e;
}

return $this;
}
Expand Down
58 changes: 58 additions & 0 deletions tests/Concerns/WithChunkReadingTest.php
Expand Up @@ -2,9 +2,11 @@

namespace Maatwebsite\Excel\Tests\Concerns;

use Exception;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\AfterImport;
use Maatwebsite\Excel\Events\BeforeImport;
use Maatwebsite\Excel\Events\ImportFailed;
use Maatwebsite\Excel\Reader;
use PhpOffice\PhpSpreadsheet\Reader\IReader;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
Expand All @@ -21,6 +23,7 @@
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Tests\Data\Stubs\Database\User;
use Maatwebsite\Excel\Tests\Data\Stubs\Database\Group;
use Throwable;

class WithChunkReadingTest extends TestCase
{
Expand Down Expand Up @@ -400,4 +403,59 @@ public function batchSize(): int
$this->assertCount(10, DB::getQueryLog());
DB::connection()->disableQueryLog();
}

/**
* @test
*/
public function can_catch_job_failed_in_chunks()
{
$import = new class implements ToModel, WithChunkReading, WithEvents
{
use Importable;

public $failed = false;

/**
* @param array $row
*
* @return Model|null
*/
public function model(array $row)
{
throw new Exception('Something went wrong in the chunk');
}

/**
* @return int
*/
public function chunkSize(): int
{
return 1;
}

/**
* @return array
*/
public function registerEvents(): array
{
return [
ImportFailed::class => function (ImportFailed $event) {
Assert::assertInstanceOf(Throwable::class, $event->getException());
Assert::assertEquals('Something went wrong in the chunk', $event->e->getMessage());

$this->failed = true;
},
];
}
};

try {
$import->import('import-users.xlsx');
} catch (Throwable $e) {
$this->assertInstanceOf(Exception::class, $e);
$this->assertEquals('Something went wrong in the chunk', $e->getMessage());
}

$this->assertTrue($import->failed, 'ImportFailed event was not called.');
}
}

0 comments on commit 9da01af

Please sign in to comment.