Passed
Pull Request — master (#19)
by Matthew
01:49
created

Mapper::hasFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
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
        foreach ($mappings as $dbField => $salsifyField) {
105
            $field = $salsifyField;
106
            $value = null;
107
            // default to raw
108
            $type = $this->getFieldType($salsifyField);
109
            $objectData = $data;
110
111
            if (is_array($salsifyField)) {
112
                if (!array_key_exists('salsifyField', $salsifyField)) {
113
                    continue;
114
                }
115
                $field = $salsifyField['salsifyField'];
116
117
                if (array_key_exists('shouldSkip', $salsifyField)) {
118
                    if ($this->handleShouldSkip(
119
                        $salsifyField['shouldSkip'],
120
                        $dbField,
121
                        $salsifyField,
122
                        $data
123
                    )) {
124
                        ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue");
125
                        return null;
126
                    };
127
                }
128
129
                if (array_key_exists('modification', $salsifyField)) {
130
                    $objectData = $this->handleModification(
131
                        $salsifyField['modification'],
132
                        $dbField,
133
                        $salsifyField,
134
                        $data
135
                    );
136
                }
137
            }
138
139
            if (!array_key_exists($field, $objectData)) {
140
                continue;
141
            }
142
143
            $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

143
            $value = $this->handleType(/** @scrutinizer ignore-type */ $type, $objectData, $field, $salsifyField, $dbField, $class);
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("$firstUniqueKey $firstUniqueValue was not changed.");
152
        }
153
        return $object;
154
    }
155
156
    /**
157
     * @param string $class
158
     * @param array $mappings
159
     * @param array $data
160
     *
161
     * @return \SilverStripe\ORM\DataObject
162
     */
163
    private function findObjectByUnique($class, $mappings, $data)
164
    {
165
        $uniqueFields = $this->uniqueFields($mappings);
166
        // creates a filter
167
        $filter = [];
168
        foreach ($uniqueFields as $dbField => $salsifyField) {
169
            $modifiedData = $data;
170
            $fieldMapping = $mappings[$dbField];
171
172
            if (array_key_exists('modification', $fieldMapping)) {
173
                $modifiedData = $this->handleModification(
174
                    $fieldMapping['modification'],
175
                    $dbField,
176
                    $fieldMapping,
177
                    $modifiedData
178
                );
179
            }
180
181
            // adds unique fields to filter
182
            $filter[$dbField] = $modifiedData[$salsifyField];
183
        }
184
185
        return DataObject::get($class)->filter($filter)->first();
186
    }
187
188
    /**
189
     * Gets a list of all the unique field keys
190
     *
191
     * @param array $mappings
192
     * @return array
193
     */
194
    private function uniqueFields($mappings)
195
    {
196
        // cached after first map
197
        if (!empty($this->currentUniqueFields)) {
198
            return $this->currentUniqueFields;
199
        }
200
201
        $uniqueFields = [];
202
        foreach ($mappings as $dbField => $salsifyField) {
203
            if (!is_array($salsifyField)) {
204
                continue;
205
            }
206
207
            if (!array_key_exists('unique', $salsifyField) ||
208
                !array_key_exists('salsifyField', $salsifyField)) {
209
                continue;
210
            }
211
212
            if ($salsifyField['unique'] !== true) {
213
                continue;
214
            }
215
216
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
217
        }
218
219
        $this->currentUniqueFields = $uniqueFields;
220
        return $uniqueFields;
221
    }
222
223
    /**
224
     * @param string $mod
225
     * @param string $dbField
226
     * @param array $config
227
     * @param array $data
228
     * @return array
229
     */
230
    private function handleModification($mod, $dbField, $config, $data)
231
    {
232
        if ($this->hasMethod($mod)) {
233
            return $this->{$mod}($dbField, $config, $data);
234
        }
235
        ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
236
        return $data;
237
    }
238
239
    /**
240
     * @param string $skipMethod
241
     * @param string $dbField
242
     * @param array $config
243
     * @param array $data
244
     * @return boolean
245
     */
246
    private function handleShouldSkip($skipMethod, $dbField, $config, $data)
247
    {
248
        if ($this->hasMethod($skipMethod)) {
249
            return $this->{$skipMethod}($dbField, $config, $data);
250
        }
251
        ImportTask::output("{$skipMethod} is not a valid skip test method. skipping skip test for field {$dbField}.");
252
        return false;
253
    }
254
255
    /**
256
     * @param string|array $field
257
     * @return string
258
     */
259
    public function getFieldType($field)
260
    {
261
        $fieldTypes = $this->config()->get('field_types');
262
        if (is_array($field) && array_key_exists('type', $field)) {
263
            if (in_array($field['type'], $fieldTypes)) {
264
                return $field['type'];
265
            }
266
        }
267
        return 'Raw';
268
    }
269
270
    /**
271
     * @param int $type
272
     * @param array $salsifyData
273
     * @param string $salsifyField
274
     * @param array $dbFieldConfig
275
     * @param string $dbField
276
     * @param string $class
277
     *
278
     * @return mixed
279
     */
280
    private function handleType($type, $salsifyData, $salsifyField, $dbFieldConfig, $dbField, $class)
281
    {
282
        if ($this->hasMethod("handle{$type}Type")) {
283
            return $this->{"handle{$type}Type"}($salsifyData, $salsifyField, $dbFieldConfig, $dbField, $class);
284
        }
285
        ImportTask::output("{$type} is not a valid type. skipping field {$dbField}.");
286
        return '';
287
    }
288
289
    /**
290
     * @param DataObject $object
291
     * @param string $dbField
292
     * @param mixed $value
293
     *
294
     * @throws \Exception
295
     */
296
    private function writeValue($object, $dbField, $value)
297
    {
298
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
299
            array_key_exists($dbField, $object->config()->get('many_many')) ||
300
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
301
302
        if (!$isManyRelation) {
303
            $object->$dbField = $value;
304
            return;
305
        }
306
307
        if (!$object->exists()) {
308
            $object->write();
309
        }
310
311
        if (is_array($value)) {
312
            $object->{$dbField}()->addMany($value);
313
            return;
314
        }
315
316
        $object->{$dbField}()->add($value);
317
    }
318
319
    /**
320
     * @return \JsonMachine\JsonMachine
321
     */
322
    public function getAssetStream()
323
    {
324
        return $this->assetStream;
325
    }
326
327
    /**
328
     * @return bool
329
     */
330
    public function hasFile()
331
    {
332
        return $this->file !== null;
333
    }
334
}
335