Passed
Push — master ( eb9975...e5d815 )
by Matthew
01:56
created

Mapper::getField()   C

Complexity

Conditions 12
Paths 20

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
c 0
b 0
f 0
dl 0
loc 38
rs 6.9666
cc 12
nc 20
nop 2

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

143
            $value = $this->handleType(/** @scrutinizer ignore-type */ $type, $class, $objectData, $field, $salsifyField, $dbField);
Loading history...
144
            $this->writeValue($object, $dbField, $value);
145
        }
146
147
        if ($object->isChanged()) {
148
            $object->write();
149
            $this->importCount++;
150
        } else {
151
            ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed.");
152
        }
153
        return $object;
154
    }
155
156
    /**
157
     * @param DataObject $object
158
     * @param array $data
159
     * @param string $firstUniqueKey
160
     * @param string $firstUniqueValue
161
     * @return bool
162
     */
163
    private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)
164
    {
165
        if (
166
            $this->config()->get('skipUpToDate') == true &&
167
            $object->hasField('SalsifyUpdatedAt') &&
168
            $data['salsify:updated_at'] == $object->getField('SalsifyUpdatedAt')
169
        ) {
170
            ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue. It is up to Date.");
171
            return true;
172
        }
173
        return false;
174
    }
175
176
    /**
177
     * @param array $salsifyField
178
     * @param array $data
179
     *
180
     * @return string|false
181
     */
182
    private function getField($salsifyField, $data)
183
    {
184
        if (!is_array($salsifyField)) {
0 ignored issues
show
introduced by
The condition is_array($salsifyField) is always true.
Loading history...
185
            return array_key_exists($salsifyField, $data) ? $salsifyField : false;
186
        }
187
188
        $hasSalsifyField = array_key_exists('salsifyField', $salsifyField);
189
        $isLiteralField = (
190
            $this->getFieldType($salsifyField) === 'Literal' &&
191
            array_key_exists('value', $salsifyField)
192
        );
193
194
        if ($isLiteralField) {
195
            return $salsifyField['value'];
196
        }
197
198
        if (!$hasSalsifyField) {
199
            return false;
200
        }
201
202
        if (array_key_exists($salsifyField['salsifyField'], $data)) {
203
            return $salsifyField['salsifyField'];
204
        } elseif (array_key_exists('fallback', $salsifyField)) {
205
            // make fallback an array
206
            if (!is_array($salsifyField['fallback'])) {
207
                $salsifyField['fallback'] = [$salsifyField['fallback']];
208
            }
209
210
            foreach ($salsifyField['fallback'] as $fallback) {
211
                if (array_key_exists($fallback, $data)) {
212
                    return $fallback;
213
                }
214
            }
215
        } elseif (array_key_exists('modification', $salsifyField)) {
216
            return $salsifyField['salsifyField'];
217
        }
218
219
        return false;
220
    }
221
222
    /**
223
     * @param string $class
224
     * @param array $mappings
225
     * @param array $data
226
     *
227
     * @return \SilverStripe\ORM\DataObject
228
     */
229
    private function findObjectByUnique($class, $mappings, $data)
230
    {
231
        $uniqueFields = $this->uniqueFields($class, $mappings);
232
        // creates a filter
233
        $filter = [];
234
        foreach ($uniqueFields as $dbField => $salsifyField) {
235
            $modifiedData = $data;
236
            $fieldMapping = $mappings[$dbField];
237
238
            $modifiedData = $this->handleModification($class, $dbField, $fieldMapping, $modifiedData);
239
240
            // adds unique fields to filter
241
            if (array_key_exists($salsifyField, $modifiedData)) {
242
                $filter[$dbField] = $modifiedData[$salsifyField];
243
            }
244
        }
245
246
        return DataObject::get($class)->filter($filter)->first();
247
    }
248
249
    /**
250
     * Gets a list of all the unique field keys
251
     *
252
     * @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...
253
     * @param array $mappings
254
     * @return array
255
     */
256
    private function uniqueFields($class, $mappings)
