Completed
Push — feature/directimport-for-initi... ( 8f8de3...e79d3c )
by
unknown
06:55
created

DirectImportCommand   C

Complexity

Total Complexity 45

Size/Duplication

Total Lines 320
Duplicated Lines 4.38 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 45
c 3
b 0
f 0
lcom 1
cbo 17
dl 14
loc 320
rs 6.2726

5 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 7 1
F execute() 14 176 33
A generateLanguages() 0 17 3
B updateReferences() 0 47 4
B findServiceObject() 0 29 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DirectImportCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DirectImportCommand, and based on these observations, apply Extract Interface, too.

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
        /** @var SplFileInfo $file */
106
        foreach ($finder as $file) {
107
            $fileName = str_replace($location, '', $file->getPathname());
108
109
            $progress->advance();
110
111
            $contents = explode('---', $file->getContents());
112
            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...
113
                $errors[$fileName] = 'Content of file is incorrect, could not parse it with --- ';
114
                continue;
115
            }
116
117
            $target = trim(str_replace('target:', '', $contents[1]));
118
            $yaml = $contents[2];
119
120
            // Make Service Name:
121
            $targets = explode('/', $target);
122
            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...
123
                $errors[$fileName] = 'Target is not correctly defined: '.json_encode($targets);
124
                continue;
125
            }
126
            $domain = $targets[1];
127
128
            try {
129
                $data = Yaml::parse($yaml);
130
            } catch (ParseException $e) {
131
                $errors[$fileName] = 'Could not parse yml file';
132
                continue;
133
            }
134
135
            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...
136
                $errors[$fileName] = 'Yml file is empty or parsed as empty.';
137
                continue;
138
            }
139
140
            $objectId = array_key_exists('id', $data) ? $data['id'] : end($targets);
141
142
            // Locate class name graviton.i18n.document.language
143
            $service = $this->findServiceObject($targets, $fileName);
144
            $objectClass = $service['class'];
145
            $serviceName = $service['service'];
146
147
            if (!$objectClass) {
148
                $errors[$fileName] = 'Has no Service related';
149
                continue;
150
            }
151
152
            $object = clone $objectClass;
153
            $method = false;
154
            foreach ($data as $key => $value) {
155
                $translated = [];
156
                if ($object instanceof TranslatableDocumentInterface) {
157
                    $translated = $object->getTranslatableFields();
158
                }
159
                $method = 'set' . ucfirst($key);
160
161
                if (method_exists($object, $method)) {
162
                    if (is_array($value) && in_array($key, $translated)) {
163
                        $this->generateLanguages($domain, $value, $objectId);
164
                        $object->$method(reset($value));
165
                    } elseif (is_array($value)) {
166
                        try {
167
                            $object->$method((object) $value);
168
                        } catch (\Exception $e) {
169
                            $referenceObjects[$objectId] = [
170
                                'data' => $data,
171
                                'file' => $fileName,
172
                                'service' => $serviceName
173
                            ];
174
                        }
175
                    } else {
176
                        try {
177
                            $object->$method($value);
178
                        } catch (\Exception $e) {
179
                            $errors[$fileName] = 'Method: '.$method.' with:'.$value.' error:'.$e->getMessage();
180
                            continue;
181
                        }
182
                    }
183
                } else {
184
                    $errors[$fileName] = 'Method: '.$method.' does not exists';
185
                }
186
            }
187
            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...
188
                $documentManager->persist($object);
189
            }
190
191
            try {
192
                $documentManager->flush();
193
                $documentManager->clear();
194
            } catch (\Exception $e) {
195
                $errors[$fileName] = 'Save error:'.$e->getMessage();
196
                continue;
197
            }
198
            $success[] = $fileName;
199
        }
200
201
        $progress->finish();
202
203
        // Resume:
204
        $inserted = $totalFiles - count($errors);
205
        $output->writeln("\n".'<info>Inserted Objects:'.$inserted.'</info>');
206
207
        // Referenced object update
208
        $errorsRefs=[];
209
        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...
210
            $output->writeln("\n".'References Updating: '.count($referenceObjects));
211
            $errorsRefs = $this->updateReferences($referenceObjects, $output);
212
        }
213
214
        // Output's
215
216
        if (!$errors && !$errorsRefs) {
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...
Bug Best Practice introduced by
The expression $errorsRefs 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...
217
            $output->writeln('<comment>Done without errors</comment>');
218
        } else {
219
220 View Code Duplication
            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...
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...
221
                $output->writeln("\n".'<error>== Errors: '.count($errors).' ==/error>');
222
                foreach ($errors as $fileName => $error) {
223
                    $errString = is_array($error) ? json_encode($error) : $error;
224
                    $output->writeln("<comment>$fileName : $errString</comment>");
225
                }
226
            }
