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

ImportCommand::validateUploadFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 7
cts 8
cp 0.875
rs 9.4285
cc 3
eloc 9
nc 3
nop 2
crap 3.0175
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