ImportCommand::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1.0046

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1.0046
1
<?php
2
3
namespace Mathielen\ImportEngineBundle\Command;
4
5
use Ddeboer\DataImport\Filter\OffsetFilter;
6
use Mathielen\DataImport\Event\ImportItemEvent;
7
use Mathielen\ImportEngine\Event\ImportConfigureEvent;
8
use Mathielen\ImportEngine\Event\ImportRequestEvent;
9
use Mathielen\ImportEngine\Import\ImportBuilder;
10
use Mathielen\ImportEngine\Import\Run\ImportRunner;
11
use Mathielen\ImportEngine\ValueObject\ImportRequest;
12
use Mathielen\ImportEngine\ValueObject\ImportRun;
13
use Mathielen\ImportEngineBundle\Utils;
14
use Symfony\Component\Console\Command\Command;
15
use Symfony\Component\Console\Helper\ProgressBar;
16
use Symfony\Component\Console\Helper\Table;
17
use Symfony\Component\Console\Helper\TableSeparator;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
23
use Symfony\Component\Validator\ConstraintViolation;
24
25
class ImportCommand extends Command
26
{
27
    const MAX_VIOLATION_ERRORS = 10;
28
29
    /**
30 4
     * @var ImportBuilder
31
     */
32 4
    private $importBuilder;
33 4
34 4
    /**
35 4
     * @var ImportRunner
36 4
     */
37 4
    private $importRunner;
38 4
39 4
    /**
40
     * @var EventDispatcherInterface
41 4
     */
42
    private $eventDispatcher;
43 4
44
    public function __construct(
45 4
        ImportBuilder $importBuilder,
46 4
        ImportRunner $importRunner,
47
        EventDispatcherInterface $eventDispatcher)
48
    {
49 4
        parent::__construct('importengine:import');
50
51 4
        $this->importBuilder = $importBuilder;
52
        $this->importRunner = $importRunner;
53 4
        $this->eventDispatcher = $eventDispatcher;
54
    }
55 4
56 4
    protected function configure()
57 4
    {
58 4
        $this
59 4
            ->setDescription('Imports data with a definied importer')
60
            ->addArgument('source_id', InputArgument::OPTIONAL, "id of source. Different StorageProviders need different id data.\n- upload, directory: \"<path/to/file>\"\n- doctrine: \"<id of query>\"\n- service: \"<service>.<method>[?arguments_like_url_query]\"")
61
            ->addArgument('source_provider', InputArgument::OPTIONAL, 'id of source provider', 'default')
62
            ->addOption('importer', 'i', InputOption::VALUE_REQUIRED, 'id/name of importer')
63
            ->addOption('context', 'c', InputOption::VALUE_REQUIRED, 'Supply optional context information to import. Supply key-value data in query style: key=value&otherkey=othervalue&...')
64
            ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit imported rows')
65 4
            ->addOption('dryrun', 'd', InputOption::VALUE_NONE, 'Do not import - Validation only')
66
            ->addOption('validate-and-run', null, InputOption::VALUE_NONE, 'Validate and run if no error')
67 4
        ;
68
    }
69
70
    protected function execute(InputInterface $input, OutputInterface $output)
71 4
    {
72 4
        $importerId = $input->getOption('importer');
73
        $sourceProviderId = $input->getArgument('source_provider');
74 4
        $sourceId = $input->getArgument('source_id');
75
        $isDryrun = $input->getOption('dryrun');
76 4
        $isValidateAndRun = $input->getOption('validate-and-run');
77
        if ($isDryrun && $isValidateAndRun) {
78 4
            throw new \InvalidArgumentException("Cannot invoke with dryrun and validate-and-run");
79 4
        }
80
        $runMode = $isDryrun ? 'dryrun' : $isValidateAndRun ? 'validate_and_run' : 'run';
81
        if ($context = $input->getOption('context')) {
82 4
            //parse key=value&key=value string to array
83 1
            if (strpos($context, '=') !== false) {
84
                parse_str($input->getOption('context'), $context);
85
            }
86
        }
87 1
        $limit = $input->getOption('limit');
88
89
        if (empty($importerId) && empty($sourceId)) {
90
            throw new \InvalidArgumentException('There must be at least an importerId with a configured source-definition given or a sourceId which can be automatically recognized by pre-conditions.');
91 4
        }
92
93
        $this->import($output, $importerId, $sourceProviderId, $sourceId, $context, $limit, $runMode);
94
    }
95 2
96
    protected function import(OutputInterface $output, $importerId, $sourceProviderId, $sourceId, $context = null, $limit = null, $runMode = 'run')
97
    {
98
        $output->writeln("Commencing import with mode <comment>$runMode</comment> using importer ".(empty($importerId) ? '<comment>unknown</comment>' : "<info>$importerId</info>")." with source provider <info>$sourceProviderId</info> and source id <info>$sourceId</info>");
99 4
100
        $sourceId = Utils::parseSourceId($sourceId);
101 4
        $progress = new ProgressBar($output);
102
103 4
        //set limit
104
        if ($limit) {
105
            $output->writeln("Limiting import to <info>$limit</info> rows.");
106 4
107
            $this->eventDispatcher->addListener(ImportConfigureEvent::AFTER_BUILD, function (ImportConfigureEvent $event) use ($limit) {
108
                $event->getImport()->importer()->filters()->add(new OffsetFilter(0, $limit));
109 4
            });
110
        }
111
112
        //show discovered importer id
113
        if (empty($importerId)) {
114
            $this->eventDispatcher->addListener(ImportRequestEvent::DISCOVERED, function (ImportRequestEvent $event) use ($output) {
115
                $importerId = $event->getImportRequest()->getImporterId();
116
                $output->writeln("Importer discovered: <info>$importerId</info>");
117
            });
118
        }
119
120
        $importRequest = new ImportRequest($sourceId, $sourceProviderId, $importerId, Utils::whoAmI().'@CLI', $context);
121
122 4
        $import = $this->importBuilder->buildFromRequest($importRequest);
123
124
        //apply context info from commandline
125 4
        $importRun = $import->getRun();
126 4
127 1
        //status callback
128
        $this->eventDispatcher->addListener(ImportItemEvent::AFTER_READ, function (ImportItemEvent $event) use ($output, &$progress) {
129 3
            /** @var ImportRun $importRun */
130
            $importRun = $event->getContext()->getRun();
131
            $stats = $importRun->getStatistics();
132 4
            $processed = isset($stats['processed']) ? $stats['processed'] : 0;
133 4
            $max = $importRun->getInfo()['count'];
134 4
135 4
            if ($progress->getMaxSteps() != $max) {
136
                $progress = new ProgressBar($output, $max);
137 4
                $progress->start();
138
            }
139 4
140
            $progress->setProgress($processed);
141 4
        });
142 4
143 4
        if ($runMode === 'dryrun') {
144 4
            $this->importRunner->dryRun($import);
145
        } elseif ($runMode === 'validate_and_run') {
146 4
            $this->importRunner->dryRun($import);
147 4
            $this->importRunner->run($import);
148
        } else {
149 4
            $this->importRunner->run($import);
150
        }
151 4
152 4
        $progress->finish();
153
        $output->writeln('');
154
        $output->writeln('<info>Import done</info>');
155
        $output->writeln('');
156
157
        $this->writeStatistics($importRun->getStatistics(), new Table($output));
158
159
        $this->writeValidationViolations(
160
            $import
161
                ->importer()
162
                ->validation()
163
                ->getViolations(),
164
            new Table($output));
165
166
        $output->writeln('');
167
    }
168
169
    protected function writeValidationViolations(array $violations, Table $table)
170
    {
171
        if (empty($violations)) {
172
            return;
173
        }
174
        $violations = $violations['source'] + $violations['target'];
175
176
        $table
177
            ->setHeaders(array('Constraint', 'Occurrences (lines)'))
178
        ;
179
180
        $tree = [];
181
        foreach ($violations as $line => $validations) {
182
            /** @var ConstraintViolation $validation */
183
            foreach ($validations as $validation) {
184
                $key = $validation->__toString();
185
                if (!isset($tree[$key])) {
186
                    $tree[$key] = [];
187
                }
188
                $tree[$key][] = $line;
189
            }
190 4
        }
191
192 4
        $i = 0;
193 4
        foreach ($tree as $violation => $lines) {
194
            $table->addRow([$violation, implode(', ', Utils::numbersToRangeText($lines))]);
195
            ++$i;
196
197
            if ($i === self::MAX_VIOLATION_ERRORS) {
198 4
                $table->addRow(new TableSeparator());
199 4
                $table->addRow(array(null, 'There are more errors...'));
200
201 4
                break;
202 4
            }
203
        }
204
205
        if ($i > 0) {
206
            $table->render();
207
        }
208
    }
209
210
    protected function writeStatistics(array $statistics, Table $table)
211
    {
212
        $rows = [];
213
        foreach ($statistics as $k => $v) {
214
            $rows[] = [$k, $v];
215
        }
216
217
        $table
218
            ->setHeaders(array('Statistics'))
219
            ->setRows($rows)
220
        ;
221
        $table->render();
222
    }
223
}
224