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

ImportCommand::import()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 74
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 74
ccs 31
cts 31
cp 1
rs 6.2894
c 0
b 0
f 0
cc 8
eloc 42
nc 8
nop 7
crap 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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