Passed
Push — master ( 0b1635...98680e )
by Matthew
02:15
created

Mapper::mapToObject()   C

Complexity

Conditions 12
Paths 120

Size

Total Lines 60
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 38
c 3
b 0
f 0
dl 0
loc 60
rs 6.8
cc 12
nc 120
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Dynamic\Salsify\Task\ImportTask;
6
use Exception;
7
use JsonMachine\JsonMachine;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\Versioned\Versioned;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Versioned\Versioned was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
11
/**
12
 * Class Mapper
13
 * @package Dynamic\Salsify\Model
14
 */
15
class Mapper extends Service
16
{
17
18
    /**
19
     * @var
20
     */
21
    private $file = null;
22
23
    /**
24
     * @var JsonMachine
25
     */
26
    private $productStream;
27
28
    /**
29
     * @var JsonMachine
30
     */
31
    private $assetStream;
32
33
    /**
34
     * @var array
35
     */
36
    private $currentUniqueFields = [];
37
38
    /**
39
     * @var int
40
     */
41
    private $importCount = 0;
42
43
    /**
44
     * @var bool
45
     */
46
    public $skipSiliently = false;
47
48
    /**
49
     * Mapper constructor.
50
     * @param string $importerKey
51
     * @param $file
52
     * @throws \Exception
53
     */
54
    public function __construct($importerKey, $file = null)
55
    {
56
        parent::__construct($importerKey);
57
        if (!$this->config()->get('mapping')) {
58
            throw  new Exception('A Mapper needs a mapping');
59
        }
60
61
        if ($file !== null) {
62
            $this->file = $file;
63
            $this->productStream = JsonMachine::fromFile($file, '/4/products');
64
            $this->resetAssetStream();
65
        }
66
    }
67
68
    /**
69
     *
70
     */
71
    public function resetAssetStream()
72
    {
73
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
74
    }
75
76
    /**
77
     * Maps the data
78
     * @throws \Exception
79
     */
80
    public function map()
81
    {
82
        foreach ($this->productStream as $name => $data) {
83
            foreach ($this->config()->get('mapping') as $class => $mappings) {
84
                $this->mapToObject($class, $mappings, $data);
85
                $this->currentUniqueFields = [];
86
            }
87
        }
88
        ImportTask::output("Imported and updated $this->importCount products.");
89
    }
90
91
    /**
92
     * @param string|DataObject $class
93
     * @param array $mappings The mapping for a specific class
94
     * @param array $data
95
     *
96
     * @return DataObject
97
     * @throws \Exception
98
     */
99
    public function mapToObject($class, $mappings, $data)
100
    {
101
        $object = $this->findObjectByUnique($class, $mappings, $data);
102
        if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
103
            $object = $class::create();
104
        }
105
106
        $wasPublished = $object->hasExtension(Versioned::class) ? $object->isPublished() : false;
107
        $wasWritten = $object->isInDB();
108
109
        $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0];
110
        if (array_key_exists($mappings[$firstUniqueKey]['salsifyField'], $data)) {
111
            $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
112
        } else {
113
            $firstUniqueValue = 'NULL';
114
        }
115
        ImportTask::output("Updating $class $firstUniqueKey $firstUniqueValue");
116
117
        if ($this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) {
118
            return $object;
119
        }
120
121
        foreach ($mappings as $dbField => $salsifyField) {
122
            $field = $this->getField($salsifyField, $data);
123
            if ($field === false) {
124
                continue;
125
            }
126
127
            $value = null;
128
            $type = $this->getFieldType($salsifyField);
129
            $objectData = $data;
130
131
            if (is_array($salsifyField)) {
132
                if ($this->handleShouldSkip($class, $dbField, $salsifyField, $data)) {
133
                    if (!$this->skipSiliently) {
134
                        ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue");
135
                        $this->skipSiliently = false;
136
                    }
137
                    return null;
138
                };
139
140
                $objectData = $this->handleModification($class, $dbField, $salsifyField, $data);
141
            }
142
143
            if (!array_key_exists($field, $objectData)) {
144
                continue;
145
            }
146
147
            $value = $this->handleType($type, $class, $objectData, $field, $salsifyField, $dbField);
0 ignored issues
show
Bug introduced by
$type of type string is incompatible with the type integer expected by parameter $type of Dynamic\Salsify\Model\Mapper::handleType(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
            $value = $this->handleType(/** @scrutinizer ignore-type */ $type, $class, $objectData, $field, $salsifyField, $dbField);
Loading history...
148
            $this->writeValue($object, $dbField, $value);
149
        }
150
151
        if ($object->isChanged()) {
152
            $object->write();
153
            $this->importCount++;
154
            $this->extend('afterObjectWrite', $object, $wasWritten, $wasPublished);
155
        } else {
156
            ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed.");
157
        }
158
        return $object;
159
    }
