Completed
Push — master ( 034da8...67d8d3 )
by Markus
08:20
created

ImportCommand::execute()   B

Complexity

Conditions 9
Paths 25

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 10
cts 10
cp 1
rs 8.0555
c 0
b 0
f 0
cc 9
nc 25
nop 2
crap 9
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);
0 ignored issues
show
Unused Code introduced by
The call to ImportRequest::__construct() has too many arguments starting with $context.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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