Passed
Push — master ( b0babf...f41801 )
by Matthew
02:12
created

Mapper::getField()   B

Complexity

Conditions 11
Paths 18

Size

Total Lines 36
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
c 0
b 0
f 0
dl 0
loc 36
rs 7.3166
cc 11
nc 18
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
     * Mapper constructor.
44
     * @param string $importerKey
45
     * @param $file
46
     * @throws \Exception
47
     */
48
    public function __construct($importerKey, $file = null)
49
    {
50
        parent::__construct($importerKey);
51
        if (!$this->config()->get('mapping')) {
52
            throw  new Exception('A Mapper needs a mapping');
53
        }
54
55
        if ($file !== null) {
56
            $this->file = $file;
57
            $this->productStream = JsonMachine::fromFile($file, '/4/products');
58
            $this->resetAssetStream();
59
        }
60
    }
61
62
    /**
63
     *
64
     */
65
    public function resetAssetStream()
66
    {
67
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
68
    }
69
70
    /**
71
     * Maps the data
72
     * @throws \Exception
73
     */
74
    public function map()
75
    {
76
        foreach ($this->productStream as $name => $data) {
77
            foreach ($this->config()->get('mapping') as $class => $mappings) {
78
                $this->mapToObject($class, $mappings, $data);
79
                $this->currentUniqueFields = [];
80
            }
81
        }
82
        ImportTask::output("Imported and updated $this->importCount products.");
83
    }
84
85
    /**
86
     * @param string|DataObject $class
87
     * @param array $mappings The mapping for a specific class
88
     * @param array $data
89
     *
90
     * @return DataObject
91
     * @throws \Exception
92
     */
93
    public function mapToObject($class, $mappings, $data)
94
    {
95
        $object = $this->findObjectByUnique($class, $mappings, $data);
96
        if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
97
            $object = $class::create();
98
        }
99
100
        $firstUniqueKey = array_keys($this->uniqueFields($mappings))[0];
101
        $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
102
        ImportTask::output("Updating $firstUniqueKey $firstUniqueValue");
103
104
        if ($this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) {
105
            return $object;
106
        }
107
108
        foreach ($mappings as $dbField => $salsifyField) {
109
            $field = $this->getField($salsifyField, $data);
110
            if ($field === false) {
111
                continue;
112
            }
113
            
114
            $value = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
115
            $type = $this->getFieldType($salsifyField);
116
            $objectData = $data;
117
118
            if (is_array($salsifyField)) {
119
                if ($this->handleShouldSkip($dbField, $salsifyField, $data)) {
120
                    ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue");
121
                    return null;
122
                };
123
124
                $objectData = $this->handleModification($dbField, $salsifyField, $data);
125
            }
126
127
            $value = $this->handleType($type, $objectData, $field, $salsifyField, $dbField, $class);
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

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