HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/pietvanmierlo/stempelbv.nl/app/Komma/Kms/Transfer/AbstractCsvImportService.php
<?php


namespace App\Komma\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;
    }
}