Completed
Push — master ( fc13ab...2c6a12 )
by Markus
07:05
created

ImportCommand   C

Complexity

Total Complexity 27

Size/Duplication

Total Lines 178
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Test Coverage

Coverage 72.52%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 20
dl 0
loc 178
ccs 66
cts 91
cp 0.7252
rs 6.4705
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 12 1
A validateInput() 0 7 3
B execute() 0 22 5
C import() 0 74 8
C 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 JMS\SecurityExtraBundle\Exception\InvalidArgumentException;
7
use Mathielen\DataImport\Event\ImportItemEvent;
8
use Mathielen\ImportEngine\Event\ImportConfigureEvent;
9
use Mathielen\ImportEngine\Event\ImportRequestEvent;
10
use Mathielen\ImportEngine\Exception\InvalidConfigurationException;
11
use Mathielen\ImportEngine\Import\ImportBuilder;
12
use Mathielen\ImportEngine\Import\Run\ImportRunner;
13
use Mathielen\ImportEngine\ValueObject\ImportRequest;
14
use Mathielen\ImportEngine\ValueObject\ImportRun;
15
use Mathielen\ImportEngineBundle\Utils;
16
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
17
use Symfony\Component\Console\Helper\ProgressBar;
18
use Symfony\Component\Console\Helper\Table;
19
use Symfony\Component\Console\Helper\TableSeparator;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Validator\ConstraintViolation;
25
26
class ImportCommand extends ContainerAwareCommand
27
{
28
    const MAX_VIOLATION_ERRORS = 10;
29
30 4
    protected function configure()
31
    {
32 4
        $this->setName('importengine:import')
33 4
            ->setDescription('Imports data with a definied importer')
34 4
            ->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]\"")
35 4
            ->addArgument('source_provider', InputArgument::OPTIONAL, 'id of source provider', 'default')
36 4
            ->addOption('importer', 'i', InputOption::VALUE_REQUIRED, 'id/name of importer')
37 4
            ->addOption('context', 'c', InputOption::VALUE_REQUIRED, 'Supply optional context information to import. Supply key-value data in query style: key=value&otherkey=othervalue&...')
38 4
            ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit imported rows')
39 4
            ->addOption('dryrun', 'd', InputOption::VALUE_NONE, 'Do not import - Validation only')
40
        ;
41 4
    }
42
43 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...
44
    {
45 4
        if (!$this->getContainer()->has('mathielen_importengine.import.builder') ||
46 4
            !$this->getContainer()->has('mathielen_importengine.import.runner')) {
47
            throw new InvalidConfigurationException('No importengine services have been found. Did you register the bundle in AppKernel and configured at least one importer in config?');
48
        }
49 4
    }
50
51 4
    protected function execute(InputInterface $input, OutputInterface $output)
52
    {
53 4
        $this->validateInput($input);
54
55 4
        $importerId = $input->getOption('importer');
56 4
        $sourceProviderId = $input->getArgument('source_provider');
57 4
        $sourceId = $input->getArgument('source_id');
58 4
        $isDryrun = $input->getOption('dryrun');
59 4
        if ($context = $input->getOption('context')) {
60
            //parse key=value&key=value string to array
61
            if (strpos($context, '=') !== false) {
62
                parse_str($input->getOption('context'), $context);
63
            }
64
        }
65 4
        $limit = $input->getOption('limit');
66
67 4
        if (empty($importerId) && empty($sourceId)) {
68
            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.');
69
        }
70
71 4
        $this->import($output, $importerId, $sourceProviderId, $sourceId, $context, $limit, $isDryrun);
72 4
    }
73
74 4
    protected function import(OutputInterface $output, $importerId, $sourceProviderId, $sourceId, $context = null, $limit = null, $isDryrun = false)
