Completed
Push — feature/directimport-for-initi... ( 2461f0 )
by
unknown
11:07
created

DirectImportCommand::updateReferences()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 47
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 47
rs 8.6845
cc 4
eloc 31
nc 4
nop 2
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: taachja1
5
 * Date: 03/03/16
6
 * Time: 16:42
7
 *
8
 * Import direct into db
9
 *
10
 * php vendor/graviton/graviton/bin/graviton DirectImportCommand:initialData /path-to-data-folder
11
 * Run first /initial and then /data
12
 */
13
14
namespace Graviton\CoreBundle\Command;
15
16
use Graviton\I18nBundle\Document\TranslatableDocumentInterface;
17
use GuzzleHttp\Client;
18
use GuzzleHttp\Exception\ClientException;
19
use GuzzleHttp\Exception\ServerException;
20
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\DependencyInjection\Container;
25
use Symfony\Component\Finder\Finder;
26
use Symfony\Component\Finder\SplFileInfo;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\Yaml\Exception\ParseException;
29
use Symfony\Component\Yaml\Yaml;
30
use Graviton\I18nBundle\Document\Translatable;
31
use Graviton\I18nBundle\Model\Translatable as ModelTranslatable;
32
use Symfony\Component\Console\Helper\ProgressBar;
33
use Doctrine\ODM\MongoDB\DocumentManager;
34
use Graviton\RestBundle\Validator\Form;
35
use Graviton\ExceptionBundle\Exception\ValidationException;
36
use Graviton\DocumentBundle\Service\FormDataMapperInterface;
37
use Graviton\RestBundle\Model\DocumentModel;
38
39
/**
40
 * Class DirectImportCommand
41
 * @package Graviton\CoreBundle\Command
42
 * @author  List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
43
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
44
 * @link    http://swisscom.ch
45
 * @return  void
46
 */
47
class DirectImportCommand extends ContainerAwareCommand
48
{
49
    /** @var ModelTranslatable */
50
    protected $modelTranslatable;
51
52
    /** @var Container */
53
    protected $container;
54
55
    /**
56
     * Starting Command
57
     * @return void
58
     */
59
    protected function configure()
60
    {
61
        $this
62
            ->setName('DirectImportCommand:initialData')
63
            ->setDescription('Greet someone')
64
            ->addArgument('location', InputArgument::REQUIRED, 'Full path to location of folder to import');
65
    }
66
67
    /**
68
     * @param  InputInterface  $input  Sf command line input
69
     * @param  OutputInterface $output Sf command line output
70
     * @return void
71
     */
72
    protected function execute(InputInterface $input, OutputInterface $output)
73
    {
74
        $location = $input->getArgument('location');
75
76
        $this->container = $container = $this->getContainer();
0 ignored issues
show
Documentation Bug introduced by
$container = $this->getContainer() is of type object<Symfony\Component...ion\ContainerInterface>, but the property $container was declared to be of type object<Symfony\Component...ncyInjection\Container>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
77
        $this->modelTranslatable = $container->get('graviton.i18n.model.translatable');
78
        /** @var DocumentManager $documentManager */
79
        $documentManager = $container->get('doctrine_mongodb.odm.default_document_manager');
80
81
        $output->writeln('Importing from: '.$location);
82
83
        /**
84
         * @param SplFileInfo $file
85
         * @return bool
86
         */
87
        $filter = function (SplFileInfo $file) {
88
            if ($file->getExtension() !== 'yml' || $file->isDir()) {
89
                return false;
90
            }
91
            return true;
92
        };
93
94
        $finder  = new Finder();
95
        $finder->files()->in($location)->ignoreDotFiles(true)->filter($filter);
96
97
        $totalFiles = $finder->count();
98
        $progress = new ProgressBar($output, $totalFiles);
99
        $errors = [];
100
        $success = [];
101
102
        // CoreTypes and Refferences
103
        $referenceObjects = [];
104
105
        // Do this by rest
106
        $restObjects = [];
107
108
        /** @var SplFileInfo $file */
109
        foreach ($finder as $file) {
110
            $fileName = str_replace($location, '', $file->getPathname());
111
112
            $progress->advance();
113
114
            $contents = explode('---', $file->getContents());
115
            if (!$contents || count($contents)!==3) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $contents of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
116
                $errors[$fileName] = 'Content of file is incorrect, could not parse it with --- ';
117
                continue;
118
            }
119
120
            $target = trim(str_replace('target:', '', $contents[1]));
121
            $yaml = $contents[2];
122
123
            // Make Service Name:
124
            $targets = explode('/', $target);
125
            if (!$targets || count($targets)<3) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $targets of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
126
                $errors[$fileName] = 'Target is not correctly defined: '.json_encode($targets);
127
                continue;
128
            }
129
            $domain = $targets[1];
130
131
            try {
132
                $data = Yaml::parse($yaml);
133
            } catch (ParseException $e) {
134
                $errors[$fileName] = 'Could not parse yml file';
135
                continue;
136
            }
137
138
            if (!$data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
139
                $errors[$fileName] = 'Yml file is empty or parsed as empty.';
140
                continue;
141
            }
142
143
            // Locate class name graviton.i18n.document.language
144
            $service = $this->findServiceObject($targets, $fileName);
145
            $objectClass = $service['class'];
146
            $serviceName = $service['service'];
147
148
            if (!$objectClass) {
149
                $restObjects[] = ['target'=> $target, 'data' =>$data];
150
                continue;
151
            }
152
153
            if (array_key_exists('items', $data) && is_array($data['items'])) {
154 View Code Duplication
                foreach ($data['items'] as $item) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155
                    $object = clone $objectClass;
156
                    $translated = [];
157
                    if ($object instanceof TranslatableDocumentInterface) {
158
                        $translated = $object->getTranslatableFields();
159
                    }
160
                    if (is_object($object)) {
161
                        foreach ($item as $key => $value) {
162
                            $method = 'set' . ucfirst($key);
163
                            if (method_exists($object, $method)) {
164
                                if (is_array($value) && in_array($key, $translated)) {
165
                                    $this->generateLanguages($domain, $value);
166
                                } elseif (is_array($value)) {
167
                                    $referenceObjects[$key] = [
168
                                        'data'    => $data,
169
                                        'file'    => $fileName,
170
                                        'service' => $serviceName
171
                                    ];
172
                                } else {
173
                                    try {
174
                                        $object->$method($value);
175
                                    } catch (\Exception $e) {
176
                                        $errors[$fileName] = 'Method: '.$method.
177
                                            ' with:'.$value.' error:'.$e->getMessage();
178
                                        continue;
179
                                    }
180
                                }
181
                            }
182
                        }
183
                        $documentManager->persist($object);
184
                    }
185
                }
186
            } else {
187
                $object = clone $objectClass;
188
                $method = false;
189
                $id = false;
190 View Code Duplication
                foreach ($data as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
                    $translated = [];
192
                    if ($object instanceof TranslatableDocumentInterface) {
193
                        $translated = $object->getTranslatableFields();
194
                    }
195
                    if ($key == 'id') {
196
                        $id = $value;
197
                    }
198
                    $method = 'set' . ucfirst($key);
199
                    if (method_exists($object, $method)) {
200
                        if (is_array($value) && in_array($key, $translated)) {
201
                            $this->generateLanguages($domain, $value, $id);
202
                            $object->$method(reset($value));
203
                        } elseif (is_array($value)) {
204
                            $referenceObjects[$key] = [
205
                                'data'    => $data,
206
                                'file'    => $fileName,
207
                                'service' => $serviceName
208
                            ];
209
                        } else {
210
                            try {
211
                                $object->$method($value);
212
                            } catch (\Exception $e) {
213
                                $errors[$fileName] = 'Method: '.$method.' with:'.$value.' error:'.$e->getMessage();
214
                                continue;
215
                            }
216
                        }
217
                    }
218
                }
219
                if ($method) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $method of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
220
                    $documentManager->persist($object);
221
                }