160
161
    /**
162
     * @param DataObject $object
163
     * @param array $data
164
     * @param string $firstUniqueKey
165
     * @param string $firstUniqueValue
166
     * @return bool
167
     */
168
    private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)
169
    {
170
        if (
171
            $this->config()->get('skipUpToDate') == true &&
172
            $object->hasField('SalsifyUpdatedAt') &&
173
            $data['salsify:updated_at'] == $object->getField('SalsifyUpdatedAt')
174
        ) {
175
            ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue. It is up to Date.");
176
            return true;
177
        }
178
        return false;
179
    }
180
181
    /**
182
     * @param array $salsifyField
183
     * @param array $data
184
     *
185
     * @return string|false
186
     */
187
    private function getField($salsifyField, $data)
188
    {
189
        if (!is_array($salsifyField)) {
0 ignored issues
show
introduced by
The condition is_array($salsifyField) is always true.
Loading history...
190
            return array_key_exists($salsifyField, $data) ? $salsifyField : false;
191
        }
192
193
        $hasSalsifyField = array_key_exists('salsifyField', $salsifyField);
194
        $isLiteralField = (
195
            $this->getFieldType($salsifyField) === 'Literal' &&
196
            array_key_exists('value', $salsifyField)
197
        );
198
199
        if ($isLiteralField) {
200
            return $salsifyField['value'];
201
        }
202
203
        if (!$hasSalsifyField) {
204
            return false;
205
        }
206
207
        if (array_key_exists($salsifyField['salsifyField'], $data)) {
208
            return $salsifyField['salsifyField'];
209
        } elseif (array_key_exists('fallback', $salsifyField)) {
210
            // make fallback an array
211
            if (!is_array($salsifyField['fallback'])) {
212
                $salsifyField['fallback'] = [$salsifyField['fallback']];
213
            }
214
215
            foreach ($salsifyField['fallback'] as $fallback) {
216
                if (array_key_exists($fallback, $data)) {
217
                    return $fallback;
218
                }
219
            }
220
        } elseif (array_key_exists('modification', $salsifyField)) {
221
            return $salsifyField['salsifyField'];
222
        }
223
224
        return false;
225
    }
226
227
    /**
228
     * @param string $class
229
     * @param array $mappings
230
     * @param array $data
231
     *
232
     * @return \SilverStripe\ORM\DataObject
233
     */
234
    private function findObjectByUnique($class, $mappings, $data)
235
    {
236
        $uniqueFields = $this->uniqueFields($class, $mappings);
237
        // creates a filter
238
        $filter = [];
239
        foreach ($uniqueFields as $dbField => $salsifyField) {
240
            $modifiedData = $data;
241
            $fieldMapping = $mappings[$dbField];
242
243
            $modifiedData = $this->handleModification($class, $dbField, $fieldMapping, $modifiedData);
244
245
            // adds unique fields to filter
246
            if (array_key_exists($salsifyField, $modifiedData)) {
247
                $filter[$dbField] = $modifiedData[$salsifyField];
248
            }
249
        }
250
251
        return DataObject::get($class)->filter($filter)->first();
252
    }
253
254
    /**
255
     * Gets a list of all the unique field keys
256
     *
257
     * @param string class
0 ignored issues
show
Bug introduced by
The type Dynamic\Salsify\Model\class was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
258
     * @param array $mappings
259
     * @return array
260
     */
