Your IP : 216.73.217.13


Current Path : /var/www/magento.test.indacotrentino.com/www/app/code/Amasty/ImportCore/Import/
Upload File :
Current File : /var/www/magento.test.indacotrentino.com/www/app/code/Amasty/ImportCore/Import/ImportStrategy.php

<?php

declare(strict_types=1);

/**
 * @author Amasty Team
 * @copyright Copyright (c) Amasty (https://www.amasty.com)
 * @package Import Core for Magento 2 (System)
 */

namespace Amasty\ImportCore\Import;

use Amasty\ImportCore\Api\ActionInterface;
use Amasty\ImportCore\Api\Config\ProfileConfigInterface;
use Amasty\ImportCore\Api\ImportProcessInterface;
use Amasty\ImportCore\Api\ImportProcessInterfaceFactory;
use Amasty\ImportCore\Api\ImportResultInterface;
use Amasty\ImportCore\Api\ImportResultInterfaceFactory;
use Amasty\ImportCore\Exception\JobDelegatedException;
use Amasty\ImportCore\Exception\JobDoneException;
use Amasty\ImportCore\Exception\JobTerminatedException;
use Amasty\ImportCore\Import\Config\EntityConfigProvider;
use Amasty\ImportCore\Import\Parallelization\ResultMerger;
use Amasty\ImportCore\Model\ConfigProvider;
use Amasty\ImportCore\Model\Process\ProcessRepository;
use Amasty\ImportExportCore\Parallelization\JobManager;
use Amasty\ImportExportCore\Parallelization\JobManagerFactory;
use Magento\Framework\App\State;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\ObjectManagerInterface;
use Psr\Log\LoggerInterface;

class ImportStrategy
{
    /**
     * @var array
     */
    private $actionGroups;

    /**
     * @var ObjectManagerInterface
     */
    private $objectManager;

    /**
     * @var ImportResultInterfaceFactory
     */
    private $importResultFactory;

    /**
     * @var ConfigProvider
     */
    private $configProvider;

    /**
     * @var ProcessRepository
     */
    private $processRepository;

    /**
     * @var ResultMerger
     */
    private $resultMerger;

    /**
     * @var JobManagerFactory
     */
    private $jobManagerFactory;

    /**
     * @var JobManager
     */
    private $jobManager;

    /**
     * @var ImportProcessInterfaceFactory
     */
    private $importProcessFactory;

    /**
     * @var EntityConfigProvider
     */
    private $entityConfigProvider;

    /**
     * @var ManagerInterface
     */
    private $eventManager;

    /**
     * @var State
     */
    private $appState;

    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(
        ImportResultInterfaceFactory $importResultFactory,
        ObjectManagerInterface $objectManager,
        ConfigProvider $configProvider,
        ProcessRepository $processRepository,
        JobManagerFactory $jobManagerFactory,
        EntityConfigProvider $entityConfigProvider,
        ResultMerger $resultMerger,
        ImportProcessInterfaceFactory $importProcessFactory,
        ManagerInterface $eventManager,
        State $appState,
        LoggerInterface $logger,
        array $actionGroups = []
    ) {
        $this->objectManager = $objectManager;
        $this->importResultFactory = $importResultFactory;
        $this->configProvider = $configProvider;
        $this->processRepository = $processRepository;
        $this->resultMerger = $resultMerger;
        $this->jobManagerFactory = $jobManagerFactory;
        $this->actionGroups = $actionGroups;
        $this->importProcessFactory = $importProcessFactory;
        $this->entityConfigProvider = $entityConfigProvider;
        $this->eventManager = $eventManager;
        $this->appState = $appState;
        $this->logger = $logger;
    }

