Completed
Push — master ( e21ac0...e43437 )
by Markus
05:40
created

ImportCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1.0046

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1.0046
1
<?php
2
3
namespace Mathielen\ImportEngineBundle\Command;
4
5
use Ddeboer\DataImport\Filter\OffsetFilter;
6
use Mathielen\DataImport\Event\ImportItemEvent;
7
use Mathielen\ImportEngine\Event\ImportConfigureEvent;
8
use Mathielen\ImportEngine\Event\ImportRequestEvent;
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\Component\Console\Command\Command;
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\EventDispatcher\EventDispatcherInterface;
23
use Symfony\Component\Validator\ConstraintViolation;
24
25
class ImportCommand extends Command
26
{
27
    const MAX_VIOLATION_ERRORS = 10;
28
29
    /**
30 4
     * @var ImportBuilder
31
     */
32 4
    private $importBuilder;
33 4
34 4
    /**
35 4
     * @var ImportRunner
36 4
     */
37 4
    private $importRunner;
38 4
39 4
    /**
40
     * @var EventDispatcherInterface
41 4
     */
42
    private $eventDispatcher;
43 4
44
    public function __construct(
45 4
        ImportBuilder $importBuilder,
46 4
        ImportRunner $importRunner,
47
        EventDispatcherInterface $eventDispatcher)
48
    {
49 4
        parent::__construct('importengine:import');
50
51 4
        $this->importBuilder = $importBuilder;
52
        $this->importRunner = $importRunner;
53 4
        $this->eventDispatcher = $eventDispatcher;
54
    }
55 4
56 4
    protected function configure()
57 4
    {
58 4
        $this
59 4
            ->setDescription('Imports data with a definied importer')
60
            ->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]\"")
61
            ->addArgument('source_provider', InputArgument::OPTIONAL, 'id of source provider', 'default')
62
            ->addOption('importer', 'i', InputOption::VALUE_REQUIRED, 'id/name of importer')
63
            ->addOption('context', 'c', InputOption::VALUE_REQUIRED, 'Supply optional context information to import. Supply key-value data in query style: key=value&otherkey=othervalue&...')
64
            ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit imported rows')
65 4
            ->addOption('dryrun', 'd', InputOption::VALUE_NONE, 'Do not import - Validation only')
66
        ;
67 4
    }
68
69
    protected function execute(InputInterface $input, OutputInterface $output)
70
    {
71 4
        $importerId = $input->getOption('importer');
72 4
        $sourceProviderId = $input->getArgument('source_provider');
73
        $sourceId = $input->getArgument('source_id');
74 4
        $isDryrun = $input->getOption('dryrun');
75
        if ($context = $input->getOption('context')) {
76 4
            //parse key=value&key=value string to array
77
            if (strpos($context, '=') !== false) {
78 4
                parse_str($input->getOption('context'), $context);
79 4
            }
80
        }
81
        $limit = $input->getOption('limit');
82 4
83 1
        if (empty($importerId) && empty($sourceId)) {
84
            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.');
85
        }
86
87 1
        $this->import($output, $importerId, $sourceProviderId, $sourceId, $context, $limit, $isDryrun);
88
    }
89
90
    protected function import(OutputInterface $output, $importerId, $sourceProviderId, $sourceId, $context = null, $limit = null, $isDryrun = false)
0 ignored issues
show
Unused Code introduced by
The parameter $context 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...
91 4
    {
92
        $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>");
93
94
        $sourceId = Utils::parseSourceId($sourceId);
95 2
        $progress = new ProgressBar($output);
96
97
        //set limit
98
        if ($limit) {
99 4
            $output->writeln("Limiting import to <info>$limit</info> rows.");
100
101 4
            $this->eventDispatcher->addListener(ImportConfigureEvent::AFTER_BUILD, function (ImportConfigureEvent $event) use ($limit) {
102
                $event->getImport()->importer()->filters()->add(new OffsetFilter(0, $limit));
103 4
            });
104
        }
105
106 4
        //show discovered importer id
107
        if (empty($importerId)) {
108
            $this->eventDispatcher->addListener(ImportRequestEvent::DISCOVERED, function (ImportRequestEvent $event) use ($output) {
109 4
                $importerId = $event->getImportRequest()->getImporterId();
110
                $output->writeln("Importer discovered: <info>$importerId</info>");
111
            });
112
        }
113
114
        $importRequest = new ImportRequest($sourceId, $sourceProviderId, $importerId, Utils::whoAmI().'@CLI');
115
116
        $import = $this->importBuilder->buildFromRequest($importRequest);
117
118
        //apply context info from commandline
119
        $importRun = $import->getRun();
120
121
        //status callback
122 4
        $this->eventDispatcher->addListener(ImportItemEvent::AFTER_READ, function (ImportItemEvent $event) use ($output, &$progress) {
123
            /** @var ImportRun $importRun */
124
            $importRun = $event->getContext()->getRun();
125 4
            $stats = $importRun->getStatistics();
126 4
            $processed = isset($stats['processed']) ? $stats['processed'] : 0;
127 1
            $max = $importRun->getInfo()['count'];
128
129 3
            if ($progress->getMaxSteps() != $max) {
130
                $progress = new ProgressBar($output, $max);
131
                $progress->start();
132 4
            }
133 4
134 4
            $progress->setProgress($processed);
135 4
        });
136
137 4
        if ($isDryrun) {
138
            $this->importRunner->dryRun($import);
139 4
        } else {
140
            $this->importRunner->run($import);
141 4
        }
142 4
143 4
        $progress->finish();
144 4
        $output->writeln('');
145
        $output->writeln('<info>Import done</info>');
146 4
        $output->writeln('');
147 4
148
        $this->writeStatistics($importRun->getStatistics(), new Table($output));
149 4
150
        $this->writeValidationViolations(
151 4
            $import
152 4
                ->importer()
153
                ->validation()
154
                ->getViolations(),
155
            new Table($output));
156
157
        $output->writeln('');
158
    }
159
160
    protected function writeValidationViolations(array $violations, Table $table)
161
    {
162
        if (empty($violations)) {
163
            return;
164
        }
165
        $violations = $violations['source'] + $violations['target'];
166
167
        $table
168
            ->setHeaders(array('Constraint', 'Occurrences (lines)'))
169
        ;
170
171
        $tree = [];
172
        foreach ($violations as $line => $validations) {
173
            /** @var ConstraintViolation $validation */
174
            foreach ($validations as $validation) {
175
                $key = $validation->__toString();
176
                if (!isset($tree[$key])) {
177
                    $tree[$key] = [];
178
                }
179
                $tree[$key][] = $line;
180
            }
181
        }
182
183
        $i = 0;
184
        foreach ($tree as $violation => $lines) {
185
            $table->addRow([$violation, implode(', ', Utils::numbersToRangeText($lines))]);
186
            ++$i;
187
188
            if ($i === self::MAX_VIOLATION_ERRORS) {
189
                $table->addRow(new TableSeparator());
190 4
                $table->addRow(array(null, 'There are more errors...'));
191
192 4
                break;
193 4
            }
194
        }
195
196
        if ($i > 0) {
197
            $table->render();
198 4
        }
199 4
    }
200
201 4
    protected function writeStatistics(array $statistics, Table $table)
202 4
    {
203
        $rows = [];
204
        foreach ($statistics as $k => $v) {
205
            $rows[] = [$k, $v];
206
        }
207
208
        $table
209
            ->setHeaders(array('Statistics'))
210
            ->setRows($rows)
211
        ;
212
        $table->render();
213
    }
214
}
215