Gestion des erreurs d'import xml pour données inconsistentes

This commit is contained in:
Olivier Legat 2021-07-28 11:43:26 +02:00
parent 3ef393878f
commit 0663305ea7
5 changed files with 159 additions and 21 deletions

View file

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidXmlException extends Exception
{
}

View file

@ -4,19 +4,30 @@ namespace App\Jobs;
use App\Enums\LogType;
use App\Exceptions\AbortXmlImportException;
use App\Exceptions\InvalidXmlException;
use ErrorException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
use SimpleXMLElement;
use Throwable;
use TypeError;
abstract class XmlJobs extends XArcadiaFlux
{
/**
* invalid xml folder
*
* @var string
*/
protected $invalidFolderName = 'invalid';
/**
* The columns to take from the XML
* subcolumns can be added with a dot
@ -70,30 +81,33 @@ abstract class XmlJobs extends XArcadiaFlux
$this->log(sprintf(' traitement %d sur %s ', $progress++, $total));
/** @var string $data */
$data = $disk->get($filename);
try {
if ($this->encapsulate) {
$data = sprintf('<Root>%s</Root>', $data);
/** @var SimpleXMLElement|false */
$xmlContent = simplexml_load_string($data);
} else {
/** @var SimpleXMLElement|false */
$xmlContent = simplexml_load_string($data, null, LIBXML_NOCDATA | LIBXML_PARSEHUGE);
}
/** @var string $data */
$data = $disk->get($filename);
try {
if ($this->encapsulate) {
$data = sprintf('<Root>%s</Root>', $data);
/** @var SimpleXMLElement|false */
$xmlContent = simplexml_load_string($data);
} else {
/** @var SimpleXMLElement|false */
$xmlContent = simplexml_load_string($data, null, LIBXML_NOCDATA | LIBXML_PARSEHUGE);
}
if ($xmlContent === false) {
$this->log(
sprintf('File %s could not be parsed (simplexml_load_string)', $filename),
LogType::critical()
);
continue; // ignore the file
if ($xmlContent === false) {
throw new InvalidXmlException('XML is Invalid (simplexml_load_string returned false)');
// $this->log(
// sprintf('File %s could not be parsed (simplexml_load_string)', $filename),
// LogType::critical()
// );
// continue; // ignore the file
}
} catch (ErrorException $e) {
throw new InvalidXmlException('XML is Invalid', 0, $e);
}
$data = $this->extract($xmlContent);
$lines = [];
foreach ($data[$entityName] as $traca) {
$line = $this->processFields($traca);
@ -102,6 +116,7 @@ abstract class XmlJobs extends XArcadiaFlux
}
$lines[] = $line;
}
// Mise en base des données
foreach ($lines as $line) {
/** @var array Unique Key columns */
@ -116,7 +131,7 @@ abstract class XmlJobs extends XArcadiaFlux
// @todo : should we put that method in a interface ?
$savedData->updateLocalProductInfo();
}
} catch (ErrorException | TypeError | QueryException $e) {
} catch (InvalidXmlException $e) {
$message = $e->getMessage();
if (get_class($e) === QueryException::class) {
$message = implode(' ', $e->errorInfo);
@ -130,17 +145,49 @@ abstract class XmlJobs extends XArcadiaFlux
),
LogType::critical()
);
$this->sendInvalidXmlEmail($filename, $e);
$this->moveToInvalid($filename);
continue;
} catch (ErrorException | TypeError $e) {
$this->log(
sprintf(
'CRITICAL: File %s could not be parsed => %s on line %d',
$filename,
$e->getMessage(),
$e->getLine()
),
LogType::critical()
);
$this->sendErrorParseEmailToServiceWeb($e);
continue; // ignore the file
} catch (AbortXmlImportException $e) {
$this->log(
sprintf(
'File %s could not be parsed => %s on line %d',
'ERROR: File %s could not be parsed => %s on line %d',
$filename,
$e->getMessage(),
$e->getLine()
),
LogType::error()
);
$this->sendErrorParseEmailToServiceWeb($e);
$this->moveToInvalid($filename);
continue; // ignore the file
} catch (FileNotFoundException | QueryException $e) {
// This happens because of concurent xml jobs read
$message = $e->getMessage();
if (get_class($e) === QueryException::class) {
$message = implode(' ', $e->errorInfo);
}
$this->log(
sprintf(
'ERROR: File %s could not be read (not found) => %s on line %d',
$filename,
$message,
$e->getLine()
),
LogType::debug()
);
continue; // ignore the file
}
$this->moveToArchives($filename);
@ -276,4 +323,57 @@ abstract class XmlJobs extends XArcadiaFlux
}
return $isXml;
}
protected function sendErrorParseEmailToServiceWeb(Throwable $e)
{
Mail::send('emails.exception', [
'exception' => $e,
'appName' => config('app.name'),
], function ($m) {
$m->to(
config('mail.contactsList.serviceweb'),
sprintf('%s Exception Handler', config('app.name'))
)->subject('Could not eat INCO, because of its toxicity !');
});
}
protected function sendInvalidXmlEmail(string $filename, Throwable $e)
{
$message = $e->getPrevious()->getMessage();
Mail::send('emails.invalidxml', [
'strError' => sprintf(
'File %s could not be parsed => %s on line %d',
$filename,
$message,
$e->getLine()
),
'appName' => config('app.name'),
'filename' => $filename,
'exception' => $e->getPrevious()
], function ($m) {
$m->to(
config('mail.contactsList.invalidInco'),
sprintf('%s Exception Handler', config('app.name'))
)->subject('You sent me an invalid XML file, please check its validity before resend ASAP.');
});
}
/**
* Move the file to invalid folder
*
* @param string $filename
* @return void
*/
protected function moveToInvalid(string $filename): void
{
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->invalidFolderName, $filename))) {
$this->log(
sprintf(
'File %s could not be moved to invalid folder name',
$filename
),
LogType::warning()
);
}
}
}