222
            }
223
224
            try {
225
                $documentManager->flush();
226
                $documentManager->clear();
227
            } catch (\Exception $e) {
228
                $errors[$fileName] = 'Save error:'.$e->getMessage();
229
                continue;
230
            }
231
            $success[] = $fileName;
232
        }
233
234
        $progress->finish();
235
236
        // Resume:
237
        $inserted = $totalFiles - count($restObjects) - count($errors);
238
        $output->writeln("\n".'<info>Inserted Objects:'.$inserted.'</info>');
239
240
        // REST $restObjects
241
        if ($restObjects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $restObjects of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
242
            $output->writeln("\n".'Rest Objects:'.count($restObjects));
243
            $errorsRest = $this->putRestObject($restObjects, $output);
244
            $errors = array_merge($errors, $errorsRest);
245
        }
246
247
        // Referenced object update
248
        if ($referenceObjects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $referenceObjects of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
249
            $output->writeln("\n".'References Updating: '.count($referenceObjects));
250
            $errorsRefs = $this->updateReferences($referenceObjects, $output);
251
            $errors = array_merge($errors, $errorsRefs);
252
        }
253
254
255
        // Output's
256
        $output->writeln("\n".'<error>============ Errors: '.count($errors).' ============</error>');
257
        if (!$errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
258
            $output->writeln('<comment>Done without errors</comment>');
259
        } else {
260
            foreach ($errors as $fileName => $error) {
261
                $errString = is_array($error) ? json_encode($error) : $error;
262
                $output->writeln("<comment>$fileName : $errString</comment>");
263
            }
264
        }
265
266
        $documentManager->flush();
267
        $output->writeln("\n".'<info>============ Success: '.count($success).' ============</info>');
268
        if (!$success) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $success of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
269
            $output->writeln('No files imported');
270
        }
271
272
        $output->writeln('<info>============ End ============</info>'."\n");
273
        if (count($errors)) {
274
            $output->writeln('<error>============ Errors: '.count($errors).', please review ============</error>'."\n");
275
        }
276
277
    }
278
279
280
281
    /**
282
     * generate strings for languages
283
     *
284
     * @param string      $domain     Domain Name
285
     * @param array       $languages  Translated language values
286
     * @param string|bool $idLanguage Id to reference name
287
     *
288
     * @return void
289
     */