    public function run(
        ProfileConfigInterface $profileConfig,
        string $processIdentity
    ): ImportResultInterface {
        if ($profileConfig->isUseMultiProcess() && JobManager::isAvailable()) {
            $importProcess = null;
            /** @var JobManager $jobManager */
            $this->jobManager = $this->jobManagerFactory->create(
                [
                    'jobDoneCallback' => function ($response) use (&$importProcess) {
                        $this->processFinalization($importProcess, $response);
                    },
                    'maxJobs' => $profileConfig->getMaxJobs()
                ]
            );
        } else {
            $this->jobManager = null;
        }

        /** @var ImportProcessInterface $importProcess */
        $importProcess = $this->importProcessFactory->create(
            [
                'identity' => $processIdentity,
                'profileConfig' => $profileConfig,
                'entityConfig' => $this->entityConfigProvider->get($profileConfig->getEntityCode()),
                'jobManager' => $this->jobManager
            ]
        );

        $this->eventManager->dispatch('amimport_before_run', ['importProcess' => $importProcess]);
        $this->registerErrorCatching($importProcess);

        $importResult = $importProcess->getImportResult();

        foreach ($this->getSortedActionGroups() as $groupName => $actionsGroup) {
            if (!$importResult->isImportTerminated()) {
                $importResult->resetProcessedRecords();
                $importResult->setStage($groupName);
                $this->processRepository->updateProcess($importProcess);
            }
            try {
                $actions = $this->prepareActions($actionsGroup, $importProcess);
                $this->processActions($actions, $importProcess);
            } catch (JobDoneException $e) {
                return $importResult;
            } catch (\Exception $e) {
                $importProcess->addCriticalMessage($e->getMessage());
            } catch (\Throwable $e) {
                $writer = new \Zend_Log_Writer_Stream(BP . '/' . ConfigProvider::DEBUG_LOG_PATH);
                $logger = new \Zend_Log();
                $logger->addWriter($writer);
                $logger->info($e->__toString());

                if ($this->configProvider->isDebugEnabled()) {
                    $importProcess->addCriticalMessage($e->__toString());
                } else {
                    $importProcess->addCriticalMessage(
                        'An error occurred during the import process. '
                        . 'For the error details please enable \'Debug Mode\' or see the '
                        . ConfigProvider::DEBUG_LOG_PATH
                    );
                }
                $importProcess->getImportResult()->terminateImport(true);
                break;
            }
        }

        $this->finalizeProcess($importProcess);

        return $importResult;
    }

    /**
     * @codeCoverageIgnore Can't be covered by unit/integration tests due to pcntl_fork() usage
     * @param \Amasty\ImportCore\Api\ActionInterface[] $actions
     * @param ImportProcessInterface $importProcess
     */
    protected function processActions(
        array $actions,
        ImportProcessInterface $importProcess
    ) {
        $batchNumber = 0;
        do {
            $importProcess->setData([]);
            $importProcess->setBatchNumber(++$batchNumber);
            $importProcess->setLastBatchReached(false);

            try {
                foreach ($actions as $actionCode => $action) {
                    $action->execute($importProcess);
                    if ($importProcess->getImportResult()->isImportTerminated()) {
                        break 2;
                    }
                }
            } catch (JobTerminatedException $e) { // Job terminated by some child process. Entire process is failed
                return;
                // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
            } catch (JobDelegatedException $e) {
                // Another actions from current group should be processed by a child process. Moving to the next batch
            }

            if ($importProcess->isChildProcess()) {
                $this->jobManager->reportToParent($importProcess->getImportResult()->serialize());
                throw new JobDoneException();
            }

            $this->processRepository->updateProcess($importProcess);
        } while ($importProcess->getBatchNumber() <= $importProcess->getBatchQty()
            && !$importProcess->isLastBatchReached()
            && !$importProcess->getImportResult()->isImportTerminated()
        );

        if ($this->jobManager) {
            try {
                $this->jobManager->waitForAllJobs(); // Status updates here
            } catch (JobTerminatedException $e) {
                return;
            }
        }
    }

