Completed
Push — develop ( daa113...d88284 )
by Narcotic
03:41 queued 01:13
created

ImportCommand::importResource()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 81
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 5.4212

Importance

Changes 11
Bugs 0 Features 0
Metric Value
c 11
b 0
f 0
dl 0
loc 81
ccs 29
cts 39
cp 0.7436
rs 8.3904
cc 5
eloc 54
nc 3
nop 8
crap 5.4212

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