Completed
Push — master ( 487eea...fe7f2e )
by Markus
05:49
created

ImportCommand::validateInput()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 7
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 3
eloc 4
nc 2
nop 1
crap 3.072
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 1
        }
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 2
        }
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', $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...
97
98 4
        $import = $importBuilder->buildFromRequest($importRequest);
99
100
        //apply context info from commandline
101 4
        $importRun = $import->getRun();
102
103
        //status callback
104 4
        $this->getContainer()->get('event_dispatcher')->addListener(ImportItemEvent::AFTER_READ, function (ImportItemEvent $event) use ($output, &$progress) {
105
            /** @var ImportRun $importRun */
106
            $importRun = $event->getContext()->getRun();
107
            $stats = $importRun->getStatistics();
108
            $processed = isset($stats['processed'])?$stats['processed']:0;
109
            $max = $importRun->getInfo()['count'];
110
111
            if ($progress->getMaxSteps() != $max) {
112
                $progress = new ProgressBar($output, $max);
113
                $progress->start();
114
            }
115
116
            $progress->setProgress($processed);
117 4
        });
118
119
        /** @var ImportRunner $importRunner */
120 4
        $importRunner = $this->getContainer()->get('mathielen_importengine.import.runner');
121 4
        if ($isDryrun) {
122 1
            $importRunner->dryRun($import);
123 1
        } else {
124 3
            $importRunner->run($import);
125
        }
126
127 4
        $progress->finish();
128 4
        $output->writeln('');
129 4
        $output->writeln("<info>Import done</info>");
130 4
        $output->writeln('');
131
132 4
        $this->writeStatistics($importRun->getStatistics(), new Table($output));
133
134 4
        $this->writeValidationViolations(
135
            $import
136 4
                ->importer()
137 4
                ->validation()
138 4
                ->getViolations(),
139 4
            new Table($output));
140
141 4
        $output->writeln('');
142 4
    }
143
144 4
    protected function writeValidationViolations(array $violations, Table $table)
145
    {
146 4
        if (empty($violations)) {
147 4
            return;
148
        }
149
        $violations = $violations['source'] + $violations['target'];
150
151
        $table
152
            ->setHeaders(array('Constraint', 'Occurrences (lines)'))
153
        ;
154
155
        $tree = [];
156
        foreach ($violations as $line=>$validations) {
157
            /** @var ConstraintViolation $validation */
158
            foreach ($validations as $validation) {
159
                $key = $validation->__toString();
160
                if (!isset($tree[$key])) {
161
                    $tree[$key] = [];
162
                }
163
                $tree[$key][] = $line;
164
            }
165
        }
166
167
        $i = 0;
168
        foreach ($tree as $violation=>$lines) {
169
            $table->addRow([$violation, join(', ', Utils::numbersToRangeText($lines))]);
170
            ++$i;
171
172
            if ($i === self::MAX_VIOLATION_ERRORS) {
173
                $table->addRow(new TableSeparator());
174
                $table->addRow(array(null, 'There are more errors...'));
175
176
                break;
177
            }
178
        }
179
180
        if ($i > 0) {
181
            $table->render();
182
        }
183
    }
184
185 4
    protected function writeStatistics(array $statistics, Table $table)
186
    {
187 4
        $rows = [];
188 4
        foreach ($statistics as $k=>$v) {
189
            $rows[] = [$k, $v];
190 4
        }
191
192
        $table
193 4
            ->setHeaders(array('Statistics'))
194 4
            ->setRows($rows)
195
        ;
196 4
        $table->render();
197 4
    }
198
199
}
200