261
    private function uniqueFields($class, $mappings)
262
    {
263
        // cached after first map
264
        if (array_key_exists($class, $this->currentUniqueFields) && !empty($this->currentUniqueFields[$class])) {
265
            return $this->currentUniqueFields[$class];
266
        }
267
268
        $uniqueFields = [];
269
        foreach ($mappings as $dbField => $salsifyField) {
270
            if (!is_array($salsifyField)) {
271
                continue;
272
            }
273
274
            if (
275
                !array_key_exists('unique', $salsifyField) ||
276
                !array_key_exists('salsifyField', $salsifyField)
277
            ) {
278
                continue;
279
            }
280
281
            if ($salsifyField['unique'] !== true) {
282
                continue;
283
            }
284
285
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
286
        }
287
288
        $this->currentUniqueFields[$class] = $uniqueFields;
289
        return $uniqueFields;
290
    }
291
292
    /**
293
     * @param string $class
294
     * @param string $dbField
295
     * @param array $config
296
     * @param array $data
297
     * @return array
298
     */
299
    private function handleModification($class, $dbField, $config, $data)
300
    {
301
        if (array_key_exists('modification', $config)) {
302
            $mod = $config['modification'];
303
            if ($this->hasMethod($mod)) {
304
                return $this->{$mod}($class, $dbField, $config, $data);
305
            }
306
            ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
307
        }
308
        return $data;
309
    }
310
311
    /**
312
     * @param string $class
313
     * @param string $dbField
314
     * @param array $config
315
     * @param array $data
316
     * @return boolean
317
     */
318
    private function handleShouldSkip($class, $dbField, $config, $data)
319
    {
320
        if (array_key_exists('shouldSkip', $config)) {
321
            $skipMethod = $config['shouldSkip'];
322
            if ($this->hasMethod($skipMethod)) {
323
                return $this->{$skipMethod}($class, $dbField, $config, $data);
324
            }
325
            ImportTask::output(
326
                "{$skipMethod} is not a valid skip test method. Skipping skip test for field {$dbField}."
327
            );
328
        }
329
        return false;
330
    }
331
332
    /**
333
     * @param string|array $field
334
     * @return string
335
     */
336
    public function getFieldType($field)
337
    {
338
        $fieldTypes = $this->config()->get('field_types');
339
        if (is_array($field) && array_key_exists('type', $field)) {
340
            if (in_array($field['type'], $fieldTypes)) {
341
                return $field['type'];
342
            }
343
        }
344
        // default to raw
345
        return 'Raw';
346
    }
347
348
    /**
349
     * @param int $type
350
     * @param string|DataObject $class
351
     * @param array $salsifyData
352
     * @param string $salsifyField
353
     * @param array $dbFieldConfig
354
     * @param string $dbField
355
     *
356
     * @return mixed
357
     */
358
    private function handleType($type, $class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField)
359
    {
360
        if ($this->hasMethod("handle{$type}Type")) {
361
            return $this->{"handle{$type}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
362
        }
363
        ImportTask::output("{$type} is not a valid type. skipping field {$dbField}.");
364
        return '';
365
    }
366
367
    /**
368
     * @param DataObject $object
369
     * @param string $dbField
370
     * @param mixed $value
371
     *
372
     * @throws \Exception
373
     */
374
    private function writeValue($object, $dbField, $value)
375
    {
376
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
377
            array_key_exists($dbField, $object->config()->get('many_many')) ||
378
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
379
380
        if (!$isManyRelation) {
381
            $object->$dbField = $value;
382
            return;
383
        }
384
385
        if (!$object->exists()) {
386
            $object->write();
387
        }
388
389
        if (is_array($value)) {
390
            $object->{$dbField}()->addMany($value);
391
            return;
392
        }
393
394
        $object->{$dbField}()->add($value);
395
    }
396
397
    /**
398
     * @return \JsonMachine\JsonMachine
399
     */
400
    public function getAssetStream()
401
    {
402
        return $this->assetStream;
403
    }
404
405
    /**
406
     * @return bool
407
     */
408
    public function hasFile()
409
    {
410
        return $this->file !== null;
411
    }
412
}
413