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

ImportCommand   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 18

Test Coverage

Coverage 72.52%

Importance

Changes 0
Metric Value
wmc 29
lcom 2
cbo 18
dl 0
loc 199
ccs 66
cts 91
cp 0.7252
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A configure() 0 13 1
B execute() 0 25 9
B import() 0 72 8
B writeValidationViolations() 0 40 8
A writeStatistics() 0 13 2
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