Completed
Push — ezp25946-migrate_files_to_othe... ( e1d3cb...7924de )
by
unknown
12:27
created

MigrateFilesCommand::execute()   D

Complexity

Conditions 12
Paths 114

Size

Total Lines 87
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 53
nc 114
nop 2
dl 0
loc 87
rs 4.7873
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
/**
4
 * File containing the MigrateFilesCommand class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Bundle\EzPublishIOBundle\Command;
10
11
use eZ\Bundle\EzPublishIOBundle\Migration\FileListerRegistry;
12
use eZ\Bundle\EzPublishIOBundle\Migration\FileMigratorInterface;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Helper\ProgressBar;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Console\Question\ConfirmationQuestion;
19
20
class MigrateFilesCommand extends Command
21
{
22
    /** @var mixed Configuration for metadata handlers */
23
    private $configuredMetadataHandlers;
24
25
    /** @var mixed Configuration for binary data handlers */
26
    private $configuredBinarydataHandlers;
27
28
    /** @var \eZ\Bundle\EzPublishIOBundle\Migration\FileListerRegistry */
29
    private $fileListerRegistry;
30
31
    /** @var \eZ\Bundle\EzPublishIOBundle\Migration\FileListerInterface[] */
32
    private $fileListers;
33
34
    /** @var \eZ\Bundle\EzPublishIOBundle\Migration\FileMigratorInterface */
35
    private $fileMigrator;
36
37
    public function __construct(
38
        $configuredMetadataHandlers,
39
        $configuredBinarydataHandlers,
40
        FileListerRegistry $fileListerRegistry,
41
        FileMigratorInterface $fileMigrator
42
    ) {
43
        $this->configuredMetadataHandlers = $configuredMetadataHandlers;
44
        $this->configuredBinarydataHandlers = $configuredBinarydataHandlers;
45
        $this->configuredMetadataHandlers['default'] = [];
46
        $this->configuredBinarydataHandlers['default'] = [];
47
48
        $this->fileListerRegistry = $fileListerRegistry;
49
        $this->fileMigrator = $fileMigrator;
50
51
        foreach ($this->fileListerRegistry->getIdentifiers() as $fileListerIdentifier) {
52
            $this->fileListers[] = $this->fileListerRegistry->getItem($fileListerIdentifier);
53
        }
54
55
        parent::__construct();
56
    }
57
58
    protected function configure()
59
    {
60
        $this
61
            ->setName('ezplatform:io:migrate-files')
62
            ->setDescription('Migrates files from one IO repository to another')
63
            ->addOption('from', null, InputOption::VALUE_REQUIRED, 'Migrate from <from_metadata_handler>,<from_binarydata_handler>')
64
            ->addOption('to', null, InputOption::VALUE_REQUIRED, 'Migrate to <to_metadata_handler>,<to_binarydata_handler>')
65
            ->addOption('list-io-configs', null, InputOption::VALUE_NONE, 'List available IO configurations')
66
            ->addOption('remove-files', null, InputOption::VALUE_NONE, 'Remove source files after copying')
67
            ->addOption('bulk-count', null, InputOption::VALUE_REQUIRED, 'Number of files processed at once', 100)
68
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Execute a dry run')
69
            ->setHelp(
70
                <<<EOT
71
The command <info>%command.name%</info> migrates files from one IO repository
72
to another.
73
74
It can for example be used to migrate local files from the default IO
75
configuration to a new IO configuration, like a clustered setup.
76
77
The <info>--from</info> and <info>--to</info> values must be specified as <info><metadata_handler>,<binarydata_handler></info>.
78
If <info>--from</info> is omitted, the default IO configuration will be used.
79
If <info>--to</info> is omitted, the first non-default IO configuration will be used.
80
81
<fg=red>During the script execution the files should not be modified. To avoid
82
surprises you are advised to create a backup and/or execute a dry run before
83
proceeding with actual update.</>
84
85
Since this script can potentially run for a very long time, to avoid memory
86
exhaustion run it in production environment using <info>--env=prod</info> switch.
87
88
EOT
89
            );
90
    }
91
92
    protected function execute(InputInterface $input, OutputInterface $output)
93
    {
94
        if ($input->getOption('list-io-configs')) {
95
            $this->outputConfiguredHandlers($output);
96
97
            return;
98
        }
99
100
        $bulkCount = (int)$input->getOption('bulk-count');
101
        if ($bulkCount < 1) {
102
            $output->writeln('The value for --bulk-count must be a positive integer.');
103
104
            return;
105
        }
106
107
        $output->writeln($this->getProcessedHelp());
108
109
        $fromHandlers = $input->getOption('from') ? explode(',', $input->getOption('from')) : null;
110
        $toHandlers = $input->getOption('to') ? explode(',', $input->getOption('to')) : null;
111
112
        if (!$fromHandlers) {
113
            $fromHandlers = ['default', 'default'];
114
        }
115
        if (!$toHandlers) {
116
            $toHandlers = [
117
                array_keys($this->configuredMetadataHandlers)[0],
118
                array_keys($this->configuredBinarydataHandlers)[0],
119
            ];
120
        }
121
122
        if (!$this->validateHandlerOptions($fromHandlers, $toHandlers, $output)) {
123
            return;
124
        }
125
126
        $output->writeln([
127
            "Migrating from '$fromHandlers[0],$fromHandlers[1]' to '$toHandlers[0],$toHandlers[1]'",
128
            '',
129
        ]);
130
131
        $totalCount = 0;
132
        foreach ($this->fileListers as $fileLister) {
133
            $fileLister->setIODataHandlersByIdentifiers(
134
                $fromHandlers[0],
135
                $fromHandlers[1],
136
                $toHandlers[0],
137
                $toHandlers[1]
138
            );
139
140
            $totalCount += $fileLister->countFiles();
141
        }
142
        $this->fileMigrator->setIODataHandlersByIdentifiers(
143
            $fromHandlers[0],
144
            $fromHandlers[1],
145
            $toHandlers[0],
146
            $toHandlers[1]
147
        );
148
149
        $output->writeln([
150
            'Total number of files to update: ' . ($totalCount === null ? 'unknown' : $totalCount),
151
            '',
152
        ]);
153
154
        if ($totalCount === 0) {
155
            $output->writeln('Nothing to process.');
156
157
            return;
158
        }
159
160
        $helper = $this->getHelper('question');
161
        $question = new ConfirmationQuestion(
162
            '<question>Are you sure you want to proceed?</question> ',
163
            false
164
        );
165
166
        if (!$helper->ask($input, $output, $question)) {
167
            $output->writeln('Aborting.');
168
169
            return;
170
        }
171
172
        $this->migrateFiles(
173
            $totalCount,
174
            $bulkCount,
175
            $input->getOption('dry-run'),
176
            $output
177
        );
178
    }
