Completed
Pull Request — develop (#20)
by Lucas
06:15 queued 03:32
created

ImportCommand::importResource()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 86
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 6.4859

Importance

Changes 12
Bugs 0 Features 0
Metric Value
c 12
b 0
f 0
dl 0
loc 86
ccs 32
cts 42
cp 0.7619
rs 8.3234
cc 6
eloc 57
nc 6
nop 8
crap 6.4859

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * import json data into graviton
4
 *
5
 * Supports importing json data from either a single file or a complete folder of files.
6
 *
7
 * The data needs to contain frontmatter to hint where the bits and pieces should go.
8
 */
9
10
namespace Graviton\ImportExport\Command;
11
12
use Graviton\ImportExport\Exception\MissingTargetException;
13
use Graviton\ImportExport\Exception\JsonParseException;
14
use Graviton\ImportExport\Exception\UnknownFileTypeException;
15
use Graviton\ImportExport\Service\FileSender;
16
use Symfony\Component\Console\Input\InputArgument;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Finder\Finder;
21
use Symfony\Component\Yaml\Parser;
22
use Symfony\Component\VarDumper\Cloner\VarCloner;
23
use Symfony\Component\VarDumper\Dumper\CliDumper as Dumper;
24
use GuzzleHttp\Client;
25
use GuzzleHttp\Promise;
26
use GuzzleHttp\Exception\RequestException;
27
use GuzzleHttp\Exception\BadResponseException;
28
use Webuni\FrontMatter\FrontMatter;
29
use Webuni\FrontMatter\Document;
30
use Psr\Http\Message\ResponseInterface;
31
32
/**
33
 * @author   List of contributors <https://github.com/libgraviton/import-export/graphs/contributors>
34
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
35
 * @link     http://swisscom.ch
36
 */
37
class ImportCommand extends ImportCommandAbstract
38
{
39
    /**
40
     * @var Client
41
     */
42
    private $client;
43
44
    /**
45
     * @var FrontMatter
46
     */
47
    private $frontMatter;
48
49
    /**
50
     * @var Parser
51
     */
52
    private $parser;
53
54
    /**
55
     * @var VarCloner
56
     */
57
    private $cloner;
58
59
    /**
60
     * @var Dumper
61
     */
62
    private $dumper;
63
64
    /**
65
     * @var FileSender
66
     */
67
    private $filesender;
68
69
    /**
70
     * @param Client      $client      guzzle http client
71
     * @param Finder      $finder      symfony/finder instance
72
     * @param FrontMatter $frontMatter frontmatter parser
73
     * @param Parser      $parser      yaml/json parser
74
     * @param VarCloner   $cloner      var cloner for dumping reponses
75
     * @param Dumper      $dumper      dumper for outputing responses
76
     * @param FileSender  $filesender  helper service for uploading files
77
     */
78 5
    public function __construct(
79
        Client $client,
80
        Finder $finder,
81
        FrontMatter $frontMatter,
82
        Parser $parser,
83
        VarCloner $cloner,
84
        Dumper $dumper,
85
        FileSender $filesender
86
    ) {
87 5
        parent::__construct(
88
            $finder
89
        );
90 5
        $this->client = $client;
91 5
        $this->frontMatter = $frontMatter;
92 5
        $this->parser = $parser;
93 5
        $this->cloner = $cloner;
94 5
        $this->dumper = $dumper;
95 5
        $this->filesender = $filesender;
96 5
    }
97
98
    /**
99
     * Configures the current command.
100
     *
101
     * @return void
102
     */
103 5
    protected function configure()
104
    {
105
        $this
106 5
            ->setName('graviton:import')
107 5
            ->setDescription('Import files from a folder or file.')
108 5
            ->addOption(
109 5
                'rewrite-host',
110 5
                'r',
111 5
                InputOption::VALUE_OPTIONAL,
112 5
                'Replace the value of this option with the <host> value before importing.',
113 5
                'http://localhost'
114
            )
115 5
            ->addOption(
116 5
                'rewrite-to',
117 5
                't',
118 5
                InputOption::VALUE_OPTIONAL,
119 5
                'String to use as the replacement value for the [REWRITE-HOST] string.',
120 5
                '<host>'
121
            )
122 5
            ->addOption(
123 5
                'sync-requests',
124 5
                's',
125 5
                InputOption::VALUE_NONE,
126 5
                'Send requests synchronously'
127
            )
128 5
            ->addArgument(
129 5
                'host',
130 5
                InputArgument::REQUIRED,
131 5
                'Protocol and host to load data into (ie. https://graviton.nova.scapp.io)'
132
            )
133 5
            ->addArgument(
134 5
                'file',
135 5
                InputArgument::REQUIRED + InputArgument::IS_ARRAY,
136 5
                'Directories or files to load'
137
            );
138 5
    }
139
140
    /**
141
     * Executes the current command.
142
     *
143
     * @param Finder          $finder Finder
144
     * @param InputInterface  $input  User input on console
145
     * @param OutputInterface $output Output of the command
146
     *
147
     * @return void
148
     */
149 5
    protected function doImport(Finder $finder, InputInterface $input, OutputInterface $output)
150
    {
151 5
        $host = $input->getArgument('host');
152 5
        $rewriteHost = $input->getOption('rewrite-host');
153 5
        $rewriteTo = $input->getOption('rewrite-to');
154 5
        if ($rewriteTo === $this->getDefinition()->getOption('rewrite-to')->getDefault()) {
155 5
            $rewriteTo = $host;
156
        }
157 5
        $sync = $input->getOption('sync-requests');
158
159 5
        $this->importPaths($finder, $output, $host, $rewriteHost, $rewriteTo, $sync);
160 4
    }
161
162
    /**
163
     * @param Finder          $finder      finder primmed with files to import
164
     * @param OutputInterface $output      output interfac
165
     * @param string          $host        host to import into
166
     * @param string          $rewriteHost string to replace with value from $rewriteTo during loading
167
     * @param string          $rewriteTo   string to replace value from $rewriteHost with during loading
168
     * @param boolean         $sync        send requests syncronously
169
     *
170
     * @return void
171
     *
172
     * @throws MissingTargetException
173
     */
174 5
    protected function importPaths(
175
        Finder $finder,
176
        OutputInterface $output,
177
        $host,
178
        $rewriteHost,
179
        $rewriteTo,
180
        $sync = false
181
    ) {
182 5
        $promises = [];
183 5
        foreach ($finder as $file) {
184 5
            $doc = $this->frontMatter->parse($file->getContents());
185
186 5
            $output->writeln("<info>Loading data from ${file}</info>");
187
188 5
            if (!array_key_exists('target', $doc->getData())) {
189 1
                throw new MissingTargetException('Missing target in \'' . $file . '\'');
190
            }
191
192 4
            $targetUrl = sprintf('%s%s', $host, $doc->getData()['target']);
193
194 4
            $promises[] = $this->importResource(
195
                $targetUrl,
196 4
                (string) $file,
197
                $output,
198
                $doc,
199
                $host,
200
                $rewriteHost,
201
                $rewriteTo,
202
                $sync
203
            );
204
        }
205
206
        try {
207
            Promise\unwrap($promises);
208
        } catch (\GuzzleHttp\Exception\ClientException $e) {
209
            // silently ignored since we already output an error when the promise fails
210
        }
211
    }
212
213
    /**
214
     * @param string          $targetUrl   target url to import resource into
215
     * @param string          $file        path to file being loaded
216
     * @param OutputInterface $output      output of the command
217
     * @param Document        $doc         document to load
218
     * @param string          $host        host to import into
219
     * @param string          $rewriteHost string to replace with value from $host during loading
220
     * @param string          $rewriteTo   string to replace value from $rewriteHost with during loading
221
     * @param boolean         $sync        send requests syncronously
222
     *
223
     * @return Promise\Promise|null
224
     */
225
    protected function importResource(
226
        $targetUrl,
227
        $file,
228
        OutputInterface $output,
229
        Document $doc,
230
        $host,
0 ignored issues
show
Unused Code introduced by
The parameter $host 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...
231
        $rewriteHost,
232
        $rewriteTo,
233
        $sync = false
234
    ) {
235 4
        $content = str_replace($rewriteHost, $rewriteTo, $doc->getContent());
236 4
        $uploadFile = $this->validateUploadFile($doc, $file);
237
238
        $successFunc = function (ResponseInterface $response) use ($output) {
239 3
            $output->writeln(
240 3
                '<comment>Wrote ' . $response->getHeader('Link')[0] . '</comment>'
241
            );
242 4
        };
243
244
        $errFunc = function (RequestException $e) use ($output, $file) {
245 1
            $output->writeln(
246 1
                '<error>' . str_pad(
247
                    sprintf(
248 1
                        'Failed to write <%s> from \'%s\' with message \'%s\'',
249 1
                        $e->getRequest()->getUri(),
250
                        $file,
251 1
                        $e->getMessage()
252
                    ),
253 1
                    140,
254 1
                    ' '
255 1
                ) . '</error>'
256
            );
257 1
            if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
258 1
                $this->dumper->dump(
259 1
                    $this->cloner->cloneVar(
260 1
                        $this->parser->parse($e->getResponse()->getBody(), false, false, true)
261
                    ),
262
                    function ($line, $depth) use ($output) {
263 1
                        if ($depth > 0) {
264 1
                            $output->writeln(
265 1
                                '<error>' . str_pad(str_repeat('  ', $depth) . $line, 140, ' ') . '</error>'
266
                            );
267
                        }
268 1
                    }
269
                );
270
            }
271 4
        };
272
273 4
        $client = $this->client;
274 4
        if ($uploadFile !== false) {
275 1
            $client = $this->filesender;
276
        }
277
278 4
        if ($sync === false) {
279 4
            $promise = $client->requestAsync(
280 4
                'PUT',
281
                $targetUrl,
282
                [
283 4
                    'json' => $this->parseContent($content, $file),
284 4
                    'upload' => $uploadFile
285
                ]
286
            );
287 4
            $promise->then($successFunc, $errFunc);
288
        } else {
289
            $promise = new Promise\Promise;
290
            try {
291
                $promise->resolve(
292
                    $successFunc(
293
                        $client->request(
294
                            'PUT',
295
                            $targetUrl,
296
                            [
297
                                'json' => $this->parseContent($content, $file),
298
                                'upload' => $uploadFile
299
                            ]
300
                        )
301
                    )
302
                );
303
            } catch (BadResponseException $e) {
304
                $promise->resolve(
305
                    $errFunc($e)
306
                );
307
            }
308
        }
309 4
        return $promise;
310
    }
311
312
    /**
313
     * parse contents of a file depending on type
314
     *
315
     * @param string $content contents part of file
316
     * @param string $file    full path to file
317
     *
318
     * @return mixed
319
     */
320
    protected function parseContent($content, $file)
321
    {
322 4
        if (substr($file, -5) == '.json') {
323 3
            $data = json_decode($content);
324 3
            if (json_last_error() !== JSON_ERROR_NONE) {
325
                throw new JsonParseException(
326
                    sprintf(
327 3
                        '%s in %s',
328
                        json_last_error_msg(),
329
                        $file
330
                    )
331
                );
332
            }
333 1
        } elseif (substr($file, -4) == '.yml') {
334 1
            $data = $this->parser->parse($content);
335
        } else {
336
            throw new UnknownFileTypeException($file);
337
        }
338
339 4
        return $data;
340
    }
341
342
    /**
343
     * Checks if file exists and return qualified fileName location
344
     *
345
     * @param Document $doc        Data source for import data
346
     * @param string   $originFile Original full filename used toimport
347
     * @return false|string
348
     */
349
    private function validateUploadFile(Document $doc, $originFile)
350
    {
351 4
        $documentData = $doc->getData();
352
353 4
        if (!array_key_exists('file', $documentData)) {
354 3
            return false;
355
        }
356
357
        // Find file
358 1
        $fileName = dirname($originFile) . DIRECTORY_SEPARATOR . $documentData['file'];
359 1
        $fileName = str_replace('//', '/', $fileName);
360 1
        if (!file_exists($fileName)) {
361
            return false;
362
        }
363
364 1
        return $fileName;
365
    }
366
}
367