Completed
Push — master ( f25de2...487eea )
by Markus
03:35
created

ImportCommand   C

Complexity

Total Complexity 26

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Test Coverage

Coverage 72.52%

Importance

Changes 19
Bugs 5 Features 3
Metric Value
wmc 26
c 19
b 5
f 3
lcom 1
cbo 20
dl 0
loc 179
ccs 66
cts 91
cp 0.7252
rs 6.4705

6 Methods

Rating   Name   Duplication   Size   Complexity  
A validateInput() 0 7 3
A execute() 0 18 3
A configure() 0 12 1
A writeStatistics() 0 13 2
C import() 0 77 9
C writeValidationViolations() 0 40 8
1
<?php
2
namespace Mathielen\ImportEngineBundle\Command;
3
4
use Ddeboer\DataImport\Filter\OffsetFilter;
5
use Mathielen\DataImport\Event\ImportItemEvent;
6
use Mathielen\ImportEngine\Event\ImportConfigureEvent;
7
use Mathielen\ImportEngine\Event\ImportRequestEvent;
8
use Mathielen\ImportEngine\Exception\InvalidConfigurationException;
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\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
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\Validator\ConstraintViolation;
23
24
class ImportCommand extends ContainerAwareCommand
25
{
26
27
    const MAX_VIOLATION_ERRORS = 10;
28
29 4
    protected function configure()
30
    {
31 4
        $this->setName('importengine:import')
32 4
            ->setDescription('Imports data with a definied importer')
33 4
            ->addArgument('source_id', InputArgument::REQUIRED, "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]\"")
34 4
            ->addArgument('source_provider', InputArgument::OPTIONAL, 'id of source provider', 'default')
35 4
            ->addOption('importer', 'i', InputOption::VALUE_REQUIRED, 'id/name of importer')
36 4
            ->addOption('context', 'c', InputOption::VALUE_REQUIRED, 'Supply optional context information to import. Supply key-value data in query style: key=value&otherkey=othervalue&...')
37 4
            ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit imported rows')
38 4
            ->addOption('dryrun', 'd', InputOption::VALUE_NONE, 'Do not import - Validation only')
39
        ;
40 4
    }
41
42 4
    protected function validateInput(InputInterface $input)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
43
    {
44 4
        if (!$this->getContainer()->has('mathielen_importengine.import.builder') ||
45 4
            !$this->getContainer()->has('mathielen_importengine.import.runner')) {
46
            throw new InvalidConfigurationException("No importengine services have been found. Did you register the bundle in AppKernel and configured at least one importer in config?");
47
        }
48 4
    }
49
50 4
    protected function execute(InputInterface $input, OutputInterface $output)
51
    {
52 4
        $this->validateInput($input);
53
54 4
        $importerId = $input->getOption('importer');
55 4
        $sourceProviderId = $input->getArgument('source_provider');
56 4
        $sourceId = $input->getArgument('source_id');
57 4
        $isDryrun = $input->getOption('dryrun');
58 4
        if ($context = $input->getOption('context')) {
59
            //parse key=value&key=value string to array
60
            if (strpos($context, '=') !== false) {
61
                parse_str($input->getOption('context'), $context);
62
            }
63
        }
64 4
        $limit = $input->getOption('limit');
65
66 4
        $this->import($output, $importerId, $sourceProviderId, $sourceId, $context, $limit, $isDryrun);
67 4
    }
68
69 4
    protected function import(OutputInterface $output, $importerId, $sourceProviderId, $sourceId, $context=null, $limit=null, $isDryrun=false)
70
    {
71 4
        $output->writeln("Commencing ".($isDryrun?'<comment>dry-run</comment> ':'')."import using importer ".(empty($importerId)?'<comment>unknown</comment>':"<info>$importerId</info>")." with source provider <info>$sourceProviderId</info> and source id <info>$sourceId</info>");
72
73 4
        $sourceId = Utils::parseSourceId($sourceId);
74 4
        $progress = new ProgressBar($output);
75
76
        //set limit
77 4
        if ($limit) {
78 1
            $output->writeln("Limiting import to <info>$limit</info> rows.");
79
80
            $this->getContainer()->get('event_dispatcher')->addListener(ImportConfigureEvent::AFTER_BUILD, function (ImportConfigureEvent $event) use ($limit) {
81
                $event->getImport()->importer()->filters()->add(new OffsetFilter(0, $limit));
82 1
            });
83
        }
84
85
        //show discovered importer id
86 4
        if (empty($importerId)) {
87
            $this->getContainer()->get('event_dispatcher')->addListener(ImportRequestEvent::DISCOVERED, function (ImportRequestEvent $event) use ($output) {
88
                $importerId = $event->getImportRequest()->getImporterId();
89
                $output->writeln("Importer discovered: <info>$importerId</info>");
90 2
            });
91
        }
92
93
        /** @var ImportBuilder $importBuilder */
94 4
        $importBuilder = $this->getContainer()->get('mathielen_importengine.import.builder');
95
96 4
        $importRequest = new ImportRequest($sourceId, $sourceProviderId, $importerId, Utils::whoAmI().'@CLI');
97
98 4
        $import = $importBuilder->buildFromRequest($importRequest);
99
100
        //apply context info from commandline
101 4
        $importRun = $import->getRun();
102 4
        if ($context) {
103
            $importRun->setContext($context);
104
        }
105
106
        //status callback
107 4
        $this->getContainer()->get('event_dispatcher')->addListener(ImportItemEvent::AFTER_READ, function (ImportItemEvent $event) use ($output, &$progress) {
108
            /** @var ImportRun $importRun */
109
            $importRun = $event->getContext()->getRun();
110
            $stats = $importRun->getStatistics();
111
            $processed = isset($stats['processed'])?$stats['processed']:0;
112
            $max = $importRun->getInfo()['count'];
113
114
            if ($progress->getMaxSteps() != $max) {
115
                $progress = new ProgressBar($output, $max);
116
                $progress->start();
117
            }
118
119
            $progress->setProgress($processed);
120 4
        });
121
122
        /** @var ImportRunner $importRunner */
123 4
        $importRunner = $this->getContainer()->get('mathielen_importengine.import.runner');
124 4
        if ($isDryrun) {
125 1
            $importRunner->dryRun($import);
126
        } else {
127 3
            $importRunner->run($import);
128
        }
129
130 4
        $progress->finish();
131 4
        $output->writeln('');
132 4
        $output->writeln("<info>Import done</info>");
133 4
        $output->writeln('');
134
135 4
        $this->writeStatistics($importRun->getStatistics(), new Table($output));
136
137 4
        $this->writeValidationViolations(
138
            $import
139 4
                ->importer()
140 4
                ->validation()
141 4
                ->getViolations(),
142 4
            new Table($output));
143
144 4
        $output->writeln('');
145 4
    }
146
147 4
    protected function writeValidationViolations(array $violations, Table $table)
148
    {
149 4
        if (empty($violations)) {
150 4
            return;
151
        }
152
        $violations = $violations['source'] + $violations['target'];
153
154
        $table
155
            ->setHeaders(array('Constraint', 'Occurrences (lines)'))
156
        ;
157
158
        $tree = [];
159
        foreach ($violations as $line=>$validations) {
160
            /** @var ConstraintViolation $validation */
161
            foreach ($validations as $validation) {
162
                $key = $validation->__toString();
163
                if (!isset($tree[$key])) {
164
                    $tree[$key] = [];
165
                }
166
                $tree[$key][] = $line;
167
            }
168
        }
169
170
        $i = 0;
171
        foreach ($tree as $violation=>$lines) {
172
            $table->addRow([$violation, join(', ', Utils::numbersToRangeText($lines))]);
173
            ++$i;
174
175
            if ($i === self::MAX_VIOLATION_ERRORS) {
176
                $table->addRow(new TableSeparator());
177
                $table->addRow(array(null, 'There are more errors...'));
178
179
                break;
180
            }
181
        }
182
183
        if ($i > 0) {
184
            $table->render();
185
        }
186
    }
187
188 4
    protected function writeStatistics(array $statistics, Table $table)
189
    {
190 4
        $rows = [];
191 4
        foreach ($statistics as $k=>$v) {
192
            $rows[] = [$k, $v];
193
        }
194
195
        $table
196 4
            ->setHeaders(array('Statistics'))
197 4
            ->setRows($rows)
198
        ;
199 4
        $table->render();
200 4
    }
201
202
}
203