    /**
     * @codeCoverageIgnore Can't be covered by unit/integration tests due to pcntl_fork() usage
     * @param ImportProcessInterface $importProcess
     * @param $response
     */
    protected function processFinalization(
        ImportProcessInterface $importProcess,
        $response
    ) {
        if ($response) {
            $childResult = $this->importResultFactory->create();
            $childResult->unserialize($response);
            $this->resultMerger->merge($importProcess->getImportResult(), $childResult);
            $this->processRepository->updateProcess($importProcess);

            if ($importProcess->getImportResult()->isFailed()) {
                throw new JobTerminatedException();
            }
        }
    }

    /**
     * @param array $actions
     *
     * @param ImportProcessInterface $importProcess
     * @return ActionInterface[]
     */
    public function prepareActions(array $actions, ImportProcessInterface $importProcess): array
    {
        $result = [];

        foreach ($actions as $actionCode => $action) {
            if (empty($action['class'])) {
                continue;
            }
            $class = $action['class'];
            if (!is_subclass_of($class, ActionInterface::class)) {
                throw new \RuntimeException('Wrong action class: "' . $class . '"');
            }

            if (!isset($action['sortOrder'])) {
                throw new \LogicException('"sortOrder" is not specified for action "' . $actionCode . '"');
            }
            $sortOrder = (int)$action['sortOrder'];
            if (!empty($action['entities']) && is_array($action['entities'])) {
                if (!in_array(
                    $importProcess->getProfileConfig()->getEntityCode(),
                    $action['entities']
                )) {
                    continue;
                }
            }
            unset($action['class']);
            unset($action['sortOrder']);
            unset($action['entities']);

            if (!isset($result[$sortOrder])) {
                $result[$sortOrder] = [];
            }

            /** @var ActionInterface $actionObject */
            $actionObject = $this->objectManager->create($class, $action);
            $actionObject->initialize($importProcess);
            $result[$sortOrder][$actionCode] = $actionObject;
        }
        if (empty($result)) {
            return [];
        }

        ksort($result);

        return array_merge(...$result);
    }

    public function getSortedActionGroups(): array
    {
        if (empty($this->actionGroups)) {
            return [];
        }

        $result = [];
        foreach ($this->actionGroups as $groupCode => $groupConfig) {
            if (empty($groupConfig['actions'])) {
                continue;
            }

            if (!isset($groupConfig['sortOrder'])) {
                throw new \LogicException('"sortOrder" is not specified for action group "' . $groupCode . '"');
            }

            $sortOrder = (int)$groupConfig['sortOrder'];
            if (!isset($result[$sortOrder])) {
                $result[$sortOrder] = [];
            }

            unset($groupConfig['sortOrder']);
            $result[$sortOrder][$groupCode] = $groupConfig['actions'];
        }

        if (empty($result)) {
            return [];
        }

        ksort($result);

        return array_merge(...$result);
    }

    public function registerErrorCatching(ImportProcessInterface $importProcess): ImportStrategy
    {
        //phpcs:ignore
        \register_shutdown_function(function () use ($importProcess) {
            if (error_get_last() === null || (error_get_last()['type'] ?? null) != 1) {
                return;
            }
            if ($this->appState->getMode() === State::MODE_PRODUCTION) {
                $this->logger->critical(error_get_last()['message']);
                $importProcess->addCriticalMessage(
                    (string)__('Something went wrong while import. Please review logs')
                );
            } else {
                $importProcess->addCriticalMessage(
                    error_get_last()['message'] ?? __('Something went wrong while import')
                );
            }
            $importProcess->getImportResult()->terminateImport(true);
            $this->finalizeProcess($importProcess);
        });

        return $this;
    }

    public function finalizeProcess(ImportProcessInterface $importProcess): ImportStrategy
    {
        $this->eventManager->dispatch('amimport_after_run', ['importProcess' => $importProcess]);

        $this->processRepository->finalizeProcess($importProcess);

        return $this;
    }
}