Completed
Pull Request — develop (#20)
by Lucas
06:15 queued 03:32
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\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