179
180
    /**
181
     * Output the configured meta/binary data handlers.
182
     *
183
     * @param OutputInterface $output
184
     */
185
    protected function outputConfiguredHandlers(OutputInterface $output)
186
    {
187
        $output->writeln(
188
            'Configured meta data handlers: ' . implode(', ', array_keys($this->configuredMetadataHandlers))
189
        );
190
        $output->writeln(
191
            'Configured binary data handlers: ' . implode(', ', array_keys($this->configuredBinarydataHandlers))
192
        );
193
    }
194
195
    /**
196
     * Verify that the handler options have been set to meaningful values.
197
     *
198
     * @param mixed $fromHandlers
199
     * @param mixed $toHandlers
200
     * @param OutputInterface $output
201
     * @return bool
202
     */
203
    protected function validateHandlerOptions(
204
        $fromHandlers,
205
        $toHandlers,
206
        OutputInterface $output
207
    ) {
208
        foreach (['From' => $fromHandlers, 'To' => $toHandlers] as $direction => $handlers) {
209
            $lowerDirection = strtolower($direction);
210
            if (count($handlers) !== 2) {
211
                $output->writeln(
212
                    "Enter two comma separated values for the --$lowerDirection option: " .
213
                    "<{$lowerDirection}_metadata_handler>,<{$lowerDirection}_binarydata_handler>"
214
                );
215
216
                return false;
217
            }
218
219
            foreach (['meta' => $handlers[0], 'binary' => $handlers[1]] as $fileDataType => $handler) {
220
                if (!in_array($handler, array_keys(
221
                    $fileDataType === 'meta' ? $this->configuredMetadataHandlers : $this->configuredBinarydataHandlers
222
                ))) {
223
                    $output->writeln("$direction $fileDataType data handler '$handler' is not configured.");
224
                    $this->outputConfiguredHandlers($output);
225
226
                    return false;
227
                }
228
            }
229
        }
230
231
        if ($fromHandlers === $toHandlers) {
232
            $output->writeln('From and to handlers are the same. Nothing to do.');
233
234
            return false;
235
        }
236
237
        return true;
238
    }
239
240
    /**
241
     * Migrate files.
242
     *
243
     * @param int|null $totalFileCount Total count of files, null if unknown
244
     * @param int $bulkCount Number of files to process in each batch
245
     * @param bool $dryRun
246
     * @param OutputInterface $output
247
     */
248
    protected function migrateFiles(
249
        $totalFileCount = null,
250
        $bulkCount,
251
        $dryRun,
252
        OutputInterface $output
253
    ) {
254
        $progress = new ProgressBar($output, $totalFileCount);
255
        if ($totalFileCount) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $totalFileCount of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
256
            $progress->setFormat("%message%\n %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%");
257
        } else {
258
            $progress->setFormat("%message%\n %current% [%bar%] %elapsed:6s% %memory:6s%");
259
        }
260
261
        $output->writeln('');
262
        $progress->setMessage('Starting migration...');
263
        $progress->start();
264
265
        $elapsedFileCount = 0;
266
        $timestamp = microtime(true);
267
        $updateFrequency = 1;
268
269
        foreach ($this->fileListers as $fileLister) {
270
            $pass = 0;
271
            do {
272
                $metadataList = $fileLister->loadMetadataList($bulkCount, $pass * $bulkCount);
273
274
                foreach ($metadataList as $metadata) {
275
                    if (!$dryRun) {
276
                        $this->fileMigrator->migrateFile($metadata);
277
                    }
278
279
                    $progress->setMessage('Updated file ' . $metadata->id);
280
                    $progress->advance();
281
                    ++$elapsedFileCount;
282
283
                    // Magic that ensures the progressbar is updated ca. once per second
284
                    if (($elapsedFileCount % $updateFrequency) === 0) {
285
                        $newTimestamp = microtime(true);
286
                        if ($newTimestamp - $timestamp > 0.5 && $updateFrequency > 1) {
287
                            $updateFrequency = (int)($updateFrequency / 2);
288
                            $progress->setRedrawFrequency($updateFrequency);
289
                        } elseif ($newTimestamp - $timestamp < 0.1 && $updateFrequency < 10000) {
290
                            $updateFrequency = $updateFrequency * 2;
291
                            $progress->setRedrawFrequency($updateFrequency);
292
                        }
293
                        $timestamp = $newTimestamp;
294
                    }
295
                }
296
297
                ++$pass;
298
            } while (count($metadataList) > 0);
299
        }
300
301
        $progress->setMessage('');
302
        $progress->finish();
303
304
        $output->writeln("\n\nFinished processing $elapsedFileCount files.");
305
    }
306
}
307