75
    {
76 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>");
77
78 4
        $sourceId = Utils::parseSourceId($sourceId);
79 4
        $progress = new ProgressBar($output);
80
81
        //set limit
82 4
        if ($limit) {
83 1
            $output->writeln("Limiting import to <info>$limit</info> rows.");
84
85
            $this->getContainer()->get('event_dispatcher')->addListener(ImportConfigureEvent::AFTER_BUILD, function (ImportConfigureEvent $event) use ($limit) {
86
                $event->getImport()->importer()->filters()->add(new OffsetFilter(0, $limit));
87 1
            });
88
        }
89
90
        //show discovered importer id
91 4
        if (empty($importerId)) {
92
            $this->getContainer()->get('event_dispatcher')->addListener(ImportRequestEvent::DISCOVERED, function (ImportRequestEvent $event) use ($output) {
93
                $importerId = $event->getImportRequest()->getImporterId();
94
                $output->writeln("Importer discovered: <info>$importerId</info>");
95 2
            });
96
        }
97
98
        /** @var ImportBuilder $importBuilder */
99 4
        $importBuilder = $this->getContainer()->get('mathielen_importengine.import.builder');
100
101 4
        $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...
102
103 4
        $import = $importBuilder->buildFromRequest($importRequest);
104
105
        //apply context info from commandline
106 4
        $importRun = $import->getRun();
107
108
        //status callback
109 4
        $this->getContainer()->get('event_dispatcher')->addListener(ImportItemEvent::AFTER_READ, function (ImportItemEvent $event) use ($output, &$progress) {
110
            /** @var ImportRun $importRun */
111
            $importRun = $event->getContext()->getRun();
112
            $stats = $importRun->getStatistics();
113
            $processed = isset($stats['processed']) ? $stats['processed'] : 0;
114
            $max = $importRun->getInfo()['count'];
115
116
            if ($progress->getMaxSteps() != $max) {
117
                $progress = new ProgressBar($output, $max);
118
                $progress->start();
119
            }
120
121
            $progress->setProgress($processed);
122 4
        });
123
124
        /** @var ImportRunner $importRunner */
125 4
        $importRunner = $this->getContainer()->get('mathielen_importengine.import.runner');
126 4
        if ($isDryrun) {
127 1
            $importRunner->dryRun($import);
128
        } else {
129 3
            $importRunner->run($import);
130
        }
131
132 4
        $progress->finish();
133 4
        $output->writeln('');
134 4
        $output->writeln('<info>Import done</info>');
135 4
        $output->writeln('');
136
137 4
        $this->writeStatistics($importRun->getStatistics(), new Table($output));
138
139 4
        $this->writeValidationViolations(
140
            $import
141 4
                ->importer()
142 4
                ->validation()
143 4
                ->getViolations(),
144 4
            new Table($output));
145
146 4
        $output->writeln('');
147 4
    }
148
149 4
    protected function writeValidationViolations(array $violations, Table $table)
150
    {
151 4
        if (empty($violations)) {
152 4
            return;
153
        }
154
        $violations = $violations['source'] + $violations['target'];
155
156
        $table
157
            ->setHeaders(array('Constraint', 'Occurrences (lines)'))
158
        ;
159
160
        $tree = [];
161
        foreach ($violations as $line => $validations) {
162
            /** @var ConstraintViolation $validation */
163
            foreach ($validations as $validation) {
164
                $key = $validation->__toString();
165
                if (!isset($tree[$key])) {
166
                    $tree[$key] = [];
167
                }
168
                $tree[$key][] = $line;
169
            }
170
        }
171
172
        $i = 0;
173
        foreach ($tree as $violation => $lines) {
174
            $table->addRow([$violation, implode(', ', Utils::numbersToRangeText($lines))]);
175
            ++$i;
176
177
            if ($i === self::MAX_VIOLATION_ERRORS) {
178
                $table->addRow(new TableSeparator());
179
                $table->addRow(array(null, 'There are more errors...'));
180
181
                break;
182
            }
183
        }
184
185
        if ($i > 0) {
186
            $table->render();
187
        }
188
    }
189
190 4
    protected function writeStatistics(array $statistics, Table $table)
191
    {
192 4
        $rows = [];
193 4
        foreach ($statistics as $k => $v) {
194
            $rows[] = [$k, $v];
195
        }
196
197
        $table
198 4
            ->setHeaders(array('Statistics'))
199 4
            ->setRows($rows)
200
        ;
201 4
        $table->render();
202 4
    }
203
}
204