Completed
Push — feature/directimport-for-initi... ( 2461f0...8f8de3 )
by
unknown
11:13
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
            $objectId = array_key_exists('id', $data) ? $data['id'] : end($targets);
144
145
            // Locate class name graviton.i18n.document.language
146
            $service = $this->findServiceObject($targets, $fileName);
147
            $objectClass = $service['class'];
148
            $serviceName = $service['service'];
149
150
            if (!$objectClass) {
151
                $restObjects[] = ['target'=> $target, 'data' =>$data];
152
                continue;
153
            }
154
155
            $object = clone $objectClass;
156
            $method = false;
157
            foreach ($data as $key => $value) {
158
                $translated = [];
159
                if ($object instanceof TranslatableDocumentInterface) {
160
                    $translated = $object->getTranslatableFields();
161
                }
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, $objectId);
166
                        $object->$method(reset($value));
167
                    } elseif (is_array($value)) {
168
                        try {
169
                            $object->$method((object) $value);
170
                        } catch (\Exception $e) {
171
                            $referenceObjects[$objectId] = [
172
                                'data' => $data,
173
                                'file' => $fileName,
174
                                'service' => $serviceName
175
                            ];
176
                        }
177
                    } else {
178
                        try {
179
                            $object->$method($value);
180
                        } catch (\Exception $e) {
181
                            $errors[$fileName] = 'Method: '.$method.' with:'.$value.' error:'.$e->getMessage();
182
                            continue;
183
                        }
184
                    }
185
                } else {
186
                    $errors[$fileName] = 'Method: '.$method.' does not exists';
187
                }
188
            }
189
            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...
190
                $documentManager->persist($object);
191
            }
192
193
            try {
194
                $documentManager->flush();
195
                $documentManager->clear();
196
            } catch (\Exception $e) {
197
                $errors[$fileName] = 'Save error:'.$e->getMessage();
198
                continue;
199
            }
200
            $success[] = $fileName;
201
        }
202
203
        $progress->finish();
204
205
        // Resume:
206
        $inserted = $totalFiles - count($restObjects) - count($errors);
207
        $output->writeln("\n".'<info>Inserted Objects:'.$inserted.'</info>');
208
209
        // REST $restObjects
210
        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...
211
            $output->writeln("\n".'Rest Objects:'.count($restObjects));
212
            $errorsRest = $this->putRestObject($restObjects, $output);
213
            $errors = array_merge($errors, $errorsRest);
214
        }
215
216
        // Referenced object update
217
        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...
218
            $output->writeln("\n".'References Updating: '.count($referenceObjects));
219
            $errorsRefs = $this->updateReferences($referenceObjects, $output);
220
            $errors = array_merge($errors, $errorsRefs);
221
        }
222
223
        // Output's
224
        $output->writeln("\n".'<error>============ Errors: '.count($errors).' ============</error>');
225
        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...
226
            $output->writeln('<comment>Done without errors</comment>');
227
        } else {
228
            foreach ($errors as $fileName => $error) {
229
                $errString = is_array($error) ? json_encode($error) : $error;
230
                $output->writeln("<comment>$fileName : $errString</comment>");
231
            }
232
        }
233
234
        $output->writeln("\n".'<info>============ Success: '.count($success).' ============</info>');
235
        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...
236
            $output->writeln('No files imported');
237
        }
238
239
        $output->writeln('<info>============ End ============</info>'."\n");
240
        if (count($errors)) {
241
            $output->writeln('<error>============ Errors: '.count($errors).', please review ============</error>'."\n");
242
        }
243
244
    }
245
246
247
248
    /**
249
     * generate strings for languages
250
     *
251
     * @param string      $domain     Domain Name
252
     * @param array       $languages  Translated language values
253
     * @param string|bool $idLanguage Id to reference name
254
     *
255
     * @return void
256
     */
257
    private function generateLanguages($domain, $languages, $idLanguage = false)
258
    {
259
        foreach ($languages as $language => $translation) {
260
            if ($idLanguage) {
261
                $id = implode('-', array($domain, $idLanguage, $translation));
262
            } else {
263
                $id = implode('-', array($domain, $language, $translation));
264
            }
265
266
            $record = new Translatable;
267
            $record->setId($id);
268
            $record->setDomain($domain);
269
            $record->setLocale($language);
270
            $record->setOriginal($translation);
271
            $this->modelTranslatable->insertRecord($record);
272
        }
273
    }
