Completed
Pull Request — master (#8)
by
unknown
06:16
created

ImportCommand::execute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3.0327

Importance

Changes 9
Bugs 1 Features 3
Metric Value
c 9
b 1
f 3
dl 0
loc 19
ccs 11
cts 13
cp 0.8462
rs 9.4285
cc 3
eloc 12
nc 3
nop 2
crap 3.0327
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('offset', 'o', InputOption::VALUE_REQUIRED, 'Offset imported rows', 0)
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 4
        $offset = $input->getOption('offset');
67
68 4
        $this->import($output, $importerId, $sourceProviderId, $sourceId, $context, $offset, $limit, $isDryrun);
69 4
    }
70
71 4
    protected function import(OutputInterface $output, $importerId, $sourceProviderId, $sourceId, $context=null, $offset=0, $limit=null, $isDryrun=false)
72
    {
73 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>");
74
75 4
        $sourceId = Utils::parseSourceId($sourceId);
76 4
        $progress = new ProgressBar($output);
77
78
        //set limit
79 4
        if ($limit) {
80 1
            $output->writeln("Limiting import to <info>$limit</info> rows.");
81
82
            $this->getContainer()->get('event_dispatcher')->addListener(ImportConfigureEvent::AFTER_BUILD, function (ImportConfigureEvent $event) use ($offset, $limit) {
83
                $event->getImport()->importer()->filters()->add(new OffsetFilter($offset, $limit));
84 1
            });
85
        }
86
87
        //show discovered importer id
88 4
        if (empty($importerId)) {
89
            $this->getContainer()->get('event_dispatcher')->addListener(ImportRequestEvent::DISCOVERED, function (ImportRequestEvent $event) use ($output) {
90
                $importerId = $event->getImportRequest()->getImporterId();
91
                $output->writeln("Importer discovered: <info>$importerId</info>");
92 2
            });
93
        }
94
95
        /** @var ImportBuilder $importBuilder */
96 4
        $importBuilder = $this->getContainer()->get('mathielen_importengine.import.builder');
97
98 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...
99
100 4
        $import = $importBuilder->buildFromRequest($importRequest);
101
102
        //apply context info from commandline
103 4
        $importRun = $import->getRun();
104
105
        //status callback
106 4
        $this->getContainer()->get('event_dispatcher')->addListener(ImportItemEvent::AFTER_READ, function (ImportItemEvent $event) use ($output, &$progress) {
107
            /** @var ImportRun $importRun */
108
            $importRun = $event->getContext()->getRun();
109
            $stats = $importRun->getStatistics();
110
            $processed = isset($stats['processed'])?$stats['processed']:0;
111
            $max = $importRun->getInfo()['count'];
112
113
            if ($progress->getMaxSteps() != $max) {
114
                $progress = new ProgressBar($output, $max);
115
                $progress->start();
116
            }
117
118
            $progress->setProgress($processed);
119 4
        });
120
121
        /** @var ImportRunner $importRunner */
122 4
        $importRunner = $this->getContainer()->get('mathielen_importengine.import.runner');
123 4
        if ($isDryrun) {
124 1
            $importRunner->dryRun($import);
125
        } else {
126 3
            $importRunner->run($import);
127
        }
128
129 4
        $progress->finish();
130 4
        $output->writeln('');
131 4
        $output->writeln("<info>Import done</info>");
132 4
        $output->writeln('');
133
134 4
        $this->writeStatistics($importRun->getStatistics(), new Table($output));
135
136 4
        $this->writeValidationViolations(
137
            $import
138 4
                ->importer()
139 4
                ->validation()
140 4
                ->getViolations(),
141 4
            new Table($output));
142
143 4
        $output->writeln('');
144 4
    }
145
146 4
    protected function writeValidationViolations(array $violations, Table $table)
147
    {
148 4
        if (empty($violations)) {
149 4
            return;
150
        }
151
        $violations = $violations['source'] + $violations['target'];
152
153
        $table
154
            ->setHeaders(array('Constraint', 'Occurrences (lines)'))
155
        ;
156
157
        $tree = [];
158
        foreach ($violations as $line=>$validations) {
159
            /** @var ConstraintViolation $validation */
160
            foreach ($validations as $validation) {
161
                $key = $validation->__toString();
162
                if (!isset($tree[$key])) {
163
                    $tree[$key] = [];
164
                }
165
                $tree[$key][] = $line;
166
            }
167
        }
168
169
        $i = 0;
170
        foreach ($tree as $violation=>$lines) {
171
            $table->addRow([$violation, join(', ', Utils::numbersToRangeText($lines))]);
172
            ++$i;
173
174
            if ($i === self::MAX_VIOLATION_ERRORS) {
175
                $table->addRow(new TableSeparator());
176
                $table->addRow(array(null, 'There are more errors...'));
177
178
                break;
179
            }
180
        }
181
182
        if ($i > 0) {
183
            $table->render();
184
        }
185
    }
186
187 4
    protected function writeStatistics(array $statistics, Table $table)
188
    {
189 4
        $rows = [];
190 4
        foreach ($statistics as $k=>$v) {
191
            $rows[] = [$k, $v];
192
        }
193
194
        $table
195 4
            ->setHeaders(array('Statistics'))
196 4
            ->setRows($rows)
197
        ;
198 4
        $table->render();
199 4
    }
200
201
}
202