290
    private function generateLanguages($domain, $languages, $idLanguage = false)
291
    {
292
        foreach ($languages as $language => $translation) {
293
            if ($idLanguage) {
294
                $id = implode('-', array($domain, $idLanguage, $translation));
295
            } else {
296
                $id = implode('-', array($domain, $language, $translation));
297
            }
298
299
            $record = new Translatable;
300
            $record->setId($id);
301
            $record->setDomain($domain);
302
            $record->setLocale($language);
303
            $record->setOriginal($translation);
304
            $this->modelTranslatable->insertRecord($record);
305
        }
306
    }
307
308
    /**
309
     * Some Objects can be done by inserting directly.
310
     *
311
     * @param  array           $restObjects Array of data to be sent via rest
312
     * @param  OutputInterface $output      Sf command line output for progress
313
     * @return array
314
     */
315
    private function putRestObject($restObjects, $output)
316
    {
317
        $errors = [];
318
        $progress = new ProgressBar($output, count($restObjects));
319
        $client = new Client();
320
321
        /** @var SplFileInfo $file */
322
        foreach ($restObjects as $values) {
323
            $progress->advance();
324
            $target = $values['target'];
325
            $json = [
326
                'json' => $values['data']
327
            ];
328
            try {
329
                $client->request(
330
                    'PUT',
331
                    'http://localhost:8000' . $target,
332
                    $json
333
                );
334
            } catch (ServerException $e) {
335
                $errors['REST server: ' . $target] = $e->getMessage() . ' Json:' . json_encode($json);
336
            } catch (ClientException $e) {
337
                $errors['REST client: ' . $target] = $e->getMessage() . ' Json:' . json_encode($json);
338
            }
339
        }
340
        $progress->finish();
341
342
        return $errors;
343
    }
344
345
    /**
346
     * @param array           $referenceObjects Array of objects to be referenced
347
     * @param OutputInterface $output           Command line output
348
     * @return array
349
     */
350
    private function updateReferences($referenceObjects, $output)
351
    {
352
        $errors = [];
353
        $progress = new ProgressBar($output, count($referenceObjects));
354
355
        /** @var Form $formValidator */
356
        $formValidator = $this->container->get('graviton.rest.validator.form');
357
        /** @var FormDataMapperInterface $formDataMapper */
358
        $formDataMapper = $this->container->get('graviton.document.service.formdatamapper');
359
        $request = new Request();
360
        $request->setMethod('PUT');
361
362
        foreach ($referenceObjects as $id => $ref) {
363
            $progress->advance();
364
            // Get Reference
365
            $data = $ref['data'];
366
            $fileName = $ref['file'];
367
            $model = str_replace('.document.', '.model.', $ref['service']);
368
369
            if ($this->container->has($model)) {
370
                /** @var DocumentModel $modelClass */
371
                $modelClass = $this->container->get($model);
372
                $formValidator->getForm($request, $modelClass);
373
                $form = $formValidator->getForm($request, $modelClass);
374
375
                try {
376
                    $record = $formValidator->checkForm(
377
                        $form,
378
                        $modelClass,
379
                        $formDataMapper,
380
                        json_encode($data)
381
                    );
382
                } catch (ValidationException $e) {
383
                    $err = $formValidator->getErrorMessages($form);
384
                    $errors[] = $fileName.': '.$id . ': '.json_encode($err);
385
                    continue;
386
                }
387
388
                $modelClass->updateRecord($id, $record);
389
            } else {
390
                $errors[] = $fileName . ': Model not found to make relation';
391
            }
392
        }
393
394
        $progress->finish();
395
        return $errors;
396
    }
397
398
    /**
399
     * @param array  $targets  Path parts to service
400
     * @param string $fileName File name for easier find error.
401
     * @return array
402
     */
403
    private function findServiceObject($targets, $fileName)
404
    {
405
        // Locate class name graviton.i18n.document.language
406
        $cleanedName = $targets[1].str_replace('refi-', '', $targets[2]);
407
        $serviceNames = [
408
            'gravitondyn.'.$cleanedName.'.document.'.$cleanedName,
409
            'gravitondyn.evojabasics'.$cleanedName.'.document.evojabasics'.$cleanedName,
410
            'gravitondyn.'.$targets[2].'.document.'.$targets[2],
411
            'graviton.'.$targets[1].'.document.'.$targets[2]
412
        ];
413
        $objectClass = false;
414
        $serviceName = false;
415
        foreach ($serviceNames as $serviceName) {
416
            $serviceName = strtolower(str_replace('-', '', $serviceName));
417
            if ($this->container->has($serviceName)) {
418
                try {
419
                    $objectClass = $this->container->get($serviceName);
420
                } catch (\Exception $e) {
421
                    $errors[$fileName] = 'Service name not found: '.$serviceName;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$errors was never initialized. Although not strictly required by PHP, it is generally a good practice to add $errors = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
422
                }
423
                break;
424
            }
425
        }
426
427
        return [
428
            'class' => $objectClass,
429
            'service' => $serviceName
430
        ];
431
    }
432
}
433