257
    {
258
        // cached after first map
259
        if (array_key_exists($class, $this->currentUniqueFields) && !empty($this->currentUniqueFields[$class])) {
260
            return $this->currentUniqueFields[$class];
261
        }
262
263
        $uniqueFields = [];
264
        foreach ($mappings as $dbField => $salsifyField) {
265
            if (!is_array($salsifyField)) {
266
                continue;
267
            }
268
269
            if (
270
                !array_key_exists('unique', $salsifyField) ||
271
                !array_key_exists('salsifyField', $salsifyField)
272
            ) {
273
                continue;
274
            }
275
276
            if ($salsifyField['unique'] !== true) {
277
                continue;
278
            }
279
280
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
281
        }
282
283
        $this->currentUniqueFields[$class] = $uniqueFields;
284
        return $uniqueFields;
285
    }
286
287
    /**
288
     * @param string $class
289
     * @param string $dbField
290
     * @param array $config
291
     * @param array $data
292
     * @return array
293
     */
294
    private function handleModification($class, $dbField, $config, $data)
295
    {
296
        if (array_key_exists('modification', $config)) {
297
            $mod = $config['modification'];
298
            if ($this->hasMethod($mod)) {
299
                return $this->{$mod}($class, $dbField, $config, $data);
300
            }
301
            ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
302
        }
303
        return $data;
304
    }
305
306
    /**
307
     * @param string $class
308
     * @param string $dbField
309
     * @param array $config
310
     * @param array $data
311
     * @return boolean
312
     */
313
    private function handleShouldSkip($class, $dbField, $config, $data)
314
    {
315
        if (array_key_exists('shouldSkip', $config)) {
316
            $skipMethod = $config['shouldSkip'];
317
            if ($this->hasMethod($skipMethod)) {
318
                return $this->{$skipMethod}($class, $dbField, $config, $data);
319
            }
320
            ImportTask::output(
321
                "{$skipMethod} is not a valid skip test method. Skipping skip test for field {$dbField}."
322
            );
323
        }
324
        return false;
325
    }
326
327
    /**
328
     * @param string|array $field
329
     * @return string
330
     */
331
    public function getFieldType($field)
332
    {
333
        $fieldTypes = $this->config()->get('field_types');
334
        if (is_array($field) && array_key_exists('type', $field)) {
335
            if (in_array($field['type'], $fieldTypes)) {
336
                return $field['type'];
337
            }
338
        }
339
        // default to raw
340
        return 'Raw';
341
    }
342
343
    /**
344
     * @param int $type
345
     * @param string|DataObject $class
346
     * @param array $salsifyData
347
     * @param string $salsifyField
348
     * @param array $dbFieldConfig
349
     * @param string $dbField
350
     *
351
     * @return mixed
352
     */
353
    private function handleType($type, $class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField)
354
    {
355
        if ($this->hasMethod("handle{$type}Type")) {
356
            return $this->{"handle{$type}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
357
        }
358
        ImportTask::output("{$type} is not a valid type. skipping field {$dbField}.");
359
        return '';
360
    }
361
362
    /**
363
     * @param DataObject $object
364
     * @param string $dbField
365
     * @param mixed $value
366
     *
367
     * @throws \Exception
368
     */
369
    private function writeValue($object, $dbField, $value)
370
    {
371
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
372
            array_key_exists($dbField, $object->config()->get('many_many')) ||
373
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
374
375
        if (!$isManyRelation) {
376
            $object->$dbField = $value;
377
            return;
378
        }
379
380
        if (!$object->exists()) {
381
            $object->write();
382
        }
383
384
        if (is_array($value)) {
385
            $object->{$dbField}()->addMany($value);
386
            return;
387
        }
388
389
        $object->{$dbField}()->add($value);
390
    }
391
392
    /**
393
     * @return \JsonMachine\JsonMachine
394
     */
395
    public function getAssetStream()
396
    {
397
        return $this->assetStream;
398
    }
399
400
    /**
401
     * @return bool
402
     */
403
    public function hasFile()
404
    {
405
        return $this->file !== null;
406
    }
407
}
408