227
228 View Code Duplication
            if ($errorsRefs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errorsRefs 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...
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...
229
                $output->writeln("\n" . '<error>== Update Ref. Errors: ' . count($errorsRefs) . ' ==</error>');
230
                foreach ($errorsRefs as $fileName => $error) {
231
                    $errString = is_array($error) ? json_encode($error) : $error;
232
                    $output->writeln("<comment>$fileName : $errString</comment>");
233
                }
234
            }
235
        }
236
237
        $output->writeln("\n".'<info>============ Success: '.count($success).' ============</info>');
238
        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...
239
            $output->writeln('No files imported');
240
        }
241
242
        $output->writeln('<info>============ End ============</info>'."\n");
243
        if (count($errors)) {
244
            $output->writeln('<error>== Errors: '.(count($errors)+count($errorsRefs)).', please check ==</error>'."\n");
245
        }
246
247
    }
248
249
250
251
    /**
252
     * generate strings for languages
253
     *
254
     * @param string      $domain     Domain Name
255
     * @param array       $languages  Translated language values
256
     * @param string|bool $idLanguage Id to reference name
257
     *
258
     * @return void
259
     */
260
    private function generateLanguages($domain, $languages, $idLanguage = false)
261
    {
262
        foreach ($languages as $language => $translation) {
263
            if ($idLanguage) {
264
                $id = implode('-', array($domain, $idLanguage, $translation));
265
            } else {
266
                $id = implode('-', array($domain, $language, $translation));
267
            }
268
269
            $record = new Translatable;
270
            $record->setId($id);
271
            $record->setDomain($domain);
272
            $record->setLocale($language);
273
            $record->setOriginal($translation);
274
            $this->modelTranslatable->insertRecord($record);
275
        }
276
    }
277
278
279
    /**
280
     * @param array           $referenceObjects Array of objects to be referenced
281
     * @param OutputInterface $output           Command line output
282
     * @return array
283
     */
284
    private function updateReferences($referenceObjects, $output)
285
    {
286
        $errors = [];
287
        $progress = new ProgressBar($output, count($referenceObjects));
288
289
        /** @var Form $formValidator */
290
        $formValidator = $this->container->get('graviton.rest.validator.form');
291
        /** @var FormDataMapperInterface $formDataMapper */
292
        $formDataMapper = $this->container->get('graviton.document.service.formdatamapper');
293
        $request = new Request();
294
        $request->setMethod('PUT');
295
296
        foreach ($referenceObjects as $id => $ref) {
297
            $progress->advance();
298
            // Get Reference
299
            $data = $ref['data'];
300
            $fileName = $ref['file'];
301
            $model = str_replace('.document.', '.model.', $ref['service']);
302
303
            if ($this->container->has($model)) {
304
                /** @var DocumentModel $modelClass */
305
                $modelClass = $this->container->get($model);
306
                $formValidator->getForm($request, $modelClass);
307
                $form = $formValidator->getForm($request, $modelClass);
308
309
                try {
310
                    $record = $formValidator->checkForm(
311
                        $form,
312
                        $modelClass,
313
                        $formDataMapper,
314
                        json_encode($data)
315
                    );
316
                } catch (ValidationException $e) {
317
                    $err = $formValidator->getErrorMessages($form);
318
                    $errors[] = $fileName.': '.$id . ': '.json_encode($err);
319
                    continue;
320
                }
321
322
                $modelClass->updateRecord($id, $record);
323
            } else {
324
                $errors[] = $fileName . ': Model not found to make relation';
325
            }
326
        }
327
328
        $progress->finish();
329
        return $errors;
330
    }
331
332
    /**
333
     * @param array  $targets  Path parts to service
334
     * @param string $fileName File name for easier find error.
335
     * @return array
336
     */
337
    private function findServiceObject($targets, $fileName)
338
    {
339
        // Locate class name graviton.i18n.document.language
340
        $cleanedName = $targets[1].str_replace('refi-', '', $targets[2]);
341
        $serviceNames = [
342
            'gravitondyn.'.$cleanedName.'.document.'.$cleanedName,
343
            'gravitondyn.evojabasics'.$cleanedName.'.document.evojabasics'.$cleanedName,
344
            'gravitondyn.'.$targets[2].'.document.'.$targets[2],
345
            'graviton.'.$targets[1].'.document.'.$targets[2]
346
        ];
347
        $objectClass = false;
348
        $serviceName = false;
349
        foreach ($serviceNames as $serviceName) {
350
            $serviceName = strtolower(str_replace('-', '', $serviceName));
351
            if ($this->container->has($serviceName)) {
352
                try {
353
                    $objectClass = $this->container->get($serviceName);
354
                } catch (\Exception $e) {
355
                    $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...
356
                }
357
                break;
358
            }
359
        }
360
361
        return [
362
            'class' => $objectClass,
363
            'service' => $serviceName
364
        ];
365
    }
366
}
367