File: D:/HostingSpaces/SBogers10/zipwire.komma.pro/app/KommaApp/Kms/Transfer/AbstractCsvImportService.php
<?php
namespace App\KommaApp\Kms\Transfer;
use Illuminate\Support\MessageBag;
use Symfony\Component\Debug\Debug;
abstract class AbstractCsvImportService extends AbstractCsvTransferService
{
/** @var string A name of an attribute key that will receive the import errors if any*/
protected $errorsAttributeKey = '';
/**
* Imports the row into the database
*
* @param array $row
* @param int $lineNumber
* @return string A success or fail message
*/
abstract public function import(array $row, int $lineNumber):string;
/**
* Validates a row and returns an array with
* validation errors if any or an empty array if no errors
*
* @param array $row
* @param int $lineNumber
* @return array
*/
public function validate(array $row, int $lineNumber): array
{
$mismatchErrors = $this->checkForColumnCountMismatches($row, $lineNumber);
if(count($mismatchErrors) > 0) return $mismatchErrors;
return $this->validateRowData($row, $lineNumber);
}
/**
* Loops over all column maps and checks if their column numbers really map to a value.
*
* @param array $row
* @param int $lineNumber
* @return array
*/
private function checkForColumnCountMismatches(array $row, int $lineNumber):array
{
$columnMapsCollection = collect($this->getColumnMaps());
$headerString = implode(', ', $this->getHeaderNames());
$validationErrors = $columnMapsCollection->flatten()->map(function(ColumnMap $columnMap) use($row, $lineNumber, $headerString) {
if($columnMap->getColumnNumber() > count($row)) {
$error = __('kms/transfer.validation_error.column_count_mismatch', ['headers' => $headerString]);
return $error;
}
})->filter(function($errorOrNull) { return $errorOrNull !== null; })->toArray();
if(count($validationErrors) == 0) return [];
return $validationErrors;
}
/**
* Validates the rows data and returns an array with
* validation errors if any or an empty array if no errors
*
* @param array $row
* @param int $lineNumber
* @return array
*/
private function validateRowData(array $row, int $lineNumber):array
{
$columnMapsCollection = collect($this->getColumnMaps());
$validationErrors = $columnMapsCollection->flatten()->map(function(ColumnMap $columnMap) use($row, $lineNumber) {
$validationRegex = $columnMap->getValidationRegex();
$value = $row[$columnMap->getColumnNumber()];
$value = preg_replace("/\r|\n/", "", $value);
if($value == '' && $columnMap->isOptional()) return null;
try {
if($validationRegex == '') return null; //If no validation regex is defined we skip the validation and assume the data is right
$match = preg_match($validationRegex, $value);
if($match == false) return __('kms/transfer.validation_error.error_on_line', ['row_number' => $lineNumber]).': '.$columnMap->getValidationErrorText();
}
catch (\ErrorException $exception)
{
return __('kms/transfer.validation_error.error_on_line', ['row_number' => $lineNumber]).': '.$columnMap->getValidationErrorText().'. Regex: "'.$validationRegex.'". Exception: '.$exception->getMessage();
}
})->filter(function($value) { return $value !== null; })->toArray();
return $validationErrors;
}
/**
* Loads a file containing CSV data and passes each row to the import method.
*
* @param string $filePath
* @return MessageBag with keys 'errors' and 'success'
*/
public function importFromFile(string $filePath)
{
return $this->readCsvFileAndStartProcessing($filePath);
}
/**
* Reads a csv file and delegates importing.
* If an error occurs it redirect backs with errors. Use key row_validation_errors for more info
*
* @param string $filePath
* @return MessageBag with two keys 'errors' and 'success'. That represent success and error messages for the imported lines
*/
private function readCsvFileAndStartProcessing(string $filePath):MessageBag
{
\Log::debug("AbstractCsvImportService:114");
$fileHandle = fopen($filePath, 'r');
$lineNumber = 1;
$messageBag = new MessageBag();
// echo __('kms/transfer.processing').'<br/>';
$bigErrorCounter = 0;
$bigSuccessCounter = 0;
$emptyRowCounter = 0;
//Prevent memory exhaustion errors
$maxMemoryForImport = 128000000; //128 Megabytes (128000000 bytes). Php default
\Debugbar::disable();
set_time_limit(0);
ini_set('memory_limit', $maxMemoryForImport);
while (($row = fgetcsv($fileHandle, 100000, ";")) !== false) {
if(memory_get_usage() >= $maxMemoryForImport * .8)
{
$messageBag->add('errors', __('kms/transfer.memory_exhaustion_import'));
return $messageBag;
}
//Skip a row that is completely empty
$rowAsCSV = implode(';', $row);
if(str_replace(';', '', $rowAsCSV) == "") {
unset($rowAsCSV); //Force garbage collection
$emptyRowCounter++;
if($emptyRowCounter >= 10) {
break;
}
continue;
}
//Import a row
if(!self::HASHEADERROW || ($lineNumber > 1 && self::HASHEADERROW)) {
\DB::transaction(function () use ($row, $lineNumber, &$messageBag, &$bigErrorCounter, &$bigSuccessCounter) {
$errorsFromValidation = $this->validate($row, $lineNumber);
if(count($errorsFromValidation) > 0) {
foreach($errorsFromValidation as $error) {
if($messageBag->count() < 1000) //we only log the first x amount or so messages. Over that we risk memory exhaustion via the KmsFlashMessageComposer concatenation
$messageBag->add('errors', $error);
else $bigErrorCounter++;
}
} else {
$successFromImport = $this->import($row, $lineNumber);
if($messageBag->count() < 1000) { //we only log the first x amount or so messages. Over that we risk memory exhaustion via the KmsFlashMessageComposer concatenation
$messageBag->add('successes', $successFromImport);
} else {
$bigSuccessCounter++;
}
}
});
}
unset($row); //Force garbage collection
$lineNumber++;
}
fclose($fileHandle);
if($bigSuccessCounter > 0)
$messageBag->add('successes', __('kms/transfer.general_extra_success_amount', ['amount' => $bigSuccessCounter]));
if($bigErrorCounter > 0)
$messageBag->add('errors', __('kms/transfer.general_extra_error_amount', ['amount' => $bigErrorCounter]));
return $messageBag;
}
}