View file

@ -89,7 +89,8 @@ return [
],
'contactsList' => [
'serviceweb' => env('MAIL_LIST_SERVICEWEB', 'informatique.internet@ldc.fr')
'serviceweb' => explode(",", env('MAIL_LIST_SERVICEWEB', 'informatique.internet@ldc.fr')),
'invalidInco' => explode(",", env('MAIL_LIST_INVALIDINCO', 'informatique.internet@ldc.fr,vincent.pierre@ldc.fr,christelle.manceau@ldc.fr')),
],
/*

View file

@ -0,0 +1,28 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{ $appName }} Exception Handler: Unvalid XML</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>{{ $appName }} à reçu un fichier xml non valide !!</h1>
<h3>{{ $strError }}</h3>
<h4>FICHIER: `{{ $filename }}`</h4>
<p>{{ $exception->getMessage() }}</p>
<p>
<b>date</b> : {{ date('Y-m-d H:i:s') }}<br>
<b>code</b> : {{$exception->getCode() }}<br>
<b>file</b> : {{$exception->getFile() }}<br>
<b>line</b> : {{$exception->getLine() }}<br>
<p>
<b>Merci de corriger le flux, puis de renvoyer le fichier en provoquant un mouvement EAI.</b><br><br>
<b>Pour rappel</b>&nbsp;: Il est recommandé de valider un fichier XML à l'aide d'un <b>DTD</b><br>
<a href="https://www.w3schools.com/xml/xml_dtd.asp">https://www.w3schools.com/xml/xml_dtd.asp</a><br>
ou bien <b>XSD</b> <a href="https://www.w3schools.com/xml/schema_intro.asp">https://www.w3schools.com/xml/schema_intro.asp</a><br><br>
<b>Pourquoi&nbsp;?</b><br>
Pour que les concepteurs puissent identifier les types de données qui transitent (les champs et leurs caratéristiques)<br>
Mais aussi valider les fichiers reçus.
</p>
<p>En l'absence de données valides, cette fiche va être mise de coté.</p>
</body>
</html>

View file