Completed
Pull Request — master (#1757)
by
unknown
10:22
created

PopulateCommand::formatOptions()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 0
cp 0
rs 9.7333
c 0
b 0
f 0
cc 2
nc 1
nop 1
crap 6
1
<?php
2
3
/*
4
 * This file is part of the FOSElasticaBundle package.
5
 *
6
 * (c) FriendsOfSymfony <https://friendsofsymfony.github.com/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\ElasticaBundle\Command;
13
14
use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
15
use FOS\ElasticaBundle\Event\PostIndexPopulateEvent;
16
use FOS\ElasticaBundle\Event\PreIndexPopulateEvent;
17
use FOS\ElasticaBundle\Index\IndexManager;
18
use FOS\ElasticaBundle\Index\Resetter;
19
use FOS\ElasticaBundle\Persister\Event\OnExceptionEvent;
20
use FOS\ElasticaBundle\Persister\Event\PostAsyncInsertObjectsEvent;
21
use FOS\ElasticaBundle\Persister\Event\PostInsertObjectsEvent;
22
use FOS\ElasticaBundle\Persister\InPlacePagerPersister;
23
use FOS\ElasticaBundle\Persister\PagerPersisterInterface;
24
use FOS\ElasticaBundle\Persister\PagerPersisterRegistry;
25
use FOS\ElasticaBundle\Provider\PagerProviderRegistry;
26
use Symfony\Component\Console\Command\Command;
27
use Symfony\Component\Console\Helper\ProgressBar;
28
use Symfony\Component\Console\Helper\QuestionHelper;
29
use Symfony\Component\Console\Input\InputInterface;
30
use Symfony\Component\Console\Input\InputOption;
31
use Symfony\Component\Console\Output\OutputInterface;
32
use Symfony\Component\Console\Question\Question;
33
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
34
35
/**
36
 * Populate the search index.
37
 */
38
class PopulateCommand extends Command
39
{
40
    protected static $defaultName = 'fos:elastica:populate';
41
42
    /**
43
     * @var EventDispatcherInterface
44
     */
45
    private $dispatcher;
46
47
    /**
48
     * @var IndexManager
49
     */
50
    private $indexManager;
51
52
    /**
53
     * @var PagerProviderRegistry
54
     */
55
    private $pagerProviderRegistry;
56
57
    /**
58
     * @var PagerPersisterRegistry
59
     */
60
    private $pagerPersisterRegistry;
61
62
    /**
63
     * @var PagerPersisterInterface
64
     */
65
    private $pagerPersister;
66
67
    /**
68
     * @var Resetter
69
     */
70
    private $resetter;
71
72 4
    public function __construct(
73
        EventDispatcherInterface $dispatcher,
74
        IndexManager $indexManager,
75
        PagerProviderRegistry $pagerProviderRegistry,
76
        PagerPersisterRegistry $pagerPersisterRegistry,
77
        Resetter $resetter
78
    ) {
79 4
        parent::__construct();
80
81 4
        $this->dispatcher = $dispatcher;
82 4
        $this->indexManager = $indexManager;
83 4
        $this->pagerProviderRegistry = $pagerProviderRegistry;
84 4
        $this->pagerPersisterRegistry = $pagerPersisterRegistry;
85 4
        $this->resetter = $resetter;
86 4
    }
87
88 4
    protected function configure()
89
    {
90
        $this
91 4
            ->setName('fos:elastica:populate')
92 4
            ->addOption('index', null, InputOption::VALUE_OPTIONAL, 'The index to repopulate')
93 4
            ->addOption('no-reset', null, InputOption::VALUE_NONE, 'Do not reset index before populating')
94 4
            ->addOption('no-delete', null, InputOption::VALUE_NONE, 'Do not delete index after populate')
95 4
            ->addOption('sleep', null, InputOption::VALUE_REQUIRED, 'Sleep time between persisting iterations (microseconds)', 0)
96 4
            ->addOption('ignore-errors', null, InputOption::VALUE_NONE, 'Do not stop on errors')
97 4
            ->addOption('no-overwrite-format', null, InputOption::VALUE_NONE, 'Prevent this command from overwriting ProgressBar\'s formats')
98
99 4
            ->addOption('first-page', null, InputOption::VALUE_REQUIRED, 'The pager\'s page to start population from. Including the given page.', 1)
100 4
            ->addOption('last-page', null, InputOption::VALUE_REQUIRED, 'The pager\'s page to end population on. Including the given page.', null)
101 4
            ->addOption('max-per-page', null, InputOption::VALUE_REQUIRED, 'The pager\'s page size', 100)
102 4
            ->addOption('pager-persister', null, InputOption::VALUE_REQUIRED, 'The pager persister to be used to populate the index', InPlacePagerPersister::NAME)
103
104 4
            ->addOption('options', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Additional key=value values which are passed through to the provider. Define as --options=key=value.', [])
105
106 4
            ->setDescription('Populates search indexes from providers')
107
        ;
108
    }
109
110
    protected function initialize(InputInterface $input, OutputInterface $output)
111
    {
112
        $this->pagerPersister = $this->pagerPersisterRegistry->getPagerPersister($input->getOption('pager-persister'));
113
114
        if (!$input->getOption('no-overwrite-format')) {
115
            ProgressBar::setFormatDefinition('normal', " %current%/%max% [%bar%] %percent:3s%%\n%message%");
116
            ProgressBar::setFormatDefinition('verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%\n%message%");
117
            ProgressBar::setFormatDefinition('very_verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%\n%message%");
118
            ProgressBar::setFormatDefinition('debug', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%\n%message%");
119
        }
120
    }
121
122
    protected function execute(InputInterface $input, OutputInterface $output)
123
    {
124
        $indexes = (null !== $index = $input->getOption('index')) ? [$index] : \array_keys($this->indexManager->getAllIndexes());
125
        $reset = !$input->getOption('no-reset');
126
        $delete = !$input->getOption('no-delete');
127
128
        $options = array_merge(
129
            [
130
                'delete' => $delete,
131
                'reset' => $reset,
132
                'ignore_errors' => $input->getOption('ignore-errors'),
133
                'sleep' => $input->getOption('sleep'),
134
                'first_page' => $input->getOption('first-page'),
135
                'max_per_page' => $input->getOption('max-per-page')
136
            ],
137
            $this->formatOptions($input->getOption('options'))
138
        );
139
140
        if ($input->getOption('last-page')) {
141
            $options['last_page'] = $input->getOption('last-page');
142
        }
143
144
        if ($input->isInteractive() && $reset && 1 < $options['first_page']) {
145
            /** @var QuestionHelper $dialog */
146
            $dialog = $this->getHelperSet()->get('question');
147
            if (!$dialog->ask($input, $output, new Question('<question>You chose to reset the index and start indexing with an offset. Do you really want to do that?</question>'))) {
148
                return 1;
149
            }
150
        }
151
152
        foreach ($indexes as $index) {
153
            $this->populateIndex($output, $index, $reset, $options);
154
        }
155
156
        return 0;
157
    }
158
159
    /**
160
     * Recreates an index, populates it, and refreshes it.
161
     */
162
    private function populateIndex(OutputInterface $output, string $index, bool $reset, $options): void
163
    {
164
        $this->dispatcher->dispatch($event = new PreIndexPopulateEvent($index, $reset, $options));
0 ignored issues
show
Documentation introduced by
$event = new \FOS\Elasti...ndex, $reset, $options) is of type object<FOS\ElasticaBundl...\PreIndexPopulateEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
165
166
        if ($reset = $event->isReset()) {
167
            $output->writeln(\sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
168
            $this->resetter->resetIndex($index, true);
169
        }
170
171
        $offset = 1 < $options['first_page'] ? ($options['first_page'] - 1) * $options['max_per_page'] : 0;
172
        $loggerClosure = ProgressClosureBuilder::build($output, 'Populating', $index, $offset);
173
174
        $this->dispatcher->addListener(
175
            OnExceptionEvent::class,
176
            function (OnExceptionEvent $event) use ($loggerClosure) {
177
                $loggerClosure(
178
                    \count($event->getObjects()),
179
                    $event->getPager()->getNbResults(),
180
                    \sprintf('<error>%s</error>', $event->getException()->getMessage())
181
                );
182
            }
183
        );
184
185
        $this->dispatcher->addListener(
186
            PostInsertObjectsEvent::class,
187
            function (PostInsertObjectsEvent $event) use ($loggerClosure) {
188
                $loggerClosure(\count($event->getObjects()), $event->getPager()->getNbResults());
189
            }
190
        );
191
192
        $this->dispatcher->addListener(
193
            PostAsyncInsertObjectsEvent::class,
194
            function (PostAsyncInsertObjectsEvent $event) use ($loggerClosure) {
195
                $loggerClosure($event->getObjectsCount(), $event->getPager()->getNbResults(), $event->getErrorMessage());
196
            }
197
        );
198
199
        if ($options['ignore_errors']) {
200
            $this->dispatcher->addListener(
201
                OnExceptionEvent::class,
202
                function (OnExceptionEvent $event) {
203
                    if ($event->getException() instanceof BulkResponseException) {
204
                        $event->setIgnored(true);
205
                    }
206
                }
207
            );
208
        }
209
210
        $provider = $this->pagerProviderRegistry->getProvider($index);
211
        $pager = $provider->provide($options);
212
213
        $this->pagerPersister->insert($pager, \array_merge($options, ['indexName' => $index]));
214
215
        $this->dispatcher->dispatch(new PostIndexPopulateEvent($index, $reset, $options));
0 ignored issues
show
Documentation introduced by
new \FOS\ElasticaBundle\...ndex, $reset, $options) is of type object<FOS\ElasticaBundl...PostIndexPopulateEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
216
217
        $this->refreshIndex($output, $index);
218
    }
219
220
    /**
221
     * Refreshes an index.
222
     */
223
    private function refreshIndex(OutputInterface $output, string $index): void
224
    {
225
        $output->writeln(\sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
226
        $this->indexManager->getIndex($index)->refresh();
227
        $output->writeln('');
228
    }
229
230
    /**
231
     * Formats the options from key=value to ["key" => "value"]
232
     */
233
    private function formatOptions(array $options): array
234
    {
235
        return array_reduce($options, static function(array $acc, string $option) {
236
            if (strpos($option, '=') !== false) {
237
                $splitted = explode('=', $option);
238
                $key = array_shift($splitted);
239
240
                $acc[$key] = implode('=', $splitted);
241
242
            } else {
243
                $acc[] = $option;
244
            }
245
246
            return $acc;
247
        }, []);
248
    }
249
}
250