274
275
    /**
276
     * Some Objects can be done by inserting directly.
277
     *
278
     * @param  array           $restObjects Array of data to be sent via rest
279
     * @param  OutputInterface $output      Sf command line output for progress
280
     * @return array
281
     */
282
    private function putRestObject($restObjects, $output)
283
    {
284
        $errors = [];
285
        $progress = new ProgressBar($output, count($restObjects));
286
        $client = new Client();
287
288
        /** @var SplFileInfo $file */
289
        foreach ($restObjects as $values) {
290
            $progress->advance();
291
            $target = $values['target'];
292
            $json = [
293
                'json' => $values['data']
294
            ];
295
            try {
296
                $client->request(
297
                    'PUT',
298
                    'http://localhost:8000' . $target,
299
                    $json
300
                );
301
            } catch (ServerException $e) {
302
                $errors['REST server: ' . $target] = $e->getMessage() . ' Json:' . json_encode($json);
303
            } catch (ClientException $e) {
304
                $errors['REST client: ' . $target] = $e->getMessage() . ' Json:' . json_encode($json);
305
            }
306
        }
307
        $progress->finish();
308
309
        return $errors;
310
    }
311
312
    /**
313
     * @param array           $referenceObjects Array of objects to be referenced
314
     * @param OutputInterface $output           Command line output
315
     * @return array
316
     */
317
    private function updateReferences($referenceObjects, $output)
318
    {
319
        $errors = [];
320
        $progress = new ProgressBar($output, count($referenceObjects));
321
322
        /** @var Form $formValidator */
323
        $formValidator = $this->container->get('graviton.rest.validator.form');
324
        /** @var FormDataMapperInterface $formDataMapper */
325
        $formDataMapper = $this->container->get('graviton.document.service.formdatamapper');
326
        $request = new Request();
327
        $request->setMethod('PUT');
328
329
        foreach ($referenceObjects as $id => $ref) {
330
            $progress->advance();
331
            // Get Reference
332
            $data = $ref['data'];
333
            $fileName = $ref['file'];
334
            $model = str_replace('.document.', '.model.', $ref['service']);
335
336
            if ($this->container->has($model)) {
337
                /** @var DocumentModel $modelClass */
338
                $modelClass = $this->container->get($model);
339
                $formValidator->getForm($request, $modelClass);
340
                $form = $formValidator->getForm($request, $modelClass);
341
342
                try {
343
                    $record = $formValidator->checkForm(
344
                        $form,
345
                        $modelClass,
346
                        $formDataMapper,
347
                        json_encode($data)
348
                    );
349
                } catch (ValidationException $e) {
350
                    $err = $formValidator->getErrorMessages($form);
351
                    $errors[] = $fileName.': '.$id . ': '.json_encode($err);
352
                    continue;
353
                }
354
355
                $modelClass->updateRecord($id, $record);
356
            } else {
357
                $errors[] = $fileName . ': Model not found to make relation';
358
            }
359
        }
360
361
        $progress->finish();
362
        return $errors;
363
    }
364
365
    /**
366
     * @param array  $targets  Path parts to service
367
     * @param string $fileName File name for easier find error.
368
     * @return array
369
     */
370
    private function findServiceObject($targets, $fileName)
371
    {
372
        // Locate class name graviton.i18n.document.language
373
        $cleanedName = $targets[1].str_replace('refi-', '', $targets[2]);
374
        $serviceNames = [
375
            'gravitondyn.'.$cleanedName.'.document.'.$cleanedName,
376
            'gravitondyn.evojabasics'.$cleanedName.'.document.evojabasics'.$cleanedName,
377
            'gravitondyn.'.$targets[2].'.document.'.$targets[2],
378
            'graviton.'.$targets[1].'.document.'.$targets[2]
379
        ];
380
        $objectClass = false;
381
        $serviceName = false;
382
        foreach ($serviceNames as $serviceName) {
383
            $serviceName = strtolower(str_replace('-', '', $serviceName));
384
            if ($this->container->has($serviceName)) {
385
                try {
386
                    $objectClass = $this->container->get($serviceName);
387
                } catch (\Exception $e) {
388
                    $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...
389
                }
390
                break;
391
            }
392
        }
393
394
        return [
395
            'class' => $objectClass,
396
            'service' => $serviceName
397
        ];
398